From 9eb405fadeda3ac4816ce0f58bce1d34126e8301 Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Thu, 10 Sep 2020 08:41:39 +0100 Subject: [PATCH 001/651] Don't sort grouped musicvideo artists by label if random order specified in xsp --- xbmc/filesystem/SmartPlaylistDirectory.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/xbmc/filesystem/SmartPlaylistDirectory.cpp b/xbmc/filesystem/SmartPlaylistDirectory.cpp index d768361000b52..985e1f6d19b74 100644 --- a/xbmc/filesystem/SmartPlaylistDirectory.cpp +++ b/xbmc/filesystem/SmartPlaylistDirectory.cpp @@ -282,9 +282,23 @@ namespace XFILE items.SetProperty(PROPERTY_GROUP_MIXED, playlist.IsGroupMixed()); } - // sort grouped list by label + // sort grouped list by label unless random was specified for musicvideo artists if (items.Size() > 1 && !group.empty()) - items.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); + { + if (playlist.GetOrder() == SortByRandom && group == "actors" && + playlist.GetType() == "musicvideos") + items.Sort(SortByRandom, SortOrderAscending, + CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) + ? SortAttributeIgnoreArticle + : SortAttributeNone); + else + items.Sort(SortByLabel, SortOrderAscending, + CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) + ? SortAttributeIgnoreArticle + : SortAttributeNone); + } // go through and set the playlist order for (int i = 0; i < items.Size(); i++) From 2e64c66c8bd8d3261b7ca1b9d1f3b31866f8f982 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Tue, 5 Mar 2024 16:37:37 +0100 Subject: [PATCH 002/651] Move string literals to android resource files --- .../android/packaging/xbmc/src/Splash.java.in | 28 +++++++++---------- tools/android/packaging/xbmc/strings.xml.in | 16 +++++++++++ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/tools/android/packaging/xbmc/src/Splash.java.in b/tools/android/packaging/xbmc/src/Splash.java.in index 19e33a676ff37..1dbed2bcce46c 100644 --- a/tools/android/packaging/xbmc/src/Splash.java.in +++ b/tools/android/packaging/xbmc/src/Splash.java.in @@ -100,19 +100,19 @@ public class Splash extends Activity switch (mSplash.mState) { case InError: - showErrorDialog(mSplash, "Error", mErrorMsg); + showErrorDialog(mSplash, getString(R.string.error_title), mErrorMsg); break; case CheckingPermissionsInfo: AlertDialog dialog = new AlertDialog.Builder(mSplash).create(); dialog.setCancelable(false); - dialog.setTitle("Info"); - dialog.setMessage("@APP_NAME@ requires access to your device media and files to function. Please allow this via the following dialogue box or @APP_NAME@ will exit."); - dialog.setButton(DialogInterface.BUTTON_NEUTRAL, "continue", + dialog.setTitle(getString(R.string.info_title)); + dialog.setMessage(getString(R.string.notice_dialog, "@APP_NAME@")); + dialog.setButton(DialogInterface.BUTTON_NEUTRAL, getString(R.string.continue_button), (dialog1, which) -> mStateMachine.sendEmptyMessage(RecordAudioPermission)); dialog.show(); break; case RecordAudioPermission: - mSplash.mTextView.setText("Asking for permissions..."); + mSplash.mTextView.setText(getString(R.string.asking_permissions)); mSplash.mProgress.setVisibility(View.INVISIBLE); requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, @@ -144,7 +144,7 @@ public class Splash extends Activity sendEmptyMessage(CheckExternalStorage); else { - mErrorMsg = "Permission denied!! Exiting..."; + mErrorMsg = getString(R.string.permission_denied); sendEmptyMessage(InError); break; } @@ -152,7 +152,7 @@ public class Splash extends Activity case Checking: break; case Clearing: - mSplash.mTextView.setText("Clearing cache..."); + mSplash.mTextView.setText(getString(R.string.clearing_cache)); mSplash.mProgress.setVisibility(View.INVISIBLE); break; case Caching: @@ -178,11 +178,11 @@ public class Splash extends Activity } break; case WaitingStorageChecked: - mSplash.mTextView.setText("Waiting for external storage..."); + mSplash.mTextView.setText(getString(R.string.waiting_external)); mSplash.mProgress.setVisibility(View.INVISIBLE); break; case StorageChecked: - mSplash.mTextView.setText("External storage OK..."); + mSplash.mTextView.setText(getString(R.string.external_ok)); mExternalStorageChecked = true; mSplash.stopWatchingExternalStorage(); if (mSplash.mCachingDone) @@ -210,7 +210,7 @@ public class Splash extends Activity break; case StartingXBMC: - mSplash.mTextView.setText("Starting @APP_NAME@..."); + mSplash.mTextView.setText(getString(R.string.starting, "@APP_NAME@")); mSplash.mProgress.setVisibility(View.INVISIBLE); mSplash.startXBMC(); break; @@ -345,13 +345,13 @@ public class Splash extends Activity catch (FileNotFoundException e1) { Log.e(TAG, "Exception: " + e1.getMessage()); - mErrorMsg = "Cannot find package."; + mErrorMsg = getString(R.string.no_find_apk); return -1; } catch (IOException e) { Log.e(TAG, "Exception: " + e.getMessage()); - mErrorMsg = "Cannot read package."; + mErrorMsg = getString(R.string.no_read_apk); File obb = new File(sPackagePath); obb.delete(); return -1; @@ -368,7 +368,7 @@ public class Splash extends Activity switch (mState) { case Caching: - mSplash.mTextView.setText("Preparing for first run. Please wait..."); + mSplash.mTextView.setText(getString(R.string.first_run, "@APP_NAME@")); mSplash.mProgress.setVisibility(View.VISIBLE); mSplash.mProgress.setProgress(value); break; @@ -398,7 +398,7 @@ public class Splash extends Activity builder.setTitle(title); builder.setIcon(android.R.drawable.ic_dialog_alert); builder.setMessage(Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY)); - builder.setPositiveButton("Exit", (dialog, arg1) -> { + builder.setPositiveButton(getString(R.string.exit_button), (dialog, arg1) -> { dialog.dismiss(); act.finish(); }); diff --git a/tools/android/packaging/xbmc/strings.xml.in b/tools/android/packaging/xbmc/strings.xml.in index 141a1a3fe1f37..43eea24dc8736 100644 --- a/tools/android/packaging/xbmc/strings.xml.in +++ b/tools/android/packaging/xbmc/strings.xml.in @@ -5,4 +5,20 @@ @APP_NAME@ Suggestions + + + Continue + Error + Info + Exit + %1$s requires access to your device media and files to function. Please allow this via the following dialogue box or %1$s will exit. + Asking for permissions… + Clearing cache… + Waiting for external storage… + External storage OK… + Starting %1$s… + Preparing for first run. Please wait… + Permission denied!! Exiting… + Cannot find package. + Cannot read package. From 20dc804ee2fb1caf20d513021794c906215cbcc1 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Wed, 6 Mar 2024 08:47:48 +0100 Subject: [PATCH 003/651] Replace deprecated two-letter codes --- .../xbmc/res/{values-id-rid => values-in-rid}/strings.xml | 0 .../xbmc/res/{values-he-ril => values-iw-ril}/strings.xml | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tools/android/packaging/xbmc/res/{values-id-rid => values-in-rid}/strings.xml (100%) rename tools/android/packaging/xbmc/res/{values-he-ril => values-iw-ril}/strings.xml (100%) diff --git a/tools/android/packaging/xbmc/res/values-id-rid/strings.xml b/tools/android/packaging/xbmc/res/values-in-rid/strings.xml similarity index 100% rename from tools/android/packaging/xbmc/res/values-id-rid/strings.xml rename to tools/android/packaging/xbmc/res/values-in-rid/strings.xml diff --git a/tools/android/packaging/xbmc/res/values-he-ril/strings.xml b/tools/android/packaging/xbmc/res/values-iw-ril/strings.xml similarity index 100% rename from tools/android/packaging/xbmc/res/values-he-ril/strings.xml rename to tools/android/packaging/xbmc/res/values-iw-ril/strings.xml From 3573999f4ab810b603db3a328c6a79360e697b04 Mon Sep 17 00:00:00 2001 From: CrystalP Date: Sun, 5 May 2024 09:37:50 -0400 Subject: [PATCH 004/651] [video] Preserve special characters in default names of extras --- xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp b/xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp index b96bd85831a2b..8db09aeaab9ba 100644 --- a/xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp @@ -276,9 +276,6 @@ std::string CGUIDialogVideoManagerExtras::GenerateVideoExtra(const std::string& // remove file extension URIUtils::RemoveExtension(extrasVersion); - // remove special characters - extrasVersion = StringUtils::ReplaceSpecialCharactersWithSpace(extrasVersion); - // trim the string return StringUtils::Trim(extrasVersion); } From 80660eb3f39d57dd95ebf0428883226b61957154 Mon Sep 17 00:00:00 2001 From: CrystalP Date: Sat, 18 May 2024 19:32:48 -0400 Subject: [PATCH 005/651] [video] Allow changing the type of a movie asset between version and extra --- xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp | 11 ++++++----- xbmc/video/dialogs/GUIDialogVideoManagerVersions.cpp | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp b/xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp index b96bd85831a2b..da47f8125a597 100644 --- a/xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp @@ -131,9 +131,9 @@ bool CGUIDialogVideoManagerExtras::AddVideoExtra() if (newAsset.m_idFile != -1 && newAsset.m_assetTypeId != -1) { - // The video already is an asset of the movie - if (newAsset.m_idMedia == dbId && - newAsset.m_mediaType == m_videoAsset->GetVideoInfoTag()->m_type) + // The video already is an extra of the movie + if (newAsset.m_idMedia == dbId && newAsset.m_mediaType == mediaType && + newAsset.m_assetType == VideoAssetType::EXTRA) { unsigned int msgid{}; @@ -153,9 +153,9 @@ bool CGUIDialogVideoManagerExtras::AddVideoExtra() return false; } - // The video is an asset of another movie + // The video is an asset of another movie or different asset type of same movie - // The video is a version, ask for confirmation + // The video is a version, ask for confirmation of the asset type change if (newAsset.m_assetType == VideoAssetType::VERSION && !CGUIDialogYesNo::ShowAndGetInput(CVariant{40015}, StringUtils::Format(g_localizeStrings.Get(40036)))) @@ -171,6 +171,7 @@ bool CGUIDialogVideoManagerExtras::AddVideoExtra() else return false; + if (newAsset.m_idMedia != dbId && newAsset.m_mediaType == mediaType) { unsigned int msgid{}; diff --git a/xbmc/video/dialogs/GUIDialogVideoManagerVersions.cpp b/xbmc/video/dialogs/GUIDialogVideoManagerVersions.cpp index 32ad3928519e9..49fad7c9ffecc 100644 --- a/xbmc/video/dialogs/GUIDialogVideoManagerVersions.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoManagerVersions.cpp @@ -498,9 +498,9 @@ bool CGUIDialogVideoManagerVersions::AddVideoVersionFilePicker() // @todo look only for a version identified by idFile instead of retrieving all versions if (newAsset.m_idFile != -1 && newAsset.m_assetTypeId != -1) { - // The video already is an asset of the movie - if (newAsset.m_idMedia == dbId && - newAsset.m_mediaType == m_videoAsset->GetVideoInfoTag()->m_type) + // The video already is a version of the movie + if (newAsset.m_idMedia == dbId && newAsset.m_mediaType == mediaType && + newAsset.m_assetType == VideoAssetType::VERSION) { unsigned int msgid{}; @@ -520,9 +520,9 @@ bool CGUIDialogVideoManagerVersions::AddVideoVersionFilePicker() return false; } - // The video is an asset of another movie + // The video is an asset of another movie or different asset type of same movie - // The video is an extra, ask for confirmation + // The video is an extra, ask for confirmation of the asset type change if (newAsset.m_assetType == VideoAssetType::EXTRA && !CGUIDialogYesNo::ShowAndGetInput(CVariant{40014}, StringUtils::Format(g_localizeStrings.Get(40035)))) @@ -538,6 +538,7 @@ bool CGUIDialogVideoManagerVersions::AddVideoVersionFilePicker() else return false; + if (newAsset.m_idMedia != dbId && newAsset.m_mediaType == mediaType) { unsigned int msgid{}; From 36c33dbc4473c03bfecfa21e7074312fe2dc0ae1 Mon Sep 17 00:00:00 2001 From: smp79 Date: Wed, 22 May 2024 19:24:02 +0300 Subject: [PATCH 006/651] VideoPlayer: add setting for using VAAPI AVC --- addons/resource.language.en_gb/resources/strings.po | 11 +++++++++++ system/settings/linux.xml | 12 ++++++++++++ xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp | 6 ++++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 95cc956764204..caa4bc433c0b7 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -7536,6 +7536,17 @@ msgctxt "#13469" msgid "Enable this option to use hardware acceleration for the AV1 codec. If disabled the CPU will be used instead." msgstr "" +#: system/settings/settings.xml +msgctxt "#13470" +msgid "Use AVC VAAPI" +msgstr "" + +#. Description of setting with label #13470 "Use AVC VAAPI" +#: system/settings/settings.xml +msgctxt "#13471" +msgid "Enable this option to use hardware acceleration for the AVC codec. If disabled the CPU will be used instead." +msgstr "" + #empty strings from id 13468 to 13504 #: system/settings/settings.xml diff --git a/system/settings/linux.xml b/system/settings/linux.xml index 531974f3f4af9..c99bd895275e7 100644 --- a/system/settings/linux.xml +++ b/system/settings/linux.xml @@ -149,6 +149,18 @@ true + + HAVE_LIBVA + false + + + true + + + 3 + true + + HAVE_LIBVA false diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp index fb7606e0d03c6..6ace85dc9244d 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp @@ -57,6 +57,7 @@ using namespace std::chrono_literals; constexpr auto SETTING_VIDEOPLAYER_USEVAAPI = "videoplayer.usevaapi"; constexpr auto SETTING_VIDEOPLAYER_USEVAAPIAV1 = "videoplayer.usevaapiav1"; +constexpr auto SETTING_VIDEOPLAYER_USEVAAPIAVC = "videoplayer.usevaapiavc"; constexpr auto SETTING_VIDEOPLAYER_USEVAAPIHEVC = "videoplayer.usevaapihevc"; constexpr auto SETTING_VIDEOPLAYER_USEVAAPIMPEG2 = "videoplayer.usevaapimpeg2"; constexpr auto SETTING_VIDEOPLAYER_USEVAAPIMPEG4 = "videoplayer.usevaapimpeg4"; @@ -542,6 +543,7 @@ bool CDecoder::Open(AVCodecContext* avctx, AVCodecContext* mainctx, const enum A {AV_CODEC_ID_VP9, SETTING_VIDEOPLAYER_USEVAAPIVP9}, {AV_CODEC_ID_HEVC, SETTING_VIDEOPLAYER_USEVAAPIHEVC}, {AV_CODEC_ID_AV1, SETTING_VIDEOPLAYER_USEVAAPIAV1}, + {AV_CODEC_ID_H264, SETTING_VIDEOPLAYER_USEVAAPIAVC}, }; auto entry = settings_map.find(avctx->codec_id); @@ -1277,12 +1279,12 @@ void CDecoder::Register(IVaapiWinSystem *winSystem, bool deepColor) if (!settings) return; - constexpr std::array vaapiSettings = { + constexpr std::array vaapiSettings = { SETTING_VIDEOPLAYER_USEVAAPI, SETTING_VIDEOPLAYER_USEVAAPIMPEG4, SETTING_VIDEOPLAYER_USEVAAPIVC1, SETTING_VIDEOPLAYER_USEVAAPIMPEG2, SETTING_VIDEOPLAYER_USEVAAPIVP8, SETTING_VIDEOPLAYER_USEVAAPIVP9, SETTING_VIDEOPLAYER_USEVAAPIHEVC, SETTING_VIDEOPLAYER_PREFERVAAPIRENDER, - SETTING_VIDEOPLAYER_USEVAAPIAV1}; + SETTING_VIDEOPLAYER_USEVAAPIAV1, SETTING_VIDEOPLAYER_USEVAAPIAVC}; for (const auto vaapiSetting : vaapiSettings) { From d7a5de5ab9c198a1b2ac7f4753142f6ed4020a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Sat, 1 Jun 2024 01:18:02 +0200 Subject: [PATCH 007/651] CFile: Handle std::bad_alloc in LoadFile This won't help on most Linux systems due to memory overcommitment. But could in theory help on Windows and also Androif if #25288 is to be trusted? Fixes #25288. --- xbmc/filesystem/File.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xbmc/filesystem/File.cpp b/xbmc/filesystem/File.cpp index cfea9ac4a1b5c..317816d792d13 100644 --- a/xbmc/filesystem/File.cpp +++ b/xbmc/filesystem/File.cpp @@ -994,6 +994,7 @@ ssize_t CFile::LoadFile(const std::string& filename, std::vector& outpu } ssize_t CFile::LoadFile(const CURL& file, std::vector& outputBuffer) +try { static const size_t max_file_size = 0x7FFFFFFF; static const size_t min_chunk_size = 64 * 1024U; @@ -1058,6 +1059,12 @@ ssize_t CFile::LoadFile(const CURL& file, std::vector& outputBuffer) return total_read; } +catch (const std::bad_alloc&) +{ + outputBuffer.clear(); + CLog::LogF(LOGERROR, "Failed to load {}: out of memory", file.GetFileName()); + return -1; +} double CFile::GetDownloadSpeed() { From 187f9740bc32a0d62410cc6c5d82b98027b63db8 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 5 Jun 2024 08:12:22 +1000 Subject: [PATCH 008/651] [tools/depends] configure.ac add explicit sdk for apple native_sdk_path Explicitly set native_sdk_path to search for macosx sdk using xcrun. There is a way somehow, where without explicitly requsting the macosx sdk, it retrieves paths from elsewhere (ie command line tools instead of Xcode) ~ % xcode-select -p /Applications/Xcode.app/Contents/Developer ~ % xcrun --show-sdk-path /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk ~ % xcrun --sdk macosx --show-sdk-path /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.5.sdk ~ % sudo xcode-select -s /Library/Developer/CommandLineTools/ ~ % xcode-select -p /Library/Developer/CommandLineTools ~ % xcrun --show-sdk-path /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk ~ % xcrun --sdk macosx --show-sdk-path /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk No harm, in setting explicitly, as its only used for build host info, so doesnt effect target --- tools/depends/configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac index 26bd6e8a5ec8d..fbe4f5e44009c 100644 --- a/tools/depends/configure.ac +++ b/tools/depends/configure.ac @@ -190,7 +190,7 @@ case $build in # acquire build platform (native) sdk sysroot. build_platform=macosx - native_sdk_path=[`$use_xcrun --show-sdk-path`] + native_sdk_path=[`$use_xcrun --sdk macosx --show-sdk-path`] host_sysroot="$native_sdk_path" host_includes="${host_includes} -isysroot $native_sdk_path" From 464d3908a7d15f82e9094617aad9c015b371a989 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 5 Jun 2024 08:18:58 +1000 Subject: [PATCH 009/651] [tools/depends][native] cmake remove setting SDKROOT Theres no need to set SDKROOT as we already pass along -isystem flags. cmake uses SDKROOT to compute CMAKE_OSX_SYSROOT which we already set in the toolchain file --- tools/depends/native/cmake/Makefile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tools/depends/native/cmake/Makefile b/tools/depends/native/cmake/Makefile index 7d8faa14e2089..6754bfc9f4e97 100644 --- a/tools/depends/native/cmake/Makefile +++ b/tools/depends/native/cmake/Makefile @@ -7,10 +7,6 @@ DEPS = ../../Makefile.include Makefile CMAKE-VERSION ../../download-files.includ SETENV=CC="$(CC_FOR_BUILD)" CXX="$(CXX_FOR_BUILD)" LD=$(LD_FOR_BUILD) CFLAGS="$(NATIVE_CFLAGS)" \ CXXFLAGS="$(NATIVE_CXXFLAGS)" LDFLAGS="$(NATIVE_LDFLAGS)" -ifeq ($(NATIVE_OS), osx) - SETENV+=SDKROOT=$(shell xcrun --show-sdk-path) -endif - CONFIGURE=./bootstrap --prefix=$(NATIVEPREFIX) --system-curl ifeq ($(USE_CCACHE), yes) CONFIGURE+=--enable-ccache From 589496811adfb6e5675779808b91b4f29216c92e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 5 Jun 2024 09:01:34 +1000 Subject: [PATCH 010/651] [tools/depends] configure.ac some comments and cleanup --- tools/depends/configure.ac | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac index fbe4f5e44009c..9414550169f43 100644 --- a/tools/depends/configure.ac +++ b/tools/depends/configure.ac @@ -194,6 +194,8 @@ case $build in host_sysroot="$native_sdk_path" host_includes="${host_includes} -isysroot $native_sdk_path" + # We only set CC_FOR_BUILD as we require use_build_toolchain to be populated + # All other build apps are detected by AC_PATH_PROG further down CC_FOR_BUILD=[`$use_xcrun --find clang`] use_build_toolchain=[`$CC_FOR_BUILD --version | grep InstalledDir | awk '{ print $2}'`] @@ -263,8 +265,11 @@ case $host in esac ;; *darwin*) + # Again we set CC to find toolchain path for use in AC_PATH_TOOL macros further down CC=[`$use_xcrun --find clang`] - platform_cxx=clang++ + # May as well just set CXX direct as platform_cxx default is g++. Setting the variable + # before AC_PATH_TOOL causes that macro to do nothing and just use the set variable + CXX=[`$use_xcrun --find clang++`] use_toolchain=[`$CC --version | grep InstalledDir | awk '{ print $2}'`] esac From 9b27b9b4bd54b34cb81cc72dda6d943e480ed063 Mon Sep 17 00:00:00 2001 From: quietvoid <39477805+quietvoid@users.noreply.github.com> Date: Thu, 6 Jun 2024 08:22:21 -0400 Subject: [PATCH 011/651] [Android] Allow setting the VideoLayout view to transparent color Sony TVs have a long standing firmware bug. Allow a user to configure the video layout background color to workaround this. Fixes #25166 Usage: ``` true ``` --- xbmc/platform/android/PlatformAndroid.cpp | 17 +++++++++++ xbmc/platform/android/PlatformAndroid.h | 1 + xbmc/platform/android/activity/XBMCApp.cpp | 35 ++++++++++++++++++++++ xbmc/platform/android/activity/XBMCApp.h | 2 ++ xbmc/settings/AdvancedSettings.cpp | 1 + xbmc/settings/AdvancedSettings.h | 1 + 6 files changed, 57 insertions(+) diff --git a/xbmc/platform/android/PlatformAndroid.cpp b/xbmc/platform/android/PlatformAndroid.cpp index 9c3be909efc1e..ba9208f6f5f46 100644 --- a/xbmc/platform/android/PlatformAndroid.cpp +++ b/xbmc/platform/android/PlatformAndroid.cpp @@ -8,7 +8,10 @@ #include "PlatformAndroid.h" +#include "ServiceBroker.h" #include "filesystem/SpecialProtocol.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" #include "utils/log.h" #include "windowing/android/WinSystemAndroidGLESContext.h" @@ -40,6 +43,20 @@ bool CPlatformAndroid::InitStageOne() return true; } +bool CPlatformAndroid::InitStageThree() +{ + if (!CPlatformPosix::InitStageThree()) + return false; + + if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiVideoLayoutTransparent) + { + CLog::Log(LOGINFO, "XBMCApp: VideoLayout view was set to transparent."); + CXBMCApp::Get().SetVideoLayoutBackgroundColor(0); + } + + return true; +} + void CPlatformAndroid::PlatformSyslog() { CLog::Log( diff --git a/xbmc/platform/android/PlatformAndroid.h b/xbmc/platform/android/PlatformAndroid.h index ef004cf7bab0e..c5346926ab7a3 100644 --- a/xbmc/platform/android/PlatformAndroid.h +++ b/xbmc/platform/android/PlatformAndroid.h @@ -20,5 +20,6 @@ class CPlatformAndroid : public CPlatformPosix ~CPlatformAndroid() override = default; bool InitStageOne() override; + bool InitStageThree() override; void PlatformSyslog() override; }; diff --git a/xbmc/platform/android/activity/XBMCApp.cpp b/xbmc/platform/android/activity/XBMCApp.cpp index 437c7371d9dc9..5f4bb7865a929 100644 --- a/xbmc/platform/android/activity/XBMCApp.cpp +++ b/xbmc/platform/android/activity/XBMCApp.cpp @@ -91,6 +91,7 @@ #include #include #include +#include #include #include #include @@ -1669,6 +1670,40 @@ std::shared_ptr CXBMCApp::GetNativeWindow(int timeout) const return m_window; } +// The map must contain keys "id" and "color", both are integers +void CXBMCApp::SetViewBackgroundColorCallback(void* mapVariant) +{ + CVariant* mapV = static_cast(mapVariant); + int viewId = (*mapV)["id"].asInteger(); + int color = (*mapV)["color"].asInteger(); + + delete mapV; + + CJNIView view = findViewById(viewId); + if (view) + { + view.setBackgroundColor(color); + } +} + +void CXBMCApp::SetVideoLayoutBackgroundColor(const int color) +{ + CJNIResources resources = CJNIContext::getResources(); + if (resources) + { + int id = resources.getIdentifier("VideoLayout", "id", CJNIContext::getPackageName()); + if (id > 0) + { + // this object is deallocated in the callback + CVariant* msg = new CVariant(CVariant::VariantTypeObject); + (*msg)["id"] = id; + (*msg)["color"] = color; + + runNativeOnUiThread(SetViewBackgroundColorCallback, msg); + } + } +} + void CXBMCApp::RegisterInputDeviceCallbacks(IInputDeviceCallbacks* handler) { if (handler != nullptr) diff --git a/xbmc/platform/android/activity/XBMCApp.h b/xbmc/platform/android/activity/XBMCApp.h index 2e2d8670a0797..ee62fc3f039e5 100644 --- a/xbmc/platform/android/activity/XBMCApp.h +++ b/xbmc/platform/android/activity/XBMCApp.h @@ -181,6 +181,7 @@ class CXBMCApp : public IActivityHandler, void SetDisplayMode(int mode, float rate); int GetDPI() const; + void SetVideoLayoutBackgroundColor(const int color); CRect MapRenderToDroid(const CRect& srcRect); @@ -243,6 +244,7 @@ class CXBMCApp : public IActivityHandler, void SetupEnv(); static void SetDisplayModeCallback(void* modeVariant); static void KeepScreenOnCallback(void* onVariant); + static void SetViewBackgroundColorCallback(void* mapVariant); static void RegisterDisplayListenerCallback(void*); void UnregisterDisplayListener(); diff --git a/xbmc/settings/AdvancedSettings.cpp b/xbmc/settings/AdvancedSettings.cpp index 0d89592cb5cb0..c4e8d9fc58b8f 100644 --- a/xbmc/settings/AdvancedSettings.cpp +++ b/xbmc/settings/AdvancedSettings.cpp @@ -1227,6 +1227,7 @@ void CAdvancedSettings::ParseSettingsFile(const std::string &file) XMLUtils::GetBoolean(pElement, "fronttobackrendering", m_guiFrontToBackRendering); XMLUtils::GetBoolean(pElement, "geometryclear", m_guiGeometryClear); XMLUtils::GetBoolean(pElement, "asynctextureupload", m_guiAsyncTextureUpload); + XMLUtils::GetBoolean(pElement, "transparentvideolayout", m_guiVideoLayoutTransparent); } std::string seekSteps; diff --git a/xbmc/settings/AdvancedSettings.h b/xbmc/settings/AdvancedSettings.h index 04e0c462cce48..0f4c5a7871be4 100644 --- a/xbmc/settings/AdvancedSettings.h +++ b/xbmc/settings/AdvancedSettings.h @@ -337,6 +337,7 @@ class CAdvancedSettings : public ISettingCallback, public ISettingsHandler bool m_guiFrontToBackRendering{false}; bool m_guiGeometryClear{true}; bool m_guiAsyncTextureUpload{false}; + bool m_guiVideoLayoutTransparent{false}; unsigned int m_addonPackageFolderSize; From 9a1ddc6ec7536ce74e83d8941893be280049193a Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Fri, 7 Jun 2024 22:59:35 +0200 Subject: [PATCH 012/651] fixed: infinite loop parsing an invalid moving speed config if type was missing we logged the fact infinitely as we did not advance to the sibling element --- xbmc/guilib/GUIControlFactory.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/guilib/GUIControlFactory.cpp b/xbmc/guilib/GUIControlFactory.cpp index 001537fb728e4..fe8cc4e45b7bc 100644 --- a/xbmc/guilib/GUIControlFactory.cpp +++ b/xbmc/guilib/GUIControlFactory.cpp @@ -764,6 +764,7 @@ bool CGUIControlFactory::GetMovingSpeedConfig(const TiXmlNode* pRootNode, if (!eventType) { CLog::LogF(LOGERROR, "Failed to parse XML \"eventconfig\" tag missing \"type\" attribute"); + configElement = configElement->NextSiblingElement("eventconfig"); continue; } From 54d10e23abb5b8c7ede2f7cccd2ed63302a6bcee Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Mon, 10 Jun 2024 15:03:55 +0200 Subject: [PATCH 013/651] replace while-loop with for loop to avoid repeated advance to sibling element statements --- xbmc/guilib/GUIControlFactory.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/xbmc/guilib/GUIControlFactory.cpp b/xbmc/guilib/GUIControlFactory.cpp index fe8cc4e45b7bc..798bcf056154a 100644 --- a/xbmc/guilib/GUIControlFactory.cpp +++ b/xbmc/guilib/GUIControlFactory.cpp @@ -757,14 +757,13 @@ bool CGUIControlFactory::GetMovingSpeedConfig(const TiXmlNode* pRootNode, StringUtils::ToUint32(XMLUtils::GetAttribute(msNode, "resettimeout"))}; float globalDelta{StringUtils::ToFloat(XMLUtils::GetAttribute(msNode, "delta"))}; - const TiXmlElement* configElement{msNode->FirstChildElement("eventconfig")}; - while (configElement) + for (const TiXmlElement* configElement{msNode->FirstChildElement("eventconfig")}; configElement; + configElement = configElement->NextSiblingElement("eventconfig")) { const char* eventType = configElement->Attribute("type"); if (!eventType) { CLog::LogF(LOGERROR, "Failed to parse XML \"eventconfig\" tag missing \"type\" attribute"); - configElement = configElement->NextSiblingElement("eventconfig"); continue; } @@ -783,8 +782,6 @@ bool CGUIControlFactory::GetMovingSpeedConfig(const TiXmlNode* pRootNode, UTILS::MOVING_SPEED::EventCfg eventCfg{acceleration, maxVelocity, resetTimeout, delta}; movingSpeedCfg.emplace(UTILS::MOVING_SPEED::ParseEventType(eventType), eventCfg); - - configElement = configElement->NextSiblingElement("eventconfig"); } return true; } From fde42f9aeafb2f3e2ff2a89fb6f6fb88f06c4452 Mon Sep 17 00:00:00 2001 From: Frank Howie Date: Tue, 11 Jun 2024 11:49:49 +0200 Subject: [PATCH 014/651] [build] fix missing include --- xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp index 142974591b1a7..dd5aae6fe68e3 100644 --- a/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp +++ b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include using namespace WSDiscovery; From a401c31390d8f008807502ccc4021d05f7967609 Mon Sep 17 00:00:00 2001 From: CastagnaIT Date: Wed, 12 Jun 2024 09:53:56 +0200 Subject: [PATCH 015/651] [CharArrayParser] Skip malformed EOL Some subtitles may have a malformed Macintosh line break as follow: CR+CR+LF a mixed line breaks between macintosh + windows format on other media players this is somewhat ignored to allow to try read the file in a better way --- xbmc/utils/CharArrayParser.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/xbmc/utils/CharArrayParser.cpp b/xbmc/utils/CharArrayParser.cpp index 5aeec2040babe..ec5af70a9c0e6 100644 --- a/xbmc/utils/CharArrayParser.cpp +++ b/xbmc/utils/CharArrayParser.cpp @@ -156,11 +156,19 @@ bool CCharArrayParser::ReadNextLine(std::string& line) line.assign(m_data + m_position, lineLimit - m_position); m_position = lineLimit; + // Skip EOL chars if (m_data[m_position] == '\r') { m_position++; + + if (m_data[m_position] == '\n') + m_position++; + // Malformed EOL as \r\r\n + else if (m_position + 1 <= m_limit && m_data[m_position] == '\r' && + m_data[m_position + 1] == '\n') + m_position += 2; } - if (m_data[m_position] == '\n') + else if (m_data[m_position] == '\n') { m_position++; } From c00ac7605edb7adee4774a04db5e0c654d556a10 Mon Sep 17 00:00:00 2001 From: Rudi Heitbaum Date: Wed, 12 Jun 2024 21:45:23 +0000 Subject: [PATCH 016/651] PeripheralBusUSBLibUdev: fix assert issue with systemd udev do not call udev_device_get_parent(udev_device_get_parent(dev)) directly as a null return on the parent will cause an assert in udev_device_get_parent --- xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.cpp b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.cpp index aeb5e71d9fe30..68bce9eba3d76 100644 --- a/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.cpp +++ b/xbmc/platform/linux/peripherals/PeripheralBusUSBLibUdev.cpp @@ -108,7 +108,9 @@ bool CPeripheralBusUSB::PerformDeviceScan(PeripheralScanResults &results) if (bContinue) { - dev = udev_device_get_parent(udev_device_get_parent(parent)); + dev = udev_device_get_parent(parent); + if (dev) + dev = udev_device_get_parent(dev); if (!dev || !udev_device_get_sysattr_value(dev,"idVendor") || !udev_device_get_sysattr_value(dev, "idProduct")) bContinue = false; } From 627e8b3da4838feadf3b2f6be26e82639a817c06 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 13 Jun 2024 11:00:13 +0200 Subject: [PATCH 017/651] [cmake] FindEGL add IMPORTED_NO_SONAME property --- cmake/modules/FindEGL.cmake | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindEGL.cmake b/cmake/modules/FindEGL.cmake index 05d74c27e11ff..e0bad2feac8f2 100644 --- a/cmake/modules/FindEGL.cmake +++ b/cmake/modules/FindEGL.cmake @@ -35,11 +35,17 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) check_include_files("EGL/egl.h;EGL/eglext.h;EGL/eglext_angle.h" HAVE_EGLEXTANGLE) unset(CMAKE_REQUIRED_INCLUDES) - add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + if(${EGL_LIBRARY} MATCHES ".+\.so$") + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} SHARED IMPORTED) + else() + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + endif() + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES IMPORTED_LOCATION "${EGL_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${EGL_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_EGL) + INTERFACE_COMPILE_DEFINITIONS HAS_EGL + IMPORTED_NO_SONAME TRUE) if(HAVE_EGLEXTANGLE) set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY From 597196c55f285af27f697fce8c899f529b2da461 Mon Sep 17 00:00:00 2001 From: Frank Howie Date: Thu, 13 Jun 2024 15:46:18 +0200 Subject: [PATCH 018/651] [cmake] use dmesg and pipe to check SSE capabilities enables SSE check in a jailed FreeBSD builder --- cmake/modules/FindSSE.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/modules/FindSSE.cmake b/cmake/modules/FindSSE.cmake index f367e2dac8433..82a33dd6deda7 100644 --- a/cmake/modules/FindSSE.cmake +++ b/cmake/modules/FindSSE.cmake @@ -42,7 +42,8 @@ if(CMAKE_SYSTEM_NAME MATCHES "Linux") endif() elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") if(CPU MATCHES "amd64" OR CPU MATCHES "i.86") - execute_process(COMMAND grep Features /var/run/dmesg.boot OUTPUT_VARIABLE CPUINFO) + execute_process(COMMAND dmesg + COMMAND grep Features OUTPUT_VARIABLE CPUINFO) string(REGEX REPLACE "^.*(SSE).*$" "\\1" _SSE_THERE "${CPUINFO}") string(COMPARE EQUAL "SSE" "${_SSE_THERE}" _SSE_TRUE) From 7df38caad9a08847b7de51d4e3b800e258b5ba18 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 26 Mar 2024 09:17:39 +0200 Subject: [PATCH 019/651] [buildsteps] Bump ios/tvos to Xcode 15.2 and SDK 17.2 --- tools/buildsteps/defaultenv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/buildsteps/defaultenv b/tools/buildsteps/defaultenv index c14478aca98be..a4201c5b8adf3 100644 --- a/tools/buildsteps/defaultenv +++ b/tools/buildsteps/defaultenv @@ -28,17 +28,17 @@ JENKINS_BUILD_STRING="${JENKINS_BUILD_TIMESTAMP}-${JENKINS_BUILD_COMMIT}-${JENKI #$XBMC_PLATFORM_DIR matches the platform subdirs! case $XBMC_PLATFORM_DIR in ios) - DEFAULT_SDK_VERSION=16.2 + DEFAULT_SDK_VERSION=17.2 DEFAULT_XBMC_DEPENDS_ROOT=$WORKSPACE/tools/depends/xbmc-depends DEFAULT_CONFIGURATION="Debug" - DEFAULT_XCODE_APP="Xcode_14.2.app" + DEFAULT_XCODE_APP="Xcode_15.2.app" ;; tvos) - DEFAULT_SDK_VERSION=16.1 + DEFAULT_SDK_VERSION=17.2 DEFAULT_XBMC_DEPENDS_ROOT=$WORKSPACE/tools/depends/xbmc-depends DEFAULT_CONFIGURATION="Debug" - DEFAULT_XCODE_APP="Xcode_14.2.app" + DEFAULT_XCODE_APP="Xcode_15.2.app" ;; osx64) From f600bd1aab2ae6e1b078240d5bc163e26a24d5b8 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 26 Mar 2024 14:53:19 +0200 Subject: [PATCH 020/651] [tools/depends][toolchain] propagate minver for ios/tvos to toolchain from configure --- tools/depends/configure.ac | 1 + tools/depends/target/Toolchain.cmake.in | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac index 9414550169f43..b3469419ea259 100644 --- a/tools/depends/configure.ac +++ b/tools/depends/configure.ac @@ -699,6 +699,7 @@ AC_SUBST(use_toolchain) AC_SUBST(use_build_toolchain) AC_SUBST(use_tarballs) AC_SUBST(target_platform) +AC_SUBST(target_minver) AC_SUBST(use_firmware) AC_SUBST(cross_compiling) AC_SUBST(platform_asflags) diff --git a/tools/depends/target/Toolchain.cmake.in b/tools/depends/target/Toolchain.cmake.in index ffed3ce3f169a..21ae20ef3c53a 100644 --- a/tools/depends/target/Toolchain.cmake.in +++ b/tools/depends/target/Toolchain.cmake.in @@ -48,10 +48,10 @@ if(CORE_SYSTEM_NAME STREQUAL darwin_embedded) set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO") if(CORE_PLATFORM_NAME STREQUAL tvos) set(CMAKE_XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "3") - set(CMAKE_XCODE_ATTRIBUTE_TVOS_DEPLOYMENT_TARGET 12.0) + set(CMAKE_XCODE_ATTRIBUTE_TVOS_DEPLOYMENT_TARGET @target_minver@) else() set(CMAKE_XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2") - set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 11.0) + set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET @target_minver@) endif() endif() endif() From adc313ddc13e46e3eabbd963f8850c99d79e1d4e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 26 Mar 2024 15:57:01 +0200 Subject: [PATCH 021/651] [buildsteps] Bump macos to Xcode 15.2 and SDK 14.2 --- tools/buildsteps/defaultenv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/buildsteps/defaultenv b/tools/buildsteps/defaultenv index a4201c5b8adf3..8bc7bb04b7e46 100644 --- a/tools/buildsteps/defaultenv +++ b/tools/buildsteps/defaultenv @@ -42,17 +42,17 @@ case $XBMC_PLATFORM_DIR in ;; osx64) - DEFAULT_SDK_VERSION=13.1 + DEFAULT_SDK_VERSION=14.2 DEFAULT_XBMC_DEPENDS_ROOT=$WORKSPACE/tools/depends/xbmc-depends DEFAULT_CONFIGURATION="Debug" - DEFAULT_XCODE_APP="Xcode_14.2.app" + DEFAULT_XCODE_APP="Xcode_15.2.app" ;; osx-arm64) - DEFAULT_SDK_VERSION=13.1 + DEFAULT_SDK_VERSION=14.2 DEFAULT_XBMC_DEPENDS_ROOT=$WORKSPACE/tools/depends/xbmc-depends DEFAULT_CONFIGURATION="Debug" - DEFAULT_XCODE_APP="Xcode_14.2.app" + DEFAULT_XCODE_APP="Xcode_15.2.app" ;; android) From 6094e540f79b533506195ea5affb4f2f587408f3 Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Mon, 25 Mar 2024 20:08:37 +0100 Subject: [PATCH 022/651] [windows] Add /utf-8 flag for MSVC This fixes C4566 warnings on Windows/MSVC and also corrects unit test failures that would start showing up with C++20. While the previous behavior might have worked sometimes by accident, this now sets proper encodings for source and execution character sets. Related reading: * https://pspdfkit.com/blog/2021/string-literals-character-encodings-and-multiplatform-cpp/ * https://devblogs.microsoft.com/cppblog/new-options-for-managing-character-sets-in-the-microsoft-cc-compiler/ * https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-1-c4566?view=msvc-170 --- cmake/scripts/windows/ArchSetup.cmake | 2 ++ cmake/scripts/windows/CFlagOverrides.cmake | 2 +- cmake/scripts/windows/CXXFlagOverrides.cmake | 2 +- cmake/scripts/windowsstore/ArchSetup.cmake | 3 +++ cmake/scripts/windowsstore/CFlagOverrides.cmake | 2 +- cmake/scripts/windowsstore/CXXFlagOverrides.cmake | 2 +- 6 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cmake/scripts/windows/ArchSetup.cmake b/cmake/scripts/windows/ArchSetup.cmake index 1744ae30b02e5..4b00257f530ed 100644 --- a/cmake/scripts/windows/ArchSetup.cmake +++ b/cmake/scripts/windows/ArchSetup.cmake @@ -52,6 +52,8 @@ endif() # -------- Compiler options --------- +# Allow to use UTF-8 strings in the source code, disable MSVC charset conversion +add_options(CXX ALL_BUILDS "/utf-8") add_options(CXX ALL_BUILDS "/wd\"4996\"") set(ARCH_DEFINES -D_WINDOWS -DTARGET_WINDOWS -DTARGET_WINDOWS_DESKTOP -D__SSE__ -D__SSE2__) set(SYSTEM_DEFINES -DWIN32_LEAN_AND_MEAN -DNOMINMAX -DHAS_DX -D__STDC_CONSTANT_MACROS diff --git a/cmake/scripts/windows/CFlagOverrides.cmake b/cmake/scripts/windows/CFlagOverrides.cmake index 64dc780ff76b9..0c67794e06cfc 100644 --- a/cmake/scripts/windows/CFlagOverrides.cmake +++ b/cmake/scripts/windows/CFlagOverrides.cmake @@ -4,7 +4,7 @@ if(MSVC) else() set(MP_FLAG "/MP") endif() - set(CMAKE_C_FLAGS "/D_UNICODE /DUNICODE /DRPC_USE_NATIVE_WCHAR ${MP_FLAG} /DWIN32 /D_WINDOWS /W3 /Zi") + set(CMAKE_C_FLAGS "/D_UNICODE /DUNICODE /utf-8 /DRPC_USE_NATIVE_WCHAR ${MP_FLAG} /DWIN32 /D_WINDOWS /W3 /Zi") set(CMAKE_C_FLAGS_DEBUG "/D_DEBUG /MDd /Ob0 /Od /RTC1 /D_ITERATOR_DEBUG_LEVEL=0") set(CMAKE_C_FLAGS_RELEASE "/MD /Ox /Ob2 /Oi /Ot /Oy /GL /DNDEBUG") endif() diff --git a/cmake/scripts/windows/CXXFlagOverrides.cmake b/cmake/scripts/windows/CXXFlagOverrides.cmake index 67ef57602b406..3e9e7a1f2fbf3 100644 --- a/cmake/scripts/windows/CXXFlagOverrides.cmake +++ b/cmake/scripts/windows/CXXFlagOverrides.cmake @@ -4,7 +4,7 @@ if(MSVC) else() set(MP_FLAG "/MP") endif() - set(CMAKE_CXX_FLAGS "/D_UNICODE /DUNICODE /DRPC_USE_NATIVE_WCHAR ${MP_FLAG} /DWIN32 /D_WINDOWS /W3 /GR /Zi /EHsc") + set(CMAKE_CXX_FLAGS "/D_UNICODE /DUNICODE /utf-8 /DRPC_USE_NATIVE_WCHAR ${MP_FLAG} /DWIN32 /D_WINDOWS /W3 /GR /Zi /EHsc") set(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Ob0 /Od /RTC1 /D_ITERATOR_DEBUG_LEVEL=0") set(CMAKE_CXX_FLAGS_RELEASE "/MD /Ox /Ob2 /Oi /Ot /Oy /GL /DNDEBUG") endif() diff --git a/cmake/scripts/windowsstore/ArchSetup.cmake b/cmake/scripts/windowsstore/ArchSetup.cmake index ff18e4a6d4ec4..10422968b43c8 100644 --- a/cmake/scripts/windowsstore/ArchSetup.cmake +++ b/cmake/scripts/windowsstore/ArchSetup.cmake @@ -68,6 +68,9 @@ endif() # -------- Compiler options --------- +# Allow to use UTF-8 strings in the source code, disable MSVC charset conversion +add_options(CXX ALL_BUILDS "/utf-8") + add_options(CXX ALL_BUILDS "/wd\"4996\"") add_options(CXX ALL_BUILDS "/wd\"4146\"") add_options(CXX ALL_BUILDS "/wd\"4251\"") diff --git a/cmake/scripts/windowsstore/CFlagOverrides.cmake b/cmake/scripts/windowsstore/CFlagOverrides.cmake index ab2f59cc23642..f3b1703259268 100644 --- a/cmake/scripts/windowsstore/CFlagOverrides.cmake +++ b/cmake/scripts/windowsstore/CFlagOverrides.cmake @@ -4,7 +4,7 @@ if(DEFINED ENV{MAXTHREADS}) else() set(MP_FLAG "/MP") endif() -string(APPEND CMAKE_C_FLAGS_INIT " /D_UNICODE /DUNICODE ${MP_FLAG} /DWIN32 /D_WINDOWS /W3 /Zi /DTARGET_WINDOWS") +string(APPEND CMAKE_C_FLAGS_INIT " /D_UNICODE /DUNICODE /utf-8 ${MP_FLAG} /DWIN32 /D_WINDOWS /W3 /Zi /DTARGET_WINDOWS") string(APPEND CMAKE_C_FLAGS_INIT " /DWINAPI_FAMILY=2 /DTARGET_WINDOWS_STORE /D_WINSOCK_DEPRECATED_NO_WARNINGS /D_CRT_NONSTDC_NO_DEPRECATE") string(APPEND CMAKE_C_FLAGS_DEBUG_INIT " /D_DEBUG /MDd /Ob0 /Od /RTC1 /D_ITERATOR_DEBUG_LEVEL=0") string(APPEND CMAKE_C_FLAGS_RELEASE_INIT " /MD /Ox /Ob2 /Oi /Ot /Oy /GL /DNDEBUG") diff --git a/cmake/scripts/windowsstore/CXXFlagOverrides.cmake b/cmake/scripts/windowsstore/CXXFlagOverrides.cmake index 4ae3ac3cb0df1..d254891d16660 100644 --- a/cmake/scripts/windowsstore/CXXFlagOverrides.cmake +++ b/cmake/scripts/windowsstore/CXXFlagOverrides.cmake @@ -4,7 +4,7 @@ if(DEFINED ENV{MAXTHREADS}) else() set(MP_FLAG "/MP") endif() -string(APPEND CMAKE_CXX_FLAGS_INIT " /D_UNICODE /DUNICODE ${MP_FLAG} /DWIN32 /D_WINDOWS /W3 /GR /Zi /EHsc /DTARGET_WINDOWS") +string(APPEND CMAKE_CXX_FLAGS_INIT " /D_UNICODE /DUNICODE /utf-8 ${MP_FLAG} /DWIN32 /D_WINDOWS /W3 /GR /Zi /EHsc /DTARGET_WINDOWS") string(APPEND CMAKE_CXX_FLAGS_INIT " /DWINAPI_FAMILY=2 /DTARGET_WINDOWS_STORE /D_WINSOCK_DEPRECATED_NO_WARNINGS /D_CRT_NONSTDC_NO_DEPRECATE") string(APPEND CMAKE_CXX_FLAGS_DEBUG_INIT " /D_DEBUG /MDd /Ob0 /Od /RTC1 /D_ITERATOR_DEBUG_LEVEL=0") string(APPEND CMAKE_CXX_FLAGS_RELEASE_INIT " /MD /Ox /Ob2 /Oi /Ot /Oy /GL /DNDEBUG") From ddcdcc156b00294e54397ff37d50a4771f0d09a3 Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Mon, 25 Mar 2024 20:56:23 +0100 Subject: [PATCH 023/651] [windows] c++20: Do not compile C files as C++ C and C++ aren't actually compatible and this breaks the build starting with C++20 (due to cc_overlay.c using u8 literals). When these files are now compiled as C files, they cannot use the C++ precompiled headers, so just disable PCH for those few files. --- CMakeLists.txt | 1 - cmake/scripts/common/Macros.cmake | 14 ------ cmake/scripts/windows/Macros.cmake | 3 ++ cmake/scripts/windowsstore/Macros.cmake | 67 +------------------------ 4 files changed, 4 insertions(+), 81 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 19c914a7de920..aa674c7c4fb4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -414,7 +414,6 @@ set_target_properties(lib${APP_NAME_LC} PROPERTIES PROJECT_LABEL "xbmc") source_group_by_folder(lib${APP_NAME_LC} RELATIVE ${CMAKE_SOURCE_DIR}/xbmc) if(WIN32) add_precompiled_header(lib${APP_NAME_LC} pch.h ${CMAKE_SOURCE_DIR}/xbmc/platform/win32/pch.cpp) - set_language_cxx(lib${APP_NAME_LC}) endif() # main binary diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index 08455f6d805fa..65d6406ec7708 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -88,7 +88,6 @@ function(core_add_library name) # Add precompiled headers to Kodi main libraries if(CORE_SYSTEM_NAME MATCHES windows) add_precompiled_header(${name} pch.h ${CMAKE_SOURCE_DIR}/xbmc/platform/win32/pch.cpp PCH_TARGET kodi) - set_language_cxx(${name}) endif() else() foreach(src IN LISTS SOURCES HEADERS OTHERS) @@ -183,19 +182,6 @@ function(core_add_shared_library name) endif() endfunction() -# Sets the compile language for all C source files in a target to CXX. -# Needs to be called from the CMakeLists.txt that defines the target. -# Arguments: -# target target -function(set_language_cxx target) - get_property(sources TARGET ${target} PROPERTY SOURCES) - foreach(file IN LISTS sources) - if(file MATCHES "\.c$") - set_source_files_properties(${file} PROPERTIES LANGUAGE CXX) - endif() - endforeach() -endfunction() - # Add a data file to installation list with a mirror in build tree # Mirroring files in the buildtree allows to execute the app from there. # Arguments: diff --git a/cmake/scripts/windows/Macros.cmake b/cmake/scripts/windows/Macros.cmake index 2d3500d8f3e4b..db608856d0529 100644 --- a/cmake/scripts/windows/Macros.cmake +++ b/cmake/scripts/windows/Macros.cmake @@ -35,6 +35,9 @@ function(add_precompiled_header target pch_header pch_source) foreach(exclude_source IN LISTS PCH_EXCLUDE_SOURCES) list(REMOVE_ITEM sources ${exclude_source}) endforeach() + # A PCH compiled in C++ mode cannot be used for C code + list(FILTER sources EXCLUDE REGEX "\\.c$") + set_source_files_properties(${sources} PROPERTIES COMPILE_FLAGS "/Yu\"${pch_header}\" /Fp\"${pch_binary}\" /FI\"${pch_header}\"" OBJECT_DEPENDS "${pch_binary}") diff --git a/cmake/scripts/windowsstore/Macros.cmake b/cmake/scripts/windowsstore/Macros.cmake index 6c28e38033b9c..b86bf627fffae 100644 --- a/cmake/scripts/windowsstore/Macros.cmake +++ b/cmake/scripts/windowsstore/Macros.cmake @@ -1,69 +1,4 @@ -function(core_link_library lib wraplib) - message(AUTHOR_WARNING "core_link_library is not compatible with windows.") -endfunction() - -function(find_soname lib) - # Windows uses hardcoded dlls in xbmc/DllPaths_win32.h. - # Therefore the output of this function is unused. -endfunction() - -# Add precompiled header to target -# Arguments: -# target existing target that will be set up to compile with a precompiled header -# pch_header the precompiled header file -# pch_source the precompiled header source file -# Optional Arguments: -# PCH_TARGET build precompiled header as separate target with the given name -# so that the same precompiled header can be used for multiple libraries -# EXCLUDE_SOURCES if not all target sources shall use the precompiled header, -# the relevant files can be listed here -# On return: -# Compiles the pch_source into a precompiled header and adds the header to -# the given target -function(add_precompiled_header target pch_header pch_source) - cmake_parse_arguments(PCH "" "PCH_TARGET" "EXCLUDE_SOURCES" ${ARGN}) - - if(PCH_PCH_TARGET) - set(pch_binary ${PRECOMPILEDHEADER_DIR}/${PCH_PCH_TARGET}.pch) - else() - set(pch_binary ${PRECOMPILEDHEADER_DIR}/${target}.pch) - endif() - - # Set compile options and dependency for sources - get_target_property(sources ${target} SOURCES) - list(REMOVE_ITEM sources ${pch_source}) - foreach(exclude_source IN LISTS PCH_EXCLUDE_SOURCES) - list(REMOVE_ITEM sources ${exclude_source}) - endforeach() - set_source_files_properties(${sources} - PROPERTIES COMPILE_FLAGS "/Yu\"${pch_header}\" /Fp\"${pch_binary}\" /FI\"${pch_header}\"" - OBJECT_DEPENDS "${pch_binary}") - - # Set compile options for precompiled header - if(NOT PCH_PCH_TARGET OR NOT TARGET ${PCH_PCH_TARGET}_pch) - set_source_files_properties(${pch_source} - PROPERTIES COMPILE_FLAGS "/Yc\"${pch_header}\" /Fp\"${pch_binary}\"" - OBJECT_OUTPUTS "${pch_binary}") - endif() - - # Compile precompiled header - if(PCH_PCH_TARGET) - # As own target for usage in multiple libraries - if(NOT TARGET ${PCH_PCH_TARGET}_pch) - add_library(${PCH_PCH_TARGET}_pch STATIC ${pch_source}) - set_target_properties(${PCH_PCH_TARGET}_pch PROPERTIES COMPILE_PDB_NAME vc140 - COMPILE_PDB_OUTPUT_DIRECTORY ${PRECOMPILEDHEADER_DIR} - FOLDER "Build Utilities") - endif() - # From VS2012 onwards, precompiled headers have to be linked against (LNK2011). - target_link_libraries(${target} PUBLIC ${PCH_PCH_TARGET}_pch) - set_target_properties(${target} PROPERTIES COMPILE_PDB_NAME vc140 - COMPILE_PDB_OUTPUT_DIRECTORY ${PRECOMPILEDHEADER_DIR}) - else() - # As part of the target - target_sources(${target} PRIVATE ${pch_source}) - endif() -endfunction() +include(${CMAKE_CURRENT_LIST_DIR}/../windows/Macros.cmake) macro(winstore_set_assets target) file(GLOB ASSET_FILES "${CMAKE_SOURCE_DIR}/tools/windows/packaging/uwp/media/*.png") From 85a50650948fbfe86aa5500b1c20bbf5bf43fa45 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Wed, 22 Dec 2021 10:50:14 -0800 Subject: [PATCH 024/651] c++20: remove volatile usage In CacheStrategy.h, it is unclear why volatile was ever used. In AEUtil.h, the std::atomic will be safer and likely more correct than the previous usage of volatile. --- xbmc/cores/AudioEngine/Utils/AEUtil.h | 11 +++++++---- xbmc/filesystem/CacheStrategy.h | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/xbmc/cores/AudioEngine/Utils/AEUtil.h b/xbmc/cores/AudioEngine/Utils/AEUtil.h index 6b5fc123c652f..65a846e04402d 100644 --- a/xbmc/cores/AudioEngine/Utils/AEUtil.h +++ b/xbmc/cores/AudioEngine/Utils/AEUtil.h @@ -9,9 +9,12 @@ #pragma once #include "AEAudioFormat.h" -#include "PlatformDefs.h" + +#include #include +#include "PlatformDefs.h" + extern "C" { #include #include @@ -55,12 +58,12 @@ class CAESpinSection { public: void enter() { m_enter++; } - void leave() { m_leave = m_enter; } + void leave() { m_leave.store(m_enter); } protected: friend class CAESpinLock; - volatile unsigned int m_enter = 0; - volatile unsigned int m_leave = 0; + std::atomic_uint32_t m_enter = 0; + std::atomic_uint32_t m_leave = 0; }; class CAESpinLock diff --git a/xbmc/filesystem/CacheStrategy.h b/xbmc/filesystem/CacheStrategy.h index 76c66bb454d6a..93da3258f8758 100644 --- a/xbmc/filesystem/CacheStrategy.h +++ b/xbmc/filesystem/CacheStrategy.h @@ -93,9 +93,9 @@ class CSimpleFileCache : public CCacheStrategy { IFile* m_cacheFileRead; IFile* m_cacheFileWrite; CEvent* m_hDataAvailEvent; - volatile int64_t m_nStartPosition = 0; - volatile int64_t m_nWritePosition = 0; - volatile int64_t m_nReadPosition = 0; + int64_t m_nStartPosition = 0; + int64_t m_nWritePosition = 0; + int64_t m_nReadPosition = 0; }; class CDoubleCache : public CCacheStrategy{ From 474f1d60c940cb395e54b9ade0e519f718faa8da Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Wed, 22 Dec 2021 10:50:47 -0800 Subject: [PATCH 025/651] c++20: need to use [=, this] to pass reference for lambda --- xbmc/cores/paplayer/PAPlayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/cores/paplayer/PAPlayer.cpp b/xbmc/cores/paplayer/PAPlayer.cpp index 577441340853c..97a7ad5262a33 100644 --- a/xbmc/cores/paplayer/PAPlayer.cpp +++ b/xbmc/cores/paplayer/PAPlayer.cpp @@ -216,7 +216,7 @@ bool PAPlayer::OpenFile(const CFileItem& file, const CPlayerOptions &options) std::unique_lock lock(m_streamsLock); m_jobCounter++; } - CServiceBroker::GetJobManager()->Submit([=]() { QueueNextFileEx(file, false); }, this, + CServiceBroker::GetJobManager()->Submit([=, this]() { QueueNextFileEx(file, false); }, this, CJob::PRIORITY_NORMAL); std::unique_lock lock(m_streamsLock); From 89a1142eb6a9beeee28a4f4aa173aafee8f6f7f3 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Wed, 22 Dec 2021 10:51:24 -0800 Subject: [PATCH 026/651] c++20: u8 literal has different meaning --- .../VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.cpp | 8 ++++---- xbmc/utils/CSSUtils.cpp | 2 +- xbmc/utils/test/TestCharsetConverter.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.cpp index c1bcc3cb88543..df329a11a7760 100644 --- a/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.cpp +++ b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.cpp @@ -182,10 +182,10 @@ void TranslateEscapeChars(std::string& text) // U+200E for "‎" and U+200F for "‏" // but libass rendering assume the text as left-to-right, // to display text in the right order we have to use embedded codes - StringUtils::Replace(text, "‎", u8"\u202a"); - StringUtils::Replace(text, "‏", u8"\u202b"); - StringUtils::Replace(text, "⁨", u8"\u2068"); - StringUtils::Replace(text, "⁩", u8"\u2069"); + StringUtils::Replace(text, "‎", "\u202a"); + StringUtils::Replace(text, "‏", "\u202b"); + StringUtils::Replace(text, "⁨", "\u2068"); + StringUtils::Replace(text, "⁩", "\u2069"); StringUtils::Replace(text, "&", "&"); StringUtils::Replace(text, "<", "<"); StringUtils::Replace(text, ">", ">"); diff --git a/xbmc/utils/CSSUtils.cpp b/xbmc/utils/CSSUtils.cpp index 329d70cdbc99b..938c4b73007de 100644 --- a/xbmc/utils/CSSUtils.cpp +++ b/xbmc/utils/CSSUtils.cpp @@ -80,7 +80,7 @@ std::string escapeStringChunk(std::string& str, size_t& pos) if (codePoint == 0 || isSurrogateCodePoint(codePoint) || isGreaterThanMaximumAllowedCodePoint(codePoint)) { - result += u8"\uFFFD"; + result += "\uFFFD"; } else if (codePoint < 0x80) { diff --git a/xbmc/utils/test/TestCharsetConverter.cpp b/xbmc/utils/test/TestCharsetConverter.cpp index 203191d690ecf..378eae0f626d0 100644 --- a/xbmc/utils/test/TestCharsetConverter.cpp +++ b/xbmc/utils/test/TestCharsetConverter.cpp @@ -251,7 +251,7 @@ TEST_F(TestCharsetConverter, isValidUtf8_4) TEST_F(TestCharsetConverter, wToUTF8) { refstrw1 = L"test_wToUTF8"; - refstra1 = u8"test_wToUTF8"; + refstra1 = "test_wToUTF8"; varstra1.clear(); g_charsetConverter.wToUTF8(refstrw1, varstra1); EXPECT_STREQ(refstra1.c_str(), varstra1.c_str()); From 921eccfbde2d9fa62eb7029ed7a4c989ff91ff07 Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Mon, 25 Mar 2024 20:42:22 +0100 Subject: [PATCH 027/651] c++20: Fix deprecated call to shared_ptr::unique --- xbmc/GUIInfoManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 835fd306b023c..10826139e68ad 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -11245,7 +11245,7 @@ void CGUIInfoManager::Clear() { swapList.clear(); for (auto &item : m_bools) - if (!item.unique()) + if (item.use_count() > 1) swapList.insert(item); m_bools.swap(swapList); } while (swapList.size() != m_bools.size()); From 6ee13f2170fbbffba4e1ce0916233816f2658845 Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Sun, 24 Mar 2024 23:28:46 +0100 Subject: [PATCH 028/651] Fix wrong format string with unescaped {} --- xbmc/platform/linux/DBusUtil.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/platform/linux/DBusUtil.cpp b/xbmc/platform/linux/DBusUtil.cpp index 2efb75cb08664..51c5a2a8d000f 100644 --- a/xbmc/platform/linux/DBusUtil.cpp +++ b/xbmc/platform/linux/DBusUtil.cpp @@ -48,7 +48,7 @@ CVariant CDBusUtil::GetAll(const char *destination, const char *object, const ch if (dbus_message_iter_init(reply, &iter)) { if (!dbus_message_has_signature(reply, "a{sv}")) - CLog::Log(LOGERROR, "DBus: wrong signature on GetAll - should be \"a{sv}\" but was {}", + CLog::Log(LOGERROR, "DBus: wrong signature on GetAll - should be \"a{{sv}}\" but was {}", dbus_message_iter_get_signature(&iter)); else { From a6bcb779b76bf99f5b461f2e1899f12e60a5f8b8 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Mon, 3 Jan 2022 11:10:06 -0800 Subject: [PATCH 029/651] SMBWSDiscoveryListener: use std::string_view for constexpr fmt::format usage --- .../filesystem/SMBWSDiscoveryListener.cpp | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp index dd5aae6fe68e3..1cf22476248e5 100644 --- a/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp +++ b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.cpp @@ -37,7 +37,7 @@ namespace WSDiscovery { // Templates for SOAPXML messages for WS-Discovery messages -static const std::string soap_msg_templ = +static constexpr std::string_view soap_msg_templ = "\n" "\n"; -static const std::string hello_body = "\n" - "\n" - "\n" - "urn:uuid:{}\n" - "\n" - "wsdp:Device pub:Computer\n" - "1\n" - "\n" - "\n"; - -static const std::string bye_body = "\n" - "\n" - "\n" - "urn:uuid:{}\n" - "\n" - "wsdp:Device pub:Computer\n" - "2\n" - "\n" - "\n"; - -static const std::string probe_body = "\n" - "\n" - "wsdp:Device\n" - "\n" - "\n"; - -static const std::string resolve_body = "\n" - "\n" - "\n" - "" - "{}" - "\n" - "\n" - "\n" - "\n"; +static constexpr std::string_view hello_body = "\n" + "\n" + "\n" + "urn:uuid:{}\n" + "\n" + "wsdp:Device pub:Computer\n" + "1\n" + "\n" + "\n"; + +static constexpr std::string_view bye_body = "\n" + "\n" + "\n" + "urn:uuid:{}\n" + "\n" + "wsdp:Device pub:Computer\n" + "2\n" + "\n" + "\n"; + +static constexpr std::string_view probe_body = "\n" + "\n" + "wsdp:Device\n" + "\n" + "\n"; + +static constexpr std::string_view resolve_body = "\n" + "\n" + "\n" + "" + "{}" + "\n" + "\n" + "\n" + "\n"; // These are the only actions we concern ourselves with for our WS-D implementation static const std::string WSD_ACT_HELLO = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Hello"; @@ -123,7 +123,7 @@ static const std::array, 1> address_tag{ static const std::array, 1> types_tag{ {{"", ""}}}; -static const std::string get_msg = +static constexpr std::string_view get_msg = "\n" " Date: Thu, 23 Dec 2021 09:15:14 -0800 Subject: [PATCH 030/651] StringUtils: Mark passed format strings as runtime, fix type conversion --- xbmc/utils/StringUtils.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/xbmc/utils/StringUtils.h b/xbmc/utils/StringUtils.h index 65471f9725001..56aade1ca7ba9 100644 --- a/xbmc/utils/StringUtils.h +++ b/xbmc/utils/StringUtils.h @@ -55,15 +55,15 @@ DEF_TO_STR_VALUE(foo) // outputs "4" #define DEF_TO_STR_NAME(x) #x #define DEF_TO_STR_VALUE(x) DEF_TO_STR_NAME(x) -template::value, int> = 0> -constexpr auto&& EnumToInt(T&& arg) noexcept +template, int> = 0> +constexpr decltype(auto) EnumToInt(T&& arg) noexcept { - return arg; + return std::forward(arg); } -template::value, int> = 0> -constexpr auto EnumToInt(T&& arg) noexcept +template, int> = 0> +constexpr decltype(auto) EnumToInt(T&& arg) noexcept { - return static_cast(arg); + return fmt::underlying(std::forward(arg)); } class StringUtils @@ -76,16 +76,16 @@ class StringUtils \return Formatted string */ template - static std::string Format(const std::string& fmt, Args&&... args) + static constexpr std::string Format(std::string_view format, Args&&... args) { // coverity[fun_call_w_exception : FALSE] - return ::fmt::format(fmt, EnumToInt(std::forward(args))...); + return fmt::format(fmt::runtime(format), EnumToInt(std::forward(args))...); } template - static std::wstring Format(const std::wstring& fmt, Args&&... args) + static constexpr std::wstring Format(std::wstring_view format, Args&&... args) { // coverity[fun_call_w_exception : FALSE] - return ::fmt::format(fmt, EnumToInt(std::forward(args))...); + return fmt::format(fmt::runtime(format), EnumToInt(std::forward(args))...); } static std::string FormatV(PRINTF_FORMAT_STRING const char *fmt, va_list args); From f743548bc438fae2b260d0494d228766c8835e5e Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Sat, 11 May 2024 10:08:53 +0200 Subject: [PATCH 031/651] CLog: allow using constexpr formatter and use fmt::vformat --- xbmc/utils/log.cpp | 2 +- xbmc/utils/log.h | 32 ++++++++++++++------------------ 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/xbmc/utils/log.cpp b/xbmc/utils/log.cpp index 24bd5072f6df5..57c43af6472c9 100644 --- a/xbmc/utils/log.cpp +++ b/xbmc/utils/log.cpp @@ -175,7 +175,7 @@ void CLog::SetLogLevel(int level) spdlog::set_level(spdLevel); FormatAndLogInternal(spdlog::level::info, "Log level changed to \"{}\"", - spdlog::level::to_string_view(spdLevel)); + fmt::make_format_args(spdlog::level::to_string_view(spdLevel))); } bool CLog::IsLogLevelLogged(int loglevel) diff --git a/xbmc/utils/log.h b/xbmc/utils/log.h index 44626189bb34b..2529a40360eb6 100644 --- a/xbmc/utils/log.h +++ b/xbmc/utils/log.h @@ -87,16 +87,13 @@ class CLog : public ISettingsHandler, public ISettingCallback Logger GetLogger(const std::string& loggerName); template - static inline void Log(int level, const std::string_view& format, Args&&... args) + static void Log(int level, fmt::format_string format, Args&&... args) { Log(MapLogLevel(level), format, std::forward(args)...); } template - static inline void Log(int level, - uint32_t component, - const std::string_view& format, - Args&&... args) + static void Log(int level, uint32_t component, fmt::format_string format, Args&&... args) { if (!GetInstance().CanLogComponent(component)) return; @@ -105,18 +102,18 @@ class CLog : public ISettingsHandler, public ISettingCallback } template - static inline void Log(spdlog::level::level_enum level, - const std::string_view& format, - Args&&... args) + static void Log(spdlog::level::level_enum level, + fmt::format_string format, + Args&&... args) { - GetInstance().FormatAndLogInternal(level, format, std::forward(args)...); + GetInstance().FormatAndLogInternal(level, format, fmt::make_format_args(args...)); } template - static inline void Log(spdlog::level::level_enum level, - uint32_t component, - const std::string_view& format, - Args&&... args) + static void Log(spdlog::level::level_enum level, + uint32_t component, + fmt::format_string format, + Args&&... args) { if (!GetInstance().CanLogComponent(component)) return; @@ -133,12 +130,11 @@ class CLog : public ISettingsHandler, public ISettingCallback static spdlog::level::level_enum MapLogLevel(int level); - template - inline void FormatAndLogInternal(spdlog::level::level_enum level, - const std::string_view& format, - Args&&... args) + void FormatAndLogInternal(spdlog::level::level_enum level, + fmt::string_view format, + fmt::format_args args) { - auto message = fmt::format(format, std::forward(args)...); + auto message = fmt::vformat(format, args); // fixup newline alignment, number of spaces should equal prefix length FormatLineBreaks(message); From c711007166d792583d7bf410fcc2e12f6ea3a45d Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Sat, 11 May 2024 00:29:27 +0200 Subject: [PATCH 032/651] libUPnP: fix template definitions for c++20 --- lib/.clang-format | 2 + lib/libUPnP/Neptune/Source/Core/NptArray.h | 12 +- lib/libUPnP/Neptune/Source/Core/NptList.h | 6 +- lib/libUPnP/Neptune/Source/Core/NptMap.h | 14 +- lib/libUPnP/Neptune/Source/Core/NptQueue.h | 2 +- lib/libUPnP/Platinum/Source/Core/PltAction.h | 2 +- .../Platinum/Source/Core/PltDeviceData.cpp | 2 +- .../Platinum/Source/Core/PltHttpClientTask.h | 4 +- .../Devices/MediaServer/PltMediaCache.h | 4 +- ...nP-fix-template-definitions-for-c-20.patch | 179 ++++++++++++++++++ 10 files changed, 204 insertions(+), 23 deletions(-) create mode 100644 lib/.clang-format create mode 100644 lib/libUPnP/patches/0050-libUPnP-fix-template-definitions-for-c-20.patch diff --git a/lib/.clang-format b/lib/.clang-format new file mode 100644 index 0000000000000..47a38a93f2dbd --- /dev/null +++ b/lib/.clang-format @@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: Never diff --git a/lib/libUPnP/Neptune/Source/Core/NptArray.h b/lib/libUPnP/Neptune/Source/Core/NptArray.h index 721bac8943d06..2592ac5dad44e 100644 --- a/lib/libUPnP/Neptune/Source/Core/NptArray.h +++ b/lib/libUPnP/Neptune/Source/Core/NptArray.h @@ -59,12 +59,12 @@ class NPT_Array typedef T* Iterator; // methods - NPT_Array(): m_Capacity(0), m_ItemCount(0), m_Items(0) {} - explicit NPT_Array(NPT_Cardinal count); - NPT_Array(NPT_Cardinal count, const T& item); - NPT_Array(const T* items, NPT_Cardinal item_count); - ~NPT_Array(); - NPT_Array(const NPT_Array& copy); + NPT_Array() : m_Capacity(0), m_ItemCount(0), m_Items(0) {} + explicit NPT_Array(NPT_Cardinal count); + NPT_Array(NPT_Cardinal count, const T& item); + NPT_Array(const T* items, NPT_Cardinal item_count); + ~NPT_Array(); + NPT_Array(const NPT_Array& copy); NPT_Array& operator=(const NPT_Array& copy); bool operator==(const NPT_Array& other) const; bool operator!=(const NPT_Array& other) const; diff --git a/lib/libUPnP/Neptune/Source/Core/NptList.h b/lib/libUPnP/Neptune/Source/Core/NptList.h index 37d1802d05f77..29fcc7e2e9bb7 100644 --- a/lib/libUPnP/Neptune/Source/Core/NptList.h +++ b/lib/libUPnP/Neptune/Source/Core/NptList.h @@ -109,9 +109,9 @@ class NPT_List }; // methods - NPT_List(); - NPT_List(const NPT_List& list); - ~NPT_List(); + NPT_List(); + NPT_List(const NPT_List& list); + ~NPT_List(); NPT_Result Add(const T& data); NPT_Result Insert(const Iterator where, const T& data); NPT_Result Remove(const T& data, bool all=false); diff --git a/lib/libUPnP/Neptune/Source/Core/NptMap.h b/lib/libUPnP/Neptune/Source/Core/NptMap.h index 75acbbed5fc5d..cd3ead5bb70ce 100644 --- a/lib/libUPnP/Neptune/Source/Core/NptMap.h +++ b/lib/libUPnP/Neptune/Source/Core/NptMap.h @@ -76,11 +76,11 @@ class NPT_Map }; // constructors - NPT_Map() {} - NPT_Map(const NPT_Map& copy); + NPT_Map() {} + NPT_Map(const NPT_Map& copy); // destructor - ~NPT_Map(); + ~NPT_Map(); // methods NPT_Result Put(const K& key, const V& value); @@ -399,12 +399,12 @@ class NPT_HashMap }; // constructors - NPT_HashMap(); - NPT_HashMap(const HF& hasher); - NPT_HashMap(const NPT_HashMap& copy); + NPT_HashMap(); + NPT_HashMap(const HF& hasher); + NPT_HashMap(const NPT_HashMap& copy); // destructor - ~NPT_HashMap(); + ~NPT_HashMap(); // methods NPT_Result Put(const K& key, const V& value); diff --git a/lib/libUPnP/Neptune/Source/Core/NptQueue.h b/lib/libUPnP/Neptune/Source/Core/NptQueue.h index ecfe1a92fd015..b4f6baee1b46b 100644 --- a/lib/libUPnP/Neptune/Source/Core/NptQueue.h +++ b/lib/libUPnP/Neptune/Source/Core/NptQueue.h @@ -75,7 +75,7 @@ class NPT_Queue // methods NPT_Queue(NPT_Cardinal max_items = 0) : m_Delegate(NPT_GenericQueue::CreateInstance(max_items)) {} - virtual ~NPT_Queue() { delete m_Delegate; } + virtual ~NPT_Queue() { delete m_Delegate; } virtual NPT_Result Push(T* item, NPT_Timeout timeout = NPT_TIMEOUT_INFINITE) { return m_Delegate->Push(reinterpret_cast(item), timeout); } diff --git a/lib/libUPnP/Platinum/Source/Core/PltAction.h b/lib/libUPnP/Platinum/Source/Core/PltAction.h index f457f3d7c16a3..63947eaea1e8d 100644 --- a/lib/libUPnP/Platinum/Source/Core/PltAction.h +++ b/lib/libUPnP/Platinum/Source/Core/PltAction.h @@ -283,7 +283,7 @@ template class PLT_GetSCPDXMLIterator { public: - PLT_GetSCPDXMLIterator(NPT_XmlElementNode* node) : + PLT_GetSCPDXMLIterator(NPT_XmlElementNode* node) : m_Node(node) {} NPT_Result operator()(T* const & data) const { diff --git a/lib/libUPnP/Platinum/Source/Core/PltDeviceData.cpp b/lib/libUPnP/Platinum/Source/Core/PltDeviceData.cpp index a092b7783379e..a322bc44345d8 100644 --- a/lib/libUPnP/Platinum/Source/Core/PltDeviceData.cpp +++ b/lib/libUPnP/Platinum/Source/Core/PltDeviceData.cpp @@ -366,7 +366,7 @@ template class PLT_GetDescriptionIterator { public: - PLT_GetDescriptionIterator(NPT_XmlElementNode* parent) : + PLT_GetDescriptionIterator(NPT_XmlElementNode* parent) : m_Parent(parent) {} NPT_Result operator()(T& data) const { diff --git a/lib/libUPnP/Platinum/Source/Core/PltHttpClientTask.h b/lib/libUPnP/Platinum/Source/Core/PltHttpClientTask.h index 62ac723cd0d47..8c7c7225e5698 100644 --- a/lib/libUPnP/Platinum/Source/Core/PltHttpClientTask.h +++ b/lib/libUPnP/Platinum/Source/Core/PltHttpClientTask.h @@ -97,13 +97,13 @@ template class PLT_HttpClientTask : public PLT_HttpClientSocketTask { public: - PLT_HttpClientTask(const NPT_HttpUrl& url, T* data) : + PLT_HttpClientTask(const NPT_HttpUrl& url, T* data) : PLT_HttpClientSocketTask(new NPT_HttpRequest(url, "GET", NPT_HTTP_PROTOCOL_1_1)), m_Data(data) {} protected: - ~PLT_HttpClientTask() override {} + ~PLT_HttpClientTask() override {} protected: // PLT_HttpClientSocketTask method diff --git a/lib/libUPnP/Platinum/Source/Devices/MediaServer/PltMediaCache.h b/lib/libUPnP/Platinum/Source/Devices/MediaServer/PltMediaCache.h index 8e365d807e139..352e4bec30ffd 100644 --- a/lib/libUPnP/Platinum/Source/Devices/MediaServer/PltMediaCache.h +++ b/lib/libUPnP/Platinum/Source/Devices/MediaServer/PltMediaCache.h @@ -58,8 +58,8 @@ class PLT_MediaCache typedef typename NPT_Map::Entry ElementEntry; typedef typename NPT_List::Iterator ElementIterator; - PLT_MediaCache() {} - virtual ~PLT_MediaCache() {} + PLT_MediaCache() {} + virtual ~PLT_MediaCache() {} NPT_Result Put(const char* root, const char* key, T& value, U* tag = NULL); NPT_Result Get(const char* root, const char* key, T& value, U* tag = NULL); diff --git a/lib/libUPnP/patches/0050-libUPnP-fix-template-definitions-for-c-20.patch b/lib/libUPnP/patches/0050-libUPnP-fix-template-definitions-for-c-20.patch new file mode 100644 index 0000000000000..d1b1b594a1ac1 --- /dev/null +++ b/lib/libUPnP/patches/0050-libUPnP-fix-template-definitions-for-c-20.patch @@ -0,0 +1,179 @@ +From 8d184dfc8e75e87f4e0dd8433696e015b4f8e9e1 Mon Sep 17 00:00:00 2001 +From: Philipp Kerling +Date: Sat, 11 May 2024 00:29:27 +0200 +Subject: [PATCH 1/8] libUPnP: fix template definitions for c++20 + +--- + lib/.clang-format | 2 ++ + lib/libUPnP/Neptune/Source/Core/NptArray.h | 12 ++++++------ + lib/libUPnP/Neptune/Source/Core/NptList.h | 6 +++--- + lib/libUPnP/Neptune/Source/Core/NptMap.h | 14 +++++++------- + lib/libUPnP/Neptune/Source/Core/NptQueue.h | 2 +- + lib/libUPnP/Platinum/Source/Core/PltAction.h | 2 +- + lib/libUPnP/Platinum/Source/Core/PltDeviceData.cpp | 2 +- + .../Platinum/Source/Core/PltHttpClientTask.h | 4 ++-- + .../Source/Devices/MediaServer/PltMediaCache.h | 4 ++-- + 9 files changed, 25 insertions(+), 23 deletions(-) + create mode 100644 lib/.clang-format + +diff --git a/lib/.clang-format b/lib/.clang-format +new file mode 100644 +index 0000000000..47a38a93f2 +--- /dev/null ++++ b/lib/.clang-format +@@ -0,0 +1,2 @@ ++DisableFormat: true ++SortIncludes: Never +diff --git a/lib/libUPnP/Neptune/Source/Core/NptArray.h b/lib/libUPnP/Neptune/Source/Core/NptArray.h +index 721bac8943..2592ac5dad 100644 +--- a/lib/libUPnP/Neptune/Source/Core/NptArray.h ++++ b/lib/libUPnP/Neptune/Source/Core/NptArray.h +@@ -59,12 +59,12 @@ public: + typedef T* Iterator; + + // methods +- NPT_Array(): m_Capacity(0), m_ItemCount(0), m_Items(0) {} +- explicit NPT_Array(NPT_Cardinal count); +- NPT_Array(NPT_Cardinal count, const T& item); +- NPT_Array(const T* items, NPT_Cardinal item_count); +- ~NPT_Array(); +- NPT_Array(const NPT_Array& copy); ++ NPT_Array() : m_Capacity(0), m_ItemCount(0), m_Items(0) {} ++ explicit NPT_Array(NPT_Cardinal count); ++ NPT_Array(NPT_Cardinal count, const T& item); ++ NPT_Array(const T* items, NPT_Cardinal item_count); ++ ~NPT_Array(); ++ NPT_Array(const NPT_Array& copy); + NPT_Array& operator=(const NPT_Array& copy); + bool operator==(const NPT_Array& other) const; + bool operator!=(const NPT_Array& other) const; +diff --git a/lib/libUPnP/Neptune/Source/Core/NptList.h b/lib/libUPnP/Neptune/Source/Core/NptList.h +index 37d1802d05..29fcc7e2e9 100644 +--- a/lib/libUPnP/Neptune/Source/Core/NptList.h ++++ b/lib/libUPnP/Neptune/Source/Core/NptList.h +@@ -109,9 +109,9 @@ public: + }; + + // methods +- NPT_List(); +- NPT_List(const NPT_List& list); +- ~NPT_List(); ++ NPT_List(); ++ NPT_List(const NPT_List& list); ++ ~NPT_List(); + NPT_Result Add(const T& data); + NPT_Result Insert(const Iterator where, const T& data); + NPT_Result Remove(const T& data, bool all=false); +diff --git a/lib/libUPnP/Neptune/Source/Core/NptMap.h b/lib/libUPnP/Neptune/Source/Core/NptMap.h +index 75acbbed5f..cd3ead5bb7 100644 +--- a/lib/libUPnP/Neptune/Source/Core/NptMap.h ++++ b/lib/libUPnP/Neptune/Source/Core/NptMap.h +@@ -76,11 +76,11 @@ public: + }; + + // constructors +- NPT_Map() {} +- NPT_Map(const NPT_Map& copy); ++ NPT_Map() {} ++ NPT_Map(const NPT_Map& copy); + + // destructor +- ~NPT_Map(); ++ ~NPT_Map(); + + // methods + NPT_Result Put(const K& key, const V& value); +@@ -399,12 +399,12 @@ public: + }; + + // constructors +- NPT_HashMap(); +- NPT_HashMap(const HF& hasher); +- NPT_HashMap(const NPT_HashMap& copy); ++ NPT_HashMap(); ++ NPT_HashMap(const HF& hasher); ++ NPT_HashMap(const NPT_HashMap& copy); + + // destructor +- ~NPT_HashMap(); ++ ~NPT_HashMap(); + + // methods + NPT_Result Put(const K& key, const V& value); +diff --git a/lib/libUPnP/Neptune/Source/Core/NptQueue.h b/lib/libUPnP/Neptune/Source/Core/NptQueue.h +index ecfe1a92fd..b4f6baee1b 100644 +--- a/lib/libUPnP/Neptune/Source/Core/NptQueue.h ++++ b/lib/libUPnP/Neptune/Source/Core/NptQueue.h +@@ -75,7 +75,7 @@ class NPT_Queue + // methods + NPT_Queue(NPT_Cardinal max_items = 0) : + m_Delegate(NPT_GenericQueue::CreateInstance(max_items)) {} +- virtual ~NPT_Queue() { delete m_Delegate; } ++ virtual ~NPT_Queue() { delete m_Delegate; } + virtual NPT_Result Push(T* item, NPT_Timeout timeout = NPT_TIMEOUT_INFINITE) { + return m_Delegate->Push(reinterpret_cast(item), timeout); + } +diff --git a/lib/libUPnP/Platinum/Source/Core/PltAction.h b/lib/libUPnP/Platinum/Source/Core/PltAction.h +index f457f3d7c1..63947eaea1 100644 +--- a/lib/libUPnP/Platinum/Source/Core/PltAction.h ++++ b/lib/libUPnP/Platinum/Source/Core/PltAction.h +@@ -283,7 +283,7 @@ template + class PLT_GetSCPDXMLIterator + { + public: +- PLT_GetSCPDXMLIterator(NPT_XmlElementNode* node) : ++ PLT_GetSCPDXMLIterator(NPT_XmlElementNode* node) : + m_Node(node) {} + + NPT_Result operator()(T* const & data) const { +diff --git a/lib/libUPnP/Platinum/Source/Core/PltDeviceData.cpp b/lib/libUPnP/Platinum/Source/Core/PltDeviceData.cpp +index a092b77833..a322bc4434 100644 +--- a/lib/libUPnP/Platinum/Source/Core/PltDeviceData.cpp ++++ b/lib/libUPnP/Platinum/Source/Core/PltDeviceData.cpp +@@ -366,7 +366,7 @@ template + class PLT_GetDescriptionIterator + { + public: +- PLT_GetDescriptionIterator(NPT_XmlElementNode* parent) : ++ PLT_GetDescriptionIterator(NPT_XmlElementNode* parent) : + m_Parent(parent) {} + + NPT_Result operator()(T& data) const { +diff --git a/lib/libUPnP/Platinum/Source/Core/PltHttpClientTask.h b/lib/libUPnP/Platinum/Source/Core/PltHttpClientTask.h +index 62ac723cd0..8c7c7225e5 100644 +--- a/lib/libUPnP/Platinum/Source/Core/PltHttpClientTask.h ++++ b/lib/libUPnP/Platinum/Source/Core/PltHttpClientTask.h +@@ -97,13 +97,13 @@ template + class PLT_HttpClientTask : public PLT_HttpClientSocketTask + { + public: +- PLT_HttpClientTask(const NPT_HttpUrl& url, T* data) : ++ PLT_HttpClientTask(const NPT_HttpUrl& url, T* data) : + PLT_HttpClientSocketTask(new NPT_HttpRequest(url, + "GET", + NPT_HTTP_PROTOCOL_1_1)), + m_Data(data) {} + protected: +- ~PLT_HttpClientTask() override {} ++ ~PLT_HttpClientTask() override {} + + protected: + // PLT_HttpClientSocketTask method +diff --git a/lib/libUPnP/Platinum/Source/Devices/MediaServer/PltMediaCache.h b/lib/libUPnP/Platinum/Source/Devices/MediaServer/PltMediaCache.h +index 8e365d807e..352e4bec30 100644 +--- a/lib/libUPnP/Platinum/Source/Devices/MediaServer/PltMediaCache.h ++++ b/lib/libUPnP/Platinum/Source/Devices/MediaServer/PltMediaCache.h +@@ -58,8 +58,8 @@ public: + typedef typename NPT_Map::Entry ElementEntry; + typedef typename NPT_List::Iterator ElementIterator; + +- PLT_MediaCache() {} +- virtual ~PLT_MediaCache() {} ++ PLT_MediaCache() {} ++ virtual ~PLT_MediaCache() {} + + NPT_Result Put(const char* root, const char* key, T& value, U* tag = NULL); + NPT_Result Get(const char* root, const char* key, T& value, U* tag = NULL); +-- +2.44.0 + From b0a8a1ebdf5ca11af20aa70c9b04df974092f2b6 Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Sat, 11 May 2024 10:01:52 +0200 Subject: [PATCH 033/651] libUPnP: Fix warning about double promotion for NPT_TimeStamp --- lib/libUPnP/Neptune/Source/Core/NptTime.cpp | 5 +++ lib/libUPnP/Neptune/Source/Core/NptTime.h | 1 + ...ing-about-double-promotion-for-NPT_T.patch | 42 +++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 lib/libUPnP/patches/0051-libUPnP-Fix-warning-about-double-promotion-for-NPT_T.patch diff --git a/lib/libUPnP/Neptune/Source/Core/NptTime.cpp b/lib/libUPnP/Neptune/Source/Core/NptTime.cpp index d842259e8525f..8615c71e43c85 100755 --- a/lib/libUPnP/Neptune/Source/Core/NptTime.cpp +++ b/lib/libUPnP/Neptune/Source/Core/NptTime.cpp @@ -72,6 +72,11 @@ NPT_TimeStamp::NPT_TimeStamp(const NPT_TimeStamp& timestamp) /*---------------------------------------------------------------------- | NPT_TimeStamp::NPT_TimeStamp +---------------------------------------------------------------------*/ +NPT_TimeStamp::NPT_TimeStamp(float seconds) +: NPT_TimeStamp(static_cast(seconds)) +{ +} + NPT_TimeStamp::NPT_TimeStamp(double seconds) { m_NanoSeconds = (NPT_Int64)(seconds * 1e9); diff --git a/lib/libUPnP/Neptune/Source/Core/NptTime.h b/lib/libUPnP/Neptune/Source/Core/NptTime.h index ae02a48c3d2c6..8b58eefac67ea 100644 --- a/lib/libUPnP/Neptune/Source/Core/NptTime.h +++ b/lib/libUPnP/Neptune/Source/Core/NptTime.h @@ -54,6 +54,7 @@ class NPT_TimeStamp NPT_TimeStamp(const NPT_TimeStamp& timestamp); NPT_TimeStamp() : m_NanoSeconds(0) {} NPT_TimeStamp(NPT_Int64 nanoseconds) : m_NanoSeconds(nanoseconds) {} + NPT_TimeStamp(float seconds); NPT_TimeStamp(double seconds); NPT_TimeStamp& operator+=(const NPT_TimeStamp& time_stamp); NPT_TimeStamp& operator-=(const NPT_TimeStamp& time_stamp); diff --git a/lib/libUPnP/patches/0051-libUPnP-Fix-warning-about-double-promotion-for-NPT_T.patch b/lib/libUPnP/patches/0051-libUPnP-Fix-warning-about-double-promotion-for-NPT_T.patch new file mode 100644 index 0000000000000..c02253d1dbb43 --- /dev/null +++ b/lib/libUPnP/patches/0051-libUPnP-Fix-warning-about-double-promotion-for-NPT_T.patch @@ -0,0 +1,42 @@ +From 002561b93ca719b5664bd63c7823b36d262377a6 Mon Sep 17 00:00:00 2001 +From: Philipp Kerling +Date: Sat, 11 May 2024 10:01:52 +0200 +Subject: [PATCH 2/8] libUPnP: Fix warning about double promotion for + NPT_TimeStamp + +--- + lib/libUPnP/Neptune/Source/Core/NptTime.cpp | 5 +++++ + lib/libUPnP/Neptune/Source/Core/NptTime.h | 1 + + 2 files changed, 6 insertions(+) + +diff --git a/lib/libUPnP/Neptune/Source/Core/NptTime.cpp b/lib/libUPnP/Neptune/Source/Core/NptTime.cpp +index d842259e85..8615c71e43 100755 +--- a/lib/libUPnP/Neptune/Source/Core/NptTime.cpp ++++ b/lib/libUPnP/Neptune/Source/Core/NptTime.cpp +@@ -72,6 +72,11 @@ NPT_TimeStamp::NPT_TimeStamp(const NPT_TimeStamp& timestamp) + /*---------------------------------------------------------------------- + | NPT_TimeStamp::NPT_TimeStamp + +---------------------------------------------------------------------*/ ++NPT_TimeStamp::NPT_TimeStamp(float seconds) ++: NPT_TimeStamp(static_cast(seconds)) ++{ ++} ++ + NPT_TimeStamp::NPT_TimeStamp(double seconds) + { + m_NanoSeconds = (NPT_Int64)(seconds * 1e9); +diff --git a/lib/libUPnP/Neptune/Source/Core/NptTime.h b/lib/libUPnP/Neptune/Source/Core/NptTime.h +index ae02a48c3d..8b58eefac6 100644 +--- a/lib/libUPnP/Neptune/Source/Core/NptTime.h ++++ b/lib/libUPnP/Neptune/Source/Core/NptTime.h +@@ -54,6 +54,7 @@ class NPT_TimeStamp + NPT_TimeStamp(const NPT_TimeStamp& timestamp); + NPT_TimeStamp() : m_NanoSeconds(0) {} + NPT_TimeStamp(NPT_Int64 nanoseconds) : m_NanoSeconds(nanoseconds) {} ++ NPT_TimeStamp(float seconds); + NPT_TimeStamp(double seconds); + NPT_TimeStamp& operator+=(const NPT_TimeStamp& time_stamp); + NPT_TimeStamp& operator-=(const NPT_TimeStamp& time_stamp); +-- +2.44.0 + From 3ba82eb728cbc3a3e6726a2d9b71bda05add3e02 Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Sat, 11 May 2024 00:44:57 +0200 Subject: [PATCH 034/651] libUPnP: Remove deprecated volatile usage With the atomic_int, the mutex is no longer necessary as well. --- .../Source/System/Posix/NptPosixThreads.cpp | 13 +-- ...PnP-Remove-deprecated-volatile-usage.patch | 89 +++++++++++++++++++ 2 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 lib/libUPnP/patches/0052-libUPnP-Remove-deprecated-volatile-usage.patch diff --git a/lib/libUPnP/Neptune/Source/System/Posix/NptPosixThreads.cpp b/lib/libUPnP/Neptune/Source/System/Posix/NptPosixThreads.cpp index 510d3e9fbd06b..47ccd4824132c 100644 --- a/lib/libUPnP/Neptune/Source/System/Posix/NptPosixThreads.cpp +++ b/lib/libUPnP/Neptune/Source/System/Posix/NptPosixThreads.cpp @@ -13,6 +13,7 @@ #if defined(__SYMBIAN32__) #include #endif +#include #include #include #include @@ -278,8 +279,7 @@ class NPT_PosixAtomicVariable : public NPT_AtomicVariableInterface private: // members - volatile int m_Value; - pthread_mutex_t m_Mutex; + std::atomic_int m_Value; }; /*---------------------------------------------------------------------- @@ -288,7 +288,6 @@ class NPT_PosixAtomicVariable : public NPT_AtomicVariableInterface NPT_PosixAtomicVariable::NPT_PosixAtomicVariable(int value) : m_Value(value) { - pthread_mutex_init(&m_Mutex, NULL); } /*---------------------------------------------------------------------- @@ -296,7 +295,6 @@ NPT_PosixAtomicVariable::NPT_PosixAtomicVariable(int value) : +---------------------------------------------------------------------*/ NPT_PosixAtomicVariable::~NPT_PosixAtomicVariable() { - pthread_mutex_destroy(&m_Mutex); } /*---------------------------------------------------------------------- @@ -307,9 +305,7 @@ NPT_PosixAtomicVariable::Increment() { int value; - pthread_mutex_lock(&m_Mutex); value = ++m_Value; - pthread_mutex_unlock(&m_Mutex); return value; } @@ -322,9 +318,7 @@ NPT_PosixAtomicVariable::Decrement() { int value; - pthread_mutex_lock(&m_Mutex); value = --m_Value; - pthread_mutex_unlock(&m_Mutex); return value; } @@ -335,7 +329,6 @@ NPT_PosixAtomicVariable::Decrement() int NPT_PosixAtomicVariable::GetValue() { - // we assume that int read/write are atomic on the platform return m_Value; } @@ -345,9 +338,7 @@ NPT_PosixAtomicVariable::GetValue() void NPT_PosixAtomicVariable::SetValue(int value) { - pthread_mutex_lock(&m_Mutex); m_Value = value; - pthread_mutex_unlock(&m_Mutex); } /*---------------------------------------------------------------------- diff --git a/lib/libUPnP/patches/0052-libUPnP-Remove-deprecated-volatile-usage.patch b/lib/libUPnP/patches/0052-libUPnP-Remove-deprecated-volatile-usage.patch new file mode 100644 index 0000000000000..fc7b2bc56edc1 --- /dev/null +++ b/lib/libUPnP/patches/0052-libUPnP-Remove-deprecated-volatile-usage.patch @@ -0,0 +1,89 @@ +From 26b0002d2c181ca4e4b4ca5b253540b428273291 Mon Sep 17 00:00:00 2001 +From: Philipp Kerling +Date: Sat, 11 May 2024 00:44:57 +0200 +Subject: [PATCH 3/8] libUPnP: Remove deprecated volatile usage + +With the atomic_int, the mutex is no longer necessary as well. +--- + .../Neptune/Source/System/Posix/NptPosixThreads.cpp | 13 ++----------- + 1 file changed, 2 insertions(+), 11 deletions(-) + +diff --git a/lib/libUPnP/Neptune/Source/System/Posix/NptPosixThreads.cpp b/lib/libUPnP/Neptune/Source/System/Posix/NptPosixThreads.cpp +index 510d3e9fbd..47ccd48241 100644 +--- a/lib/libUPnP/Neptune/Source/System/Posix/NptPosixThreads.cpp ++++ b/lib/libUPnP/Neptune/Source/System/Posix/NptPosixThreads.cpp +@@ -13,6 +13,7 @@ + #if defined(__SYMBIAN32__) + #include + #endif ++#include + #include + #include + #include +@@ -278,8 +279,7 @@ class NPT_PosixAtomicVariable : public NPT_AtomicVariableInterface + + private: + // members +- volatile int m_Value; +- pthread_mutex_t m_Mutex; ++ std::atomic_int m_Value; + }; + + /*---------------------------------------------------------------------- +@@ -288,7 +288,6 @@ class NPT_PosixAtomicVariable : public NPT_AtomicVariableInterface + NPT_PosixAtomicVariable::NPT_PosixAtomicVariable(int value) : + m_Value(value) + { +- pthread_mutex_init(&m_Mutex, NULL); + } + + /*---------------------------------------------------------------------- +@@ -296,7 +295,6 @@ NPT_PosixAtomicVariable::NPT_PosixAtomicVariable(int value) : + +---------------------------------------------------------------------*/ + NPT_PosixAtomicVariable::~NPT_PosixAtomicVariable() + { +- pthread_mutex_destroy(&m_Mutex); + } + + /*---------------------------------------------------------------------- +@@ -307,9 +305,7 @@ NPT_PosixAtomicVariable::Increment() + { + int value; + +- pthread_mutex_lock(&m_Mutex); + value = ++m_Value; +- pthread_mutex_unlock(&m_Mutex); + + return value; + } +@@ -322,9 +318,7 @@ NPT_PosixAtomicVariable::Decrement() + { + int value; + +- pthread_mutex_lock(&m_Mutex); + value = --m_Value; +- pthread_mutex_unlock(&m_Mutex); + + return value; + } +@@ -335,7 +329,6 @@ NPT_PosixAtomicVariable::Decrement() + int + NPT_PosixAtomicVariable::GetValue() + { +- // we assume that int read/write are atomic on the platform + return m_Value; + } + +@@ -345,9 +338,7 @@ NPT_PosixAtomicVariable::GetValue() + void + NPT_PosixAtomicVariable::SetValue(int value) + { +- pthread_mutex_lock(&m_Mutex); + m_Value = value; +- pthread_mutex_unlock(&m_Mutex); + } + + /*---------------------------------------------------------------------- +-- +2.44.0 + From e0502f03fa45bfb741d6162df35e0d98a4d7829a Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Sat, 11 May 2024 00:45:18 +0200 Subject: [PATCH 035/651] libUPnP: Fix deprecated generation of copy assignment operator --- lib/libUPnP/Neptune/Source/Core/NptTime.h | 1 + ...ecated-generation-of-copy-assignment.patch | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 lib/libUPnP/patches/0053-libUPnP-Fix-deprecated-generation-of-copy-assignment.patch diff --git a/lib/libUPnP/Neptune/Source/Core/NptTime.h b/lib/libUPnP/Neptune/Source/Core/NptTime.h index 8b58eefac67ea..64b244e69a265 100644 --- a/lib/libUPnP/Neptune/Source/Core/NptTime.h +++ b/lib/libUPnP/Neptune/Source/Core/NptTime.h @@ -56,6 +56,7 @@ class NPT_TimeStamp NPT_TimeStamp(NPT_Int64 nanoseconds) : m_NanoSeconds(nanoseconds) {} NPT_TimeStamp(float seconds); NPT_TimeStamp(double seconds); + NPT_TimeStamp& operator=(const NPT_TimeStamp&) = default; NPT_TimeStamp& operator+=(const NPT_TimeStamp& time_stamp); NPT_TimeStamp& operator-=(const NPT_TimeStamp& time_stamp); bool operator==(const NPT_TimeStamp& t) const { return m_NanoSeconds == t.m_NanoSeconds; } diff --git a/lib/libUPnP/patches/0053-libUPnP-Fix-deprecated-generation-of-copy-assignment.patch b/lib/libUPnP/patches/0053-libUPnP-Fix-deprecated-generation-of-copy-assignment.patch new file mode 100644 index 0000000000000..752a773e02b85 --- /dev/null +++ b/lib/libUPnP/patches/0053-libUPnP-Fix-deprecated-generation-of-copy-assignment.patch @@ -0,0 +1,25 @@ +From 751f9dd094f01b5877af7c8278006aa84399a025 Mon Sep 17 00:00:00 2001 +From: Philipp Kerling +Date: Sat, 11 May 2024 00:45:18 +0200 +Subject: [PATCH 4/8] libUPnP: Fix deprecated generation of copy assignment + operator + +--- + lib/libUPnP/Neptune/Source/Core/NptTime.h | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/lib/libUPnP/Neptune/Source/Core/NptTime.h b/lib/libUPnP/Neptune/Source/Core/NptTime.h +index 8b58eefac6..64b244e69a 100644 +--- a/lib/libUPnP/Neptune/Source/Core/NptTime.h ++++ b/lib/libUPnP/Neptune/Source/Core/NptTime.h +@@ -56,6 +56,7 @@ class NPT_TimeStamp + NPT_TimeStamp(NPT_Int64 nanoseconds) : m_NanoSeconds(nanoseconds) {} + NPT_TimeStamp(float seconds); + NPT_TimeStamp(double seconds); ++ NPT_TimeStamp& operator=(const NPT_TimeStamp&) = default; + NPT_TimeStamp& operator+=(const NPT_TimeStamp& time_stamp); + NPT_TimeStamp& operator-=(const NPT_TimeStamp& time_stamp); + bool operator==(const NPT_TimeStamp& t) const { return m_NanoSeconds == t.m_NanoSeconds; } +-- +2.44.0 + From f1b7e63a917ddc577a0252f4176323252659a290 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Wed, 22 Dec 2021 10:48:35 -0800 Subject: [PATCH 036/651] [cmake] use c++20 --- cmake/scripts/common/CompilerSettings.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/scripts/common/CompilerSettings.cmake b/cmake/scripts/common/CompilerSettings.cmake index bb0af925ceb68..c4ea3231e1146 100644 --- a/cmake/scripts/common/CompilerSettings.cmake +++ b/cmake/scripts/common/CompilerSettings.cmake @@ -1,5 +1,5 @@ # Languages and global compiler settings -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_ASM_FLAGS "${CMAKE_C_FLAGS} -x assembler-with-cpp") From 9c347e7cdbea81e271c9ce80fdc9987c69cb5019 Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Sat, 30 Mar 2024 07:17:15 +0100 Subject: [PATCH 037/651] [depends] Incorporate p8-platform PR #46 --- cmake/modules/FindP8Platform.cmake | 3 ++- .../004-all-fix-cxx-standard.patch | 27 +++++++++++++++++++ tools/depends/target/p8-platform/Makefile | 4 ++- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 tools/depends/target/p8-platform/004-all-fix-cxx-standard.patch diff --git a/cmake/modules/FindP8Platform.cmake b/cmake/modules/FindP8Platform.cmake index e80fe9768cfbb..b0875357c4053 100644 --- a/cmake/modules/FindP8Platform.cmake +++ b/cmake/modules/FindP8Platform.cmake @@ -14,7 +14,8 @@ if(NOT P8Platform::P8Platform OR P8Platform_FIND_REQUIRED) macro(buildlibp8platform) set(patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-all-fix-c++17-support.patch" "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/002-all-fixcmakeinstall.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/003-all-cmake_tweakversion.patch") + "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/003-all-cmake_tweakversion.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/004-all-fix-cxx-standard.patch") generate_patchcommand("${patches}") diff --git a/tools/depends/target/p8-platform/004-all-fix-cxx-standard.patch b/tools/depends/target/p8-platform/004-all-fix-cxx-standard.patch new file mode 100644 index 0000000000000..0055d713f0172 --- /dev/null +++ b/tools/depends/target/p8-platform/004-all-fix-cxx-standard.patch @@ -0,0 +1,27 @@ +From ad9fcceb6267e737316b616551b7aac3cf676481 Mon Sep 17 00:00:00 2001 +From: Lukas Rusak +Date: Fri, 2 Oct 2020 15:31:36 -0700 +Subject: [PATCH] [cmake] require c++11 as the minimum standard + +--- + CMakeLists.txt | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 7144b0b..e9112f9 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -3,6 +3,10 @@ project(p8-platform) + cmake_minimum_required(VERSION 2.8.9) + enable_language(CXX) + ++set(CMAKE_CXX_STANDARD 11) ++set(CMAKE_CXX_STANDARD_REQUIRED YES) ++set(CMAKE_CXX_EXTENSIONS NO) ++ + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}) + + find_package(Threads REQUIRED) +-- +2.44.0 + diff --git a/tools/depends/target/p8-platform/Makefile b/tools/depends/target/p8-platform/Makefile index 4d3fd00cb4d7a..e590c191da09b 100644 --- a/tools/depends/target/p8-platform/Makefile +++ b/tools/depends/target/p8-platform/Makefile @@ -2,7 +2,8 @@ include ../../Makefile.include P8-PLATFORM-VERSION ../../download-files.include DEPS = ../../Makefile.include Makefile P8-PLATFORM-VERSION ../../download-files.include \ 001-all-fix-c++17-support.patch \ 002-all-fixcmakeinstall.patch \ - 003-all-cmake_tweakversion.patch + 003-all-cmake_tweakversion.patch \ + 004-all-fix-cxx-standard.patch LIBDYLIB=$(PLATFORM)/build/$(BYPRODUCT) @@ -15,6 +16,7 @@ $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) cd $(PLATFORM); patch -p1 -i ../001-all-fix-c++17-support.patch cd $(PLATFORM); patch -p1 -i ../002-all-fixcmakeinstall.patch cd $(PLATFORM); patch -p1 -i ../003-all-cmake_tweakversion.patch + cd $(PLATFORM); patch -p1 -i ../004-all-fix-cxx-standard.patch cd $(PLATFORM)/build; $(CMAKE) -DBUILD_SHARED_LIBS=0 .. $(LIBDYLIB): $(PLATFORM) From 4c698a7d8975ccda08cf3b9a69eba71a30ea121d Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Sun, 24 Mar 2024 23:27:19 +0100 Subject: [PATCH 038/651] [depends] use c++20 --- tools/depends/configure.ac | 3 +- tools/depends/m4/ax_cxx_compile_stdcxx.m4 | 89 ++++++++++++++++++++--- tools/depends/target/fmt/Makefile | 2 +- tools/depends/target/smctemp/Makefile | 1 + tools/depends/target/spdlog/Makefile | 1 - 5 files changed, 81 insertions(+), 15 deletions(-) diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac index b3469419ea259..80bca65150e54 100644 --- a/tools/depends/configure.ac +++ b/tools/depends/configure.ac @@ -670,9 +670,8 @@ fi CXXFLAGS="$platform_cxxflags $platform_includes" CXX_CACHED="$CXX" -AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory]) +AX_CXX_COMPILE_STDCXX([20], [noext], [mandatory]) CXX="$CXX_CACHED" -platform_cxxflags="${platform_cxxflags} -std=c++17" if test "$ffmpeg_options" = "default"; then ffmpeg_options="$ffmpeg_options_default" diff --git a/tools/depends/m4/ax_cxx_compile_stdcxx.m4 b/tools/depends/m4/ax_cxx_compile_stdcxx.m4 index 43087b2e6889e..9ba3713962cee 100644 --- a/tools/depends/m4/ax_cxx_compile_stdcxx.m4 +++ b/tools/depends/m4/ax_cxx_compile_stdcxx.m4 @@ -10,13 +10,13 @@ # # Check for baseline language coverage in the compiler for the specified # version of the C++ standard. If necessary, add switches to CXX and -# CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) -# or '14' (for the C++14 standard). +# CXXCPP to enable support. VERSION may be '11', '14', '17', or '20' for +# the respective C++ standard version. # # The second argument, if specified, indicates whether you insist on an # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. # -std=c++11). If neither is specified, you get whatever works, with -# preference for an extended mode. +# preference for no added switch, and then for an extended mode. # # The third argument, if specified 'mandatory' or if left unspecified, # indicates that baseline support for the specified C++ standard is @@ -35,13 +35,15 @@ # Copyright (c) 2015 Moritz Klammler # Copyright (c) 2016, 2018 Krzesimir Nowak # Copyright (c) 2019 Enji Cooper +# Copyright (c) 2020 Jason Merrill +# Copyright (c) 2021 Jörn Heusipp # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 11 +#serial 18 dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro dnl (serial version number 13). @@ -50,6 +52,7 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], [$1], [14], [ax_cxx_compile_alternatives="14 1y"], [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [$1], [20], [ax_cxx_compile_alternatives="20"], [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$2], [], [], [$2], [ext], [], @@ -62,6 +65,16 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl AC_LANG_PUSH([C++])dnl ac_success=no + m4_if([$2], [], [dnl + AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, + ax_cv_cxx_compile_cxx$1, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [ax_cv_cxx_compile_cxx$1=yes], + [ax_cv_cxx_compile_cxx$1=no])]) + if test x$ax_cv_cxx_compile_cxx$1 = xyes; then + ac_success=yes + fi]) + m4_if([$2], [noext], [], [dnl if test x$ac_success = xno; then for alternative in ${ax_cxx_compile_alternatives}; do @@ -91,9 +104,18 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl dnl HP's aCC needs +std=c++11 according to: dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf dnl Cray's crayCC needs "-h std=c++11" + dnl MSVC needs -std:c++NN for C++17 and later (default is C++14) for alternative in ${ax_cxx_compile_alternatives}; do - for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do - cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}" MSVC; do + if test x"$switch" = xMSVC; then + dnl AS_TR_SH maps both `:` and `=` to `_` so -std:c++17 would collide + dnl with -std=c++17. We suffix the cache variable name with _MSVC to + dnl avoid this. + switch=-std:c++${alternative} + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_${switch}_MSVC]) + else + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + fi AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, [ac_save_CXX="$CXX" @@ -140,7 +162,6 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 ) - dnl Test body for checking C++14 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], @@ -148,12 +169,24 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 ) +dnl Test body for checking C++17 support + m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 ) +dnl Test body for checking C++20 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_20], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_20 +) + + dnl Tests for new features in C++11 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ @@ -165,7 +198,11 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ #error "This is not a C++ compiler" -#elif __cplusplus < 201103L +// MSVC always sets __cplusplus to 199711L in older versions; newer versions +// only set it correctly if /Zc:__cplusplus is specified as well as a +// /std:c++NN switch: +// https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ +#elif __cplusplus < 201103L && !defined _MSC_VER #error "This is not a C++11 compiler" @@ -456,7 +493,7 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ #error "This is not a C++ compiler" -#elif __cplusplus < 201402L +#elif __cplusplus < 201402L && !defined _MSC_VER #error "This is not a C++14 compiler" @@ -580,7 +617,7 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ #error "This is not a C++ compiler" -#elif __cplusplus < 201703L +#elif __cplusplus < 201703L && !defined _MSC_VER #error "This is not a C++17 compiler" @@ -946,6 +983,36 @@ namespace cxx17 } // namespace cxx17 -#endif // __cplusplus < 201703L +#endif // __cplusplus < 201703L && !defined _MSC_VER + +]]) + + +dnl Tests for new features in C++20 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_20], [[ + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 202002L && !defined _MSC_VER + +#error "This is not a C++20 compiler" + +#else + +#include + +namespace cxx20 +{ + +// As C++20 supports feature test macros in the standard, there is no +// immediate need to actually test for feature availability on the +// Autoconf side. + +} // namespace cxx20 + +#endif // __cplusplus < 202002L && !defined _MSC_VER ]]) diff --git a/tools/depends/target/fmt/Makefile b/tools/depends/target/fmt/Makefile index 642d8c8583789..28d2ecc269136 100644 --- a/tools/depends/target/fmt/Makefile +++ b/tools/depends/target/fmt/Makefile @@ -3,7 +3,7 @@ include FMT-VERSION DEPS = Makefile FMT-VERSION ../../download-files.include \ 001-windows-pdb-symbol-gen.patch -CMAKE_OPTIONS=-DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_EXTENSIONS:BOOL=OFF -DFMT_DOC=OFF -DFMT_INSTALL=ON -DFMT_TEST=OFF +CMAKE_OPTIONS=-DFMT_DOC=OFF -DFMT_INSTALL=ON -DFMT_TEST=OFF ifeq ($(CROSS_COMPILING), yes) DEPS += ../../Makefile.include diff --git a/tools/depends/target/smctemp/Makefile b/tools/depends/target/smctemp/Makefile index b257625403b21..6ffbc0e789916 100644 --- a/tools/depends/target/smctemp/Makefile +++ b/tools/depends/target/smctemp/Makefile @@ -3,6 +3,7 @@ DEPS = ../../Makefile.include SMCTEMP-VERSION Makefile ../../download-files.inc LIBDYLIB=$(PLATFORM)/$(BYPRODUCT) +CXXFLAGS += -std=c++17 -framework IOKit ifeq ($(CPU), arm64) CXXFLAGS += -DARCH_TYPE_ARM64 else diff --git a/tools/depends/target/spdlog/Makefile b/tools/depends/target/spdlog/Makefile index 9e8b36d401a64..88b3e86377677 100644 --- a/tools/depends/target/spdlog/Makefile +++ b/tools/depends/target/spdlog/Makefile @@ -13,7 +13,6 @@ CMAKE_OPTIONS= \ ifeq ($(CROSS_COMPILING), yes) DEPS += ../../Makefile.include else - CXXFLAGS += -std=c++17 ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) ifeq ($(PLATFORM),) From 476973948f598cc752aa3ebd1686d0a89957a928 Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Fri, 29 Mar 2024 23:20:19 +0100 Subject: [PATCH 039/651] [depends] set default C++ standard for binaddons build --- tools/depends/target/Toolchain_binaddons.cmake.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/depends/target/Toolchain_binaddons.cmake.in b/tools/depends/target/Toolchain_binaddons.cmake.in index 25f35495778bd..ce147b228144d 100644 --- a/tools/depends/target/Toolchain_binaddons.cmake.in +++ b/tools/depends/target/Toolchain_binaddons.cmake.in @@ -88,6 +88,8 @@ set(CMAKE_C_FLAGS_DEBUG "@platform_cflags_debug@ @platform_includes@") set(CMAKE_CXX_FLAGS_DEBUG "@platform_cxxflags_debug@ @platform_includes@") set(CMAKE_CPP_FLAGS "@platform_cflags@ @platform_includes@") set(CMAKE_EXE_LINKER_FLAGS "@platform_ldflags@") +# set default C++ standard version - can be overridden +set(CMAKE_CXX_STANDARD 17) set(ENV{CFLAGS} ${CMAKE_C_FLAGS}) set(ENV{CXXFLAGS} ${CMAKE_CXX_FLAGS}) set(ENV{CPPFLAGS} ${CMAKE_CPP_FLAGS}) From 3e1829930cab3c8d12c4c508f7c20d6cac85bf94 Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Sun, 2 Jun 2024 14:59:11 +0200 Subject: [PATCH 040/651] Move ColorUtils, FontUtils, Moving Speed to KODI::UTILS Usage of namespaces is split for utilities is split between UTILS and KODI::UTILS at the moment. The aim here is to settle on one variant (KODI::UTILS), as otherwise lookup conflicts will occur when both namespaces are declared. --- .../DVDCodecs/Overlay/DVDOverlayCodecTX3G.cpp | 9 +- .../VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp | 24 +++-- .../DVDDemuxers/DVDDemuxFFmpeg.cpp | 2 +- .../DVDSubtitles/DVDSubtitleTagSami.cpp | 6 +- .../DVDSubtitles/DVDSubtitlesLibass.cpp | 21 ++-- .../DVDSubtitles/webvtt/WebVTTHandler.cpp | 9 +- .../DVDSubtitles/webvtt/WebVTTHandler.h | 2 +- xbmc/dialogs/GUIDialogColorPicker.cpp | 2 +- xbmc/guilib/D3DResource.cpp | 8 +- xbmc/guilib/D3DResource.h | 8 +- xbmc/guilib/GUIButtonControl.cpp | 8 +- xbmc/guilib/GUIButtonControl.h | 8 +- xbmc/guilib/GUIColorButtonControl.cpp | 8 +- xbmc/guilib/GUIColorManager.cpp | 12 +-- xbmc/guilib/GUIColorManager.h | 11 +- xbmc/guilib/GUIControl.cpp | 4 +- xbmc/guilib/GUIControl.h | 4 +- xbmc/guilib/GUIControlFactory.cpp | 10 +- xbmc/guilib/GUIControlFactory.h | 6 +- xbmc/guilib/GUIEditControl.cpp | 4 +- xbmc/guilib/GUIFont.cpp | 20 ++-- xbmc/guilib/GUIFont.h | 22 ++-- xbmc/guilib/GUIFontCache.cpp | 35 +++--- xbmc/guilib/GUIFontCache.h | 8 +- xbmc/guilib/GUIFontManager.cpp | 30 +++--- xbmc/guilib/GUIFontManager.h | 4 +- xbmc/guilib/GUIFontTTF.cpp | 6 +- xbmc/guilib/GUIFontTTF.h | 6 +- xbmc/guilib/GUILabel.cpp | 7 +- xbmc/guilib/GUILabel.h | 4 +- xbmc/guilib/GUILabelControl.cpp | 4 +- xbmc/guilib/GUIMoverControl.cpp | 2 +- xbmc/guilib/GUIMoverControl.h | 4 +- xbmc/guilib/GUIRSSControl.cpp | 2 +- xbmc/guilib/GUIResizeControl.cpp | 2 +- xbmc/guilib/GUIResizeControl.h | 4 +- xbmc/guilib/GUITextLayout.cpp | 37 ++++--- xbmc/guilib/GUITextLayout.h | 29 +++-- xbmc/guilib/GUITexture.cpp | 8 +- xbmc/guilib/GUITexture.h | 10 +- xbmc/guilib/GUITextureD3D.cpp | 4 +- xbmc/guilib/GUITextureD3D.h | 6 +- xbmc/guilib/GUITextureGL.cpp | 4 +- xbmc/guilib/GUITextureGL.h | 4 +- xbmc/guilib/GUITextureGLES.cpp | 4 +- xbmc/guilib/GUITextureGLES.h | 4 +- xbmc/guilib/GUIVideoControl.cpp | 2 +- xbmc/guilib/GUIWindow.cpp | 2 +- xbmc/guilib/VisibleEffect.cpp | 14 +-- xbmc/guilib/VisibleEffect.h | 8 +- xbmc/guilib/guiinfo/GUIInfoColor.cpp | 2 +- xbmc/guilib/guiinfo/GUIInfoColor.h | 6 +- xbmc/interfaces/legacy/Control.h | 36 +++---- xbmc/pictures/SlideShowPicture.cpp | 33 +++--- xbmc/pictures/SlideShowPicture.h | 4 +- xbmc/pictures/SlideShowPictureDX.cpp | 5 +- xbmc/pictures/SlideShowPictureDX.h | 2 +- xbmc/pictures/SlideShowPictureGL.cpp | 4 +- xbmc/pictures/SlideShowPictureGL.h | 2 +- xbmc/pictures/SlideShowPictureGLES.cpp | 5 +- xbmc/pictures/SlideShowPictureGLES.h | 2 +- xbmc/rendering/RenderSystem.h | 2 +- xbmc/rendering/dx/RenderSystemDX.cpp | 2 +- xbmc/rendering/dx/RenderSystemDX.h | 2 +- xbmc/rendering/gl/RenderSystemGL.cpp | 2 +- xbmc/rendering/gl/RenderSystemGL.h | 2 +- xbmc/rendering/gles/RenderSystemGLES.cpp | 2 +- xbmc/rendering/gles/RenderSystemGLES.h | 2 +- xbmc/settings/SettingConditions.cpp | 2 +- xbmc/utils/ColorUtils.cpp | 29 ++--- xbmc/utils/ColorUtils.h | 7 +- xbmc/utils/FontUtils.cpp | 32 +++--- xbmc/utils/FontUtils.h | 7 +- xbmc/utils/MovingSpeed.cpp | 25 +++-- xbmc/utils/MovingSpeed.h | 7 +- xbmc/utils/TransformMatrix.h | 19 ++-- xbmc/utils/guilib/GUIContentUtils.cpp | 2 +- xbmc/utils/guilib/GUIContentUtils.h | 7 +- xbmc/video/PlayerController.h | 2 +- xbmc/video/Teletext.cpp | 100 ++++++++---------- xbmc/video/Teletext.h | 65 +++++++----- xbmc/video/dialogs/GUIDialogTeletext.cpp | 4 +- xbmc/windowing/GraphicContext.cpp | 10 +- xbmc/windowing/GraphicContext.h | 6 +- xbmc/windows/GUIWindowScreensaverDim.cpp | 3 +- 85 files changed, 459 insertions(+), 445 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecTX3G.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecTX3G.cpp index df66971c05f67..91f1c9d691ba0 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecTX3G.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Overlay/DVDOverlayCodecTX3G.cpp @@ -41,7 +41,7 @@ struct StyleRecord uint16_t fontID; uint8_t faceStyleFlags; // FaceStyleFlag uint8_t fontSize; - UTILS::COLOR::Color textColorARGB; + KODI::UTILS::COLOR::Color textColorARGB; unsigned int textColorAlphaCh; }; @@ -56,13 +56,13 @@ void ConvertStyleToTags(std::string& strUTF8, const StyleRecord& style, bool clo strUTF8.append(closingTags ? "{\\i0}" : "{\\i1}"); if (style.faceStyleFlags & UNDERLINE) strUTF8.append(closingTags ? "{\\u0}" : "{\\u1}"); - if (style.textColorARGB != UTILS::COLOR::WHITE) + if (style.textColorARGB != KODI::UTILS::COLOR::WHITE) { if (closingTags) strUTF8 += "{\\c}"; else { - UTILS::COLOR::Color color = UTILS::COLOR::ConvertToBGR(style.textColorARGB); + KODI::UTILS::COLOR::Color color = KODI::UTILS::COLOR::ConvertToBGR(style.textColorARGB); strUTF8 += StringUtils::Format("{{\\c&H{:06x}&}}", color); } } @@ -171,7 +171,8 @@ OverlayMessage CDVDOverlayCodecTX3G::Decode(DemuxPacket* pPacket) styleRec.fontID = sampleData.ReadNextUnsignedShort(); styleRec.faceStyleFlags = sampleData.ReadNextUnsignedChar(); styleRec.fontSize = sampleData.ReadNextUnsignedChar(); - styleRec.textColorARGB = UTILS::COLOR::ConvertToARGB(sampleData.ReadNextUnsignedInt()); + styleRec.textColorARGB = + KODI::UTILS::COLOR::ConvertToARGB(sampleData.ReadNextUnsignedInt()); styleRec.textColorAlphaCh = (styleRec.textColorARGB & 0xFF000000) >> 24; // clamp bgnChar/bgnChar to textLength, we alloc enough space above and // this fixes broken encoders that do not handle endChar correctly. diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp index 6f539a1880628..d56086bf14e93 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp @@ -16,6 +16,8 @@ #include +namespace COLOR = KODI::UTILS::COLOR; + namespace { /*! @@ -35,34 +37,34 @@ enum class ColorFormat * \param[in] format - the color format * \return the corresponding Color in rgb */ -constexpr UTILS::COLOR::Color CCColorConversion(const uint8_t ccColor, ColorFormat format) +constexpr COLOR::Color CCColorConversion(const uint8_t ccColor, ColorFormat format) { - UTILS::COLOR::Color color = UTILS::COLOR::NONE; + COLOR::Color color = COLOR::NONE; switch (ccColor) { case WHITE: - color = UTILS::COLOR::WHITE; + color = COLOR::WHITE; break; case GREEN: - color = UTILS::COLOR::GREEN; + color = COLOR::GREEN; break; case BLUE: - color = UTILS::COLOR::BLUE; + color = COLOR::BLUE; break; case CYAN: - color = UTILS::COLOR::CYAN; + color = COLOR::CYAN; break; case RED: - color = UTILS::COLOR::RED; + color = COLOR::RED; break; case YELLOW: - color = UTILS::COLOR::YELLOW; + color = COLOR::YELLOW; break; case MAGENTA: - color = UTILS::COLOR::MAGENTA; + color = COLOR::MAGENTA; break; case BLACK: - color = UTILS::COLOR::BLACK; + color = COLOR::BLACK; break; default: break; @@ -94,7 +96,7 @@ void ApplyStyleModifiers(std::string& ccText, const cc_attribute_t& ccAttributes { ccText = StringUtils::Format( "{}", - UTILS::COLOR::ConvertToHexRGB(CCColorConversion(ccAttributes.foreground, ColorFormat::RGB)), + COLOR::ConvertToHexRGB(CCColorConversion(ccAttributes.foreground, ColorFormat::RGB)), ccText); } } diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index 1aebc3dcbc6c0..d610993bdca23 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -1804,7 +1804,7 @@ CDemuxStream* CDVDDemuxFFmpeg::AddStream(int streamIdx) //! @todo: temporary font file management should be completely //! removed, by sending font data to the subtitle renderer and //! using libass ass_add_font to add the fonts directly in memory. - std::string filePath{UTILS::FONT::FONTPATH::TEMP}; + std::string filePath{KODI::UTILS::FONT::FONTPATH::TEMP}; XFILE::CDirectory::Create(filePath); AVDictionaryEntry* nameTag = av_dict_get(pStream->metadata, "filename", NULL, 0); diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.cpp index c1d6dee693f3d..8d8dd4b1da840 100644 --- a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.cpp +++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.cpp @@ -30,9 +30,9 @@ std::string TranslateColorValue(std::string value) //! @todo: is needed to implement a common way to get color resources //! in order to find the color name on CSS colors list StringUtils::ToLower(value); - const auto itHtmlColor = UTILS::COLOR::HTML_BASIC_COLORS.find(value); - if (itHtmlColor != UTILS::COLOR::HTML_BASIC_COLORS.cend()) - return UTILS::COLOR::ConvertToHexRGB(itHtmlColor->second); + const auto itHtmlColor = KODI::UTILS::COLOR::HTML_BASIC_COLORS.find(value); + if (itHtmlColor != KODI::UTILS::COLOR::HTML_BASIC_COLORS.cend()) + return KODI::UTILS::COLOR::ConvertToHexRGB(itHtmlColor->second); // Try validate hex color value if (value.size() == 6) diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp index a8a5e0e35f351..1858a4aa2e7e5 100644 --- a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp +++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp @@ -28,7 +28,7 @@ #include using namespace KODI::SUBTITLES::STYLE; -using namespace UTILS; +using namespace KODI::UTILS; namespace { @@ -96,24 +96,22 @@ void CDVDSubtitlesLibass::Configure() // platforms (e.g. linux/windows), on some other systems like android the // font provider is currenlty not supported, then an user can add his // additionals fonts only by using the user fonts folder. - ass_set_fonts_dir(m_library, - CSpecialProtocol::TranslatePath(UTILS::FONT::FONTPATH::USER).c_str()); + ass_set_fonts_dir(m_library, CSpecialProtocol::TranslatePath(FONT::FONTPATH::USER).c_str()); // Load additional fonts into Libass memory CFileItemList items; // Get fonts from system directory - if (XFILE::CDirectory::Exists(UTILS::FONT::FONTPATH::SYSTEM)) + if (XFILE::CDirectory::Exists(FONT::FONTPATH::SYSTEM)) { - XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::SYSTEM, items, - UTILS::FONT::SUPPORTED_EXTENSIONS_MASK, + XFILE::CDirectory::GetDirectory(FONT::FONTPATH::SYSTEM, items, FONT::SUPPORTED_EXTENSIONS_MASK, XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO); } // Get temporary fonts - if (XFILE::CDirectory::Exists(UTILS::FONT::FONTPATH::TEMP, false)) + if (XFILE::CDirectory::Exists(FONT::FONTPATH::TEMP, false)) { - XFILE::CDirectory::GetDirectory( - UTILS::FONT::FONTPATH::TEMP, items, UTILS::FONT::SUPPORTED_EXTENSIONS_MASK, - XFILE::DIR_FLAG_BYPASS_CACHE | XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO); + XFILE::CDirectory::GetDirectory(FONT::FONTPATH::TEMP, items, FONT::SUPPORTED_EXTENSIONS_MASK, + XFILE::DIR_FLAG_BYPASS_CACHE | XFILE::DIR_FLAG_NO_FILE_DIRS | + XFILE::DIR_FLAG_NO_FILE_INFO); } for (const auto& item : items) { @@ -146,8 +144,7 @@ void CDVDSubtitlesLibass::Configure() FONT::FONT_DEFAULT_FILENAME); } - ass_set_fonts(m_renderer, - UTILS::FONT::FONTPATH::GetSystemFontPath(FONT::FONT_DEFAULT_FILENAME).c_str(), + ass_set_fonts(m_renderer, FONT::FONTPATH::GetSystemFontPath(FONT::FONT_DEFAULT_FILENAME).c_str(), m_defaultFontFamilyName.c_str(), ASS_FONTPROVIDER_AUTODETECT, nullptr, 1); // Extract font must be set before loading ASS/SSA data, diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.cpp index df329a11a7760..0622b93f77e07 100644 --- a/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.cpp +++ b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.cpp @@ -480,12 +480,11 @@ void CWebVTTHandler::DecodeLine(std::string line, std::vector* sub auto colorInfo = std::find_if(m_CSSColors.begin(), m_CSSColors.end(), - [&](const std::pair& item) { - return StringUtils::CompareNoCase(item.first, colorName) == 0; - }); + [&](const std::pair& item) + { return StringUtils::CompareNoCase(item.first, colorName) == 0; }); if (colorInfo != m_CSSColors.end()) { - const uint32_t color = UTILS::COLOR::ConvertToBGR(colorInfo->second.colorARGB); + const uint32_t color = KODI::UTILS::COLOR::ConvertToBGR(colorInfo->second.colorARGB); m_feedCssStyle.m_color = StringUtils::Format("{:6x}", color); } } @@ -493,7 +492,7 @@ void CWebVTTHandler::DecodeLine(std::string line, std::vector* sub if (!colorRGB.empty()) // From CSS Color numeric R,G,B values { const auto intValues = StringUtils::Split(colorRGB, ","); - uint32_t color = UTILS::COLOR::ConvertIntToRGB( + uint32_t color = KODI::UTILS::COLOR::ConvertIntToRGB( std::stoi(intValues[2]), std::stoi(intValues[1]), std::stoi(intValues[0])); m_feedCssStyle.m_color = StringUtils::Format("{:6x}", color); } diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.h b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.h index cad56e26565e4..fa28e6ccc7880 100644 --- a/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.h +++ b/xbmc/cores/VideoPlayer/DVDSubtitles/webvtt/WebVTTHandler.h @@ -231,6 +231,6 @@ class CWebVTTHandler std::map> m_cssSelectors; bool m_CSSColorsLoaded{false}; - std::vector> m_CSSColors; + std::vector> m_CSSColors; double m_offset{0.0}; }; diff --git a/xbmc/dialogs/GUIDialogColorPicker.cpp b/xbmc/dialogs/GUIDialogColorPicker.cpp index 58814de392bbb..cbbe1102d423d 100644 --- a/xbmc/dialogs/GUIDialogColorPicker.cpp +++ b/xbmc/dialogs/GUIDialogColorPicker.cpp @@ -163,7 +163,7 @@ void CGUIDialogColorPicker::LoadColors() void CGUIDialogColorPicker::LoadColors(const std::string& filePath) { CGUIColorManager colorManager; - std::vector> colors; + std::vector> colors; if (colorManager.LoadColorsListFromXML(filePath, colors, true)) { for (auto& color : colors) diff --git a/xbmc/guilib/D3DResource.cpp b/xbmc/guilib/D3DResource.cpp index cd35a0fbdbb43..cd6e0d2a86b1b 100644 --- a/xbmc/guilib/D3DResource.cpp +++ b/xbmc/guilib/D3DResource.cpp @@ -494,7 +494,7 @@ void CD3DTexture::GenerateMipmaps() // static methods void CD3DTexture::DrawQuad(const CPoint points[4], - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, CD3DTexture* texture, const CRect* texCoords, SHADER_METHOD options) @@ -512,7 +512,7 @@ void CD3DTexture::DrawQuad(const CPoint points[4], } void CD3DTexture::DrawQuad(const CRect& rect, - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, CD3DTexture* texture, const CRect* texCoords, SHADER_METHOD options) @@ -528,7 +528,7 @@ void CD3DTexture::DrawQuad(const CRect& rect, } void CD3DTexture::DrawQuad(const CPoint points[4], - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, unsigned numViews, ID3D11ShaderResourceView** view, const CRect* texCoords, @@ -554,7 +554,7 @@ void CD3DTexture::DrawQuad(const CPoint points[4], } void CD3DTexture::DrawQuad(const CRect& rect, - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, unsigned numViews, ID3D11ShaderResourceView** view, const CRect* texCoords, diff --git a/xbmc/guilib/D3DResource.h b/xbmc/guilib/D3DResource.h index e7f6517796ec6..ce873f5fd58b1 100644 --- a/xbmc/guilib/D3DResource.h +++ b/xbmc/guilib/D3DResource.h @@ -113,26 +113,26 @@ class CD3DTexture : public ID3DResource // static methods static void DrawQuad(const CPoint points[4], - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, CD3DTexture* texture, const CRect* texCoords, SHADER_METHOD options = SHADER_METHOD_RENDER_TEXTURE_BLEND); static void DrawQuad(const CPoint points[4], - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, unsigned numViews, ID3D11ShaderResourceView** view, const CRect* texCoords, SHADER_METHOD options = SHADER_METHOD_RENDER_TEXTURE_BLEND); static void DrawQuad(const CRect& coords, - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, CD3DTexture* texture, const CRect* texCoords, SHADER_METHOD options = SHADER_METHOD_RENDER_TEXTURE_BLEND); static void DrawQuad(const CRect& coords, - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, unsigned numViews, ID3D11ShaderResourceView** view, const CRect* texCoords, diff --git a/xbmc/guilib/GUIButtonControl.cpp b/xbmc/guilib/GUIButtonControl.cpp index 22ded687b717d..bfa8da27b8051 100644 --- a/xbmc/guilib/GUIButtonControl.cpp +++ b/xbmc/guilib/GUIButtonControl.cpp @@ -375,9 +375,9 @@ std::string CGUIButtonControl::GetLabel2() const void CGUIButtonControl::PythonSetLabel(const std::string& strFont, const std::string& strText, - UTILS::COLOR::Color textColor, - UTILS::COLOR::Color shadowColor, - UTILS::COLOR::Color focusedColor) + KODI::UTILS::COLOR::Color textColor, + KODI::UTILS::COLOR::Color shadowColor, + KODI::UTILS::COLOR::Color focusedColor) { m_label.GetLabelInfo().font = g_fontManager.GetFont(strFont); m_label.GetLabelInfo().textColor = textColor; @@ -386,7 +386,7 @@ void CGUIButtonControl::PythonSetLabel(const std::string& strFont, SetLabel(strText); } -void CGUIButtonControl::PythonSetDisabledColor(UTILS::COLOR::Color disabledColor) +void CGUIButtonControl::PythonSetDisabledColor(KODI::UTILS::COLOR::Color disabledColor) { m_label.GetLabelInfo().disabledColor = disabledColor; } diff --git a/xbmc/guilib/GUIButtonControl.h b/xbmc/guilib/GUIButtonControl.h index 6436739d854a5..c5fe2c1df1c8b 100644 --- a/xbmc/guilib/GUIButtonControl.h +++ b/xbmc/guilib/GUIButtonControl.h @@ -63,10 +63,10 @@ class CGUIButtonControl : public CGUIControl void PythonSetLabel(const std::string& strFont, const std::string& strText, - UTILS::COLOR::Color textColor, - UTILS::COLOR::Color shadowColor, - UTILS::COLOR::Color focusedColor); - void PythonSetDisabledColor(UTILS::COLOR::Color disabledColor); + KODI::UTILS::COLOR::Color textColor, + KODI::UTILS::COLOR::Color shadowColor, + KODI::UTILS::COLOR::Color focusedColor); + void PythonSetDisabledColor(KODI::UTILS::COLOR::Color disabledColor); virtual void OnClick(); bool HasClickActions() const { return m_clickActions.HasActionsMeetingCondition(); } diff --git a/xbmc/guilib/GUIColorButtonControl.cpp b/xbmc/guilib/GUIColorButtonControl.cpp index 1e40667cfcb18..648badc0a4ac8 100644 --- a/xbmc/guilib/GUIColorButtonControl.cpp +++ b/xbmc/guilib/GUIColorButtonControl.cpp @@ -38,7 +38,7 @@ CGUIColorButtonControl::CGUIColorButtonControl(int parentID, m_colorPosY = 0; m_imgColorMask->SetAspectRatio(CAspectRatio::AR_KEEP); m_imgColorDisabledMask->SetAspectRatio(CAspectRatio::AR_KEEP); - m_imgBoxColor = GUIINFO::CGUIInfoColor(UTILS::COLOR::NONE); + m_imgBoxColor = GUIINFO::CGUIInfoColor(KODI::UTILS::COLOR::NONE); ControlType = GUICONTROL_COLORBUTTON; // offsetX is like a left/right padding, "hex" label does not require high values m_labelInfo.GetLabelInfo().offsetX = 2; @@ -165,9 +165,9 @@ void CGUIColorButtonControl::SetImageBoxColor(GUIINFO::CGUIInfoColor color) void CGUIColorButtonControl::SetImageBoxColor(const std::string& hexColor) { if (hexColor.empty()) - m_imgBoxColor = GUIINFO::CGUIInfoColor(UTILS::COLOR::NONE); + m_imgBoxColor = GUIINFO::CGUIInfoColor(KODI::UTILS::COLOR::NONE); else - m_imgBoxColor = GUIINFO::CGUIInfoColor(UTILS::COLOR::ConvertHexToColor(hexColor)); + m_imgBoxColor = GUIINFO::CGUIInfoColor(KODI::UTILS::COLOR::ConvertHexToColor(hexColor)); } bool CGUIColorButtonControl::UpdateColors(const CGUIListItem* item) @@ -189,7 +189,7 @@ void CGUIColorButtonControl::ProcessInfoText(unsigned int currentTime) { CRect labelRenderRect = m_labelInfo.GetRenderRect(); bool changed = m_labelInfo.SetText( - StringUtils::Format("#{:08X}", static_cast(m_imgBoxColor))); + StringUtils::Format("#{:08X}", static_cast(m_imgBoxColor))); // Set Label X position based on image mask control position float textWidth = m_labelInfo.GetTextWidth() + 2 * m_labelInfo.GetLabelInfo().offsetX; float textPosX = m_imgColorMask->GetXPosition() - textWidth; diff --git a/xbmc/guilib/GUIColorManager.cpp b/xbmc/guilib/GUIColorManager.cpp index 0034fedce007c..178193f24be0f 100644 --- a/xbmc/guilib/GUIColorManager.cpp +++ b/xbmc/guilib/GUIColorManager.cpp @@ -74,7 +74,7 @@ bool CGUIColorManager::LoadXML(CXBMCTinyXML &xmlDoc) { if (color->FirstChild() && color->Attribute("name")) { - UTILS::COLOR::Color value = 0xffffffff; + KODI::UTILS::COLOR::Color value = 0xffffffff; sscanf(color->FirstChild()->Value(), "%x", (unsigned int*) &value); std::string name = color->Attribute("name"); const auto it = m_colors.find(name); @@ -89,7 +89,7 @@ bool CGUIColorManager::LoadXML(CXBMCTinyXML &xmlDoc) } // lookup a color and return it's hex value -UTILS::COLOR::Color CGUIColorManager::GetColor(const std::string& color) const +KODI::UTILS::COLOR::Color CGUIColorManager::GetColor(const std::string& color) const { // look in our color map std::string trimmed(color); @@ -99,14 +99,14 @@ UTILS::COLOR::Color CGUIColorManager::GetColor(const std::string& color) const return (*it).second; // try converting hex directly - UTILS::COLOR::Color value = 0; + KODI::UTILS::COLOR::Color value = 0; sscanf(trimmed.c_str(), "%x", &value); return value; } bool CGUIColorManager::LoadColorsListFromXML( const std::string& filePath, - std::vector>& colors, + std::vector>& colors, bool sortColors) { CLog::Log(LOGDEBUG, "Loading colors from file {}", filePath); @@ -131,13 +131,13 @@ bool CGUIColorManager::LoadColorsListFromXML( if (xmlColor->FirstChild() && xmlColor->Attribute("name")) { colors.emplace_back(xmlColor->Attribute("name"), - UTILS::COLOR::MakeColorInfo(xmlColor->FirstChild()->Value())); + KODI::UTILS::COLOR::MakeColorInfo(xmlColor->FirstChild()->Value())); } xmlColor = xmlColor->NextSiblingElement("color"); } if (sortColors) - std::sort(colors.begin(), colors.end(), UTILS::COLOR::comparePairColorInfo); + std::sort(colors.begin(), colors.end(), KODI::UTILS::COLOR::comparePairColorInfo); return true; } diff --git a/xbmc/guilib/GUIColorManager.h b/xbmc/guilib/GUIColorManager.h index ac33c6d47aa49..c0346fce3eee1 100644 --- a/xbmc/guilib/GUIColorManager.h +++ b/xbmc/guilib/GUIColorManager.h @@ -35,7 +35,7 @@ class CGUIColorManager void Load(const std::string &colorFile); - UTILS::COLOR::Color GetColor(const std::string& color) const; + KODI::UTILS::COLOR::Color GetColor(const std::string& color) const; void Clear(); @@ -45,12 +45,13 @@ class CGUIColorManager \param sortColors if true the colors will be sorted in a hue scale \return true if success, otherwise false */ - bool LoadColorsListFromXML(const std::string& filePath, - std::vector>& colors, - bool sortColors); + bool LoadColorsListFromXML( + const std::string& filePath, + std::vector>& colors, + bool sortColors); protected: bool LoadXML(CXBMCTinyXML& xmlDoc); - std::map m_colors; + std::map m_colors; }; diff --git a/xbmc/guilib/GUIControl.cpp b/xbmc/guilib/GUIControl.cpp index 7ae97281202b5..624df97279694 100644 --- a/xbmc/guilib/GUIControl.cpp +++ b/xbmc/guilib/GUIControl.cpp @@ -202,7 +202,7 @@ void CGUIControl::DoRender() if (m_hitColor != 0xffffffff) { - UTILS::COLOR::Color color = + KODI::UTILS::COLOR::Color color = CServiceBroker::GetWinSystem()->GetGfxContext().MergeAlpha(m_hitColor); CGUITexture::DrawQuad(CServiceBroker::GetWinSystem()->GetGfxContext().GenerateAABB(m_hitRect), color); } @@ -981,7 +981,7 @@ bool CGUIControl::IsControlRenderable() } } -void CGUIControl::SetHitRect(const CRect& rect, const UTILS::COLOR::Color& color) +void CGUIControl::SetHitRect(const CRect& rect, const KODI::UTILS::COLOR::Color& color) { m_hitRect = rect; m_hitColor = color; diff --git a/xbmc/guilib/GUIControl.h b/xbmc/guilib/GUIControl.h index 93b5dcf199530..230e25f0c76f2 100644 --- a/xbmc/guilib/GUIControl.h +++ b/xbmc/guilib/GUIControl.h @@ -175,7 +175,7 @@ class CGUIControl bool IsVisibleFromSkin() const { return m_visibleFromSkinCondition; } virtual bool IsDisabled() const; virtual void SetPosition(float posX, float posY); - virtual void SetHitRect(const CRect& rect, const UTILS::COLOR::Color& color); + virtual void SetHitRect(const CRect& rect, const KODI::UTILS::COLOR::Color& color); virtual void SetCamera(const CPoint &camera); virtual void SetStereoFactor(const float &factor); bool SetColorDiffuse(const KODI::GUILIB::GUIINFO::CGUIInfoColor &color); @@ -354,7 +354,7 @@ class CGUIControl float m_height; float m_width; CRect m_hitRect; - UTILS::COLOR::Color m_hitColor = 0xffffffff; + KODI::UTILS::COLOR::Color m_hitColor = 0xffffffff; KODI::GUILIB::GUIINFO::CGUIInfoColor m_diffuseColor; int m_controlID; int m_parentID; diff --git a/xbmc/guilib/GUIControlFactory.cpp b/xbmc/guilib/GUIControlFactory.cpp index 798bcf056154a..987011a9ee081 100644 --- a/xbmc/guilib/GUIControlFactory.cpp +++ b/xbmc/guilib/GUIControlFactory.cpp @@ -611,7 +611,7 @@ bool CGUIControlFactory::GetScroller(const TiXmlNode* control, bool CGUIControlFactory::GetColor(const TiXmlNode* control, const char* strTag, - UTILS::COLOR::Color& value) + KODI::UTILS::COLOR::Color& value) { const TiXmlElement* node = control->FirstChildElement(strTag); if (node && node->FirstChild()) @@ -745,7 +745,7 @@ std::string CGUIControlFactory::GetType(const TiXmlElement* pControlNode) bool CGUIControlFactory::GetMovingSpeedConfig(const TiXmlNode* pRootNode, const char* strTag, - UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg) + KODI::UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg) { const TiXmlElement* msNode = pRootNode->FirstChildElement(strTag); if (!msNode) @@ -780,8 +780,8 @@ bool CGUIControlFactory::GetMovingSpeedConfig(const TiXmlNode* pRootNode, const char* deltaStr{configElement->Attribute("delta")}; float delta = deltaStr ? StringUtils::ToFloat(deltaStr) : globalDelta; - UTILS::MOVING_SPEED::EventCfg eventCfg{acceleration, maxVelocity, resetTimeout, delta}; - movingSpeedCfg.emplace(UTILS::MOVING_SPEED::ParseEventType(eventType), eventCfg); + KODI::UTILS::MOVING_SPEED::EventCfg eventCfg{acceleration, maxVelocity, resetTimeout, delta}; + movingSpeedCfg.emplace(KODI::UTILS::MOVING_SPEED::ParseEventType(eventType), eventCfg); } return true; } @@ -914,7 +914,7 @@ CGUIControl* CGUIControlFactory::Create(int parentID, bool bPassword = false; std::string visibleCondition; - UTILS::MOVING_SPEED::MapEventConfig movingSpeedCfg; + KODI::UTILS::MOVING_SPEED::MapEventConfig movingSpeedCfg; ///////////////////////////////////////////////////////////////////////////// // Read control properties from XML diff --git a/xbmc/guilib/GUIControlFactory.h b/xbmc/guilib/GUIControlFactory.h index 708a147e80331..6fba3de3b3270 100644 --- a/xbmc/guilib/GUIControlFactory.h +++ b/xbmc/guilib/GUIControlFactory.h @@ -113,7 +113,9 @@ class CGUIControlFactory const std::string& labelTag, std::vector& infoLabels, int parentID); - static bool GetColor(const TiXmlNode* pRootNode, const char* strTag, UTILS::COLOR::Color& value); + static bool GetColor(const TiXmlNode* pRootNode, + const char* strTag, + KODI::UTILS::COLOR::Color& value); static bool GetInfoColor(const TiXmlNode* pRootNode, const char* strTag, KODI::GUILIB::GUIINFO::CGUIInfoColor& value, @@ -131,7 +133,7 @@ class CGUIControlFactory static std::string GetType(const TiXmlElement* pControlNode); static bool GetMovingSpeedConfig(const TiXmlNode* pRootNode, const char* strTag, - UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg); + KODI::UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg); static bool GetConditionalVisibility(const TiXmlNode* control, std::string& condition, std::string& allowHiddenFocus); diff --git a/xbmc/guilib/GUIEditControl.cpp b/xbmc/guilib/GUIEditControl.cpp index adf88357472c0..ba69f9bdecbdc 100644 --- a/xbmc/guilib/GUIEditControl.cpp +++ b/xbmc/guilib/GUIEditControl.cpp @@ -599,10 +599,10 @@ bool CGUIEditControl::SetStyledText(const std::wstring &text) vecText styled; styled.reserve(text.size() + 1); - std::vector colors; + std::vector colors; colors.push_back(m_label.GetLabelInfo().textColor); colors.push_back(m_label.GetLabelInfo().disabledColor); - UTILS::COLOR::Color select = m_label.GetLabelInfo().selectedColor; + KODI::UTILS::COLOR::Color select = m_label.GetLabelInfo().selectedColor; if (!select) select = 0xFFFF0000; colors.push_back(select); diff --git a/xbmc/guilib/GUIFont.cpp b/xbmc/guilib/GUIFont.cpp index bf0c014a0aac4..2c1bcc9ecfba3 100644 --- a/xbmc/guilib/GUIFont.cpp +++ b/xbmc/guilib/GUIFont.cpp @@ -56,8 +56,8 @@ float CScrollInfo::GetPixelsPerFrame() CGUIFont::CGUIFont(const std::string& strFontName, uint32_t style, - UTILS::COLOR::Color textColor, - UTILS::COLOR::Color shadowColor, + KODI::UTILS::COLOR::Color textColor, + KODI::UTILS::COLOR::Color shadowColor, float lineSpacing, float origHeight, CGUIFontTTF* font) @@ -87,8 +87,8 @@ std::string& CGUIFont::GetFontName() void CGUIFont::DrawText(float x, float y, - const std::vector& colors, - UTILS::COLOR::Color shadowColor, + const std::vector& colors, + KODI::UTILS::COLOR::Color shadowColor, const vecText& text, uint32_t alignment, float maxPixelWidth) @@ -104,7 +104,7 @@ void CGUIFont::DrawText(float x, return; maxPixelWidth = ROUND(static_cast(maxPixelWidth / context.GetGUIScaleX())); - std::vector renderColors; + std::vector renderColors; renderColors.reserve(colors.size()); for (const auto& color : colors) renderColors.emplace_back(context.MergeColor(color ? color : m_textColor)); @@ -113,7 +113,7 @@ void CGUIFont::DrawText(float x, if (shadowColor) { shadowColor = context.MergeColor(shadowColor); - std::vector shadowColors; + std::vector shadowColors; shadowColors.reserve(renderColors.size()); for (const auto& renderColor : renderColors) shadowColors.emplace_back((renderColor & 0xff000000) != 0 ? shadowColor : 0); @@ -179,8 +179,8 @@ bool CGUIFont::UpdateScrollInfo(const vecText& text, CScrollInfo& scrollInfo) void CGUIFont::DrawScrollingText(float x, float y, - const std::vector& colors, - UTILS::COLOR::Color shadowColor, + const std::vector& colors, + KODI::UTILS::COLOR::Color shadowColor, const vecText& text, uint32_t alignment, float maxWidth, @@ -219,7 +219,7 @@ void CGUIFont::DrawScrollingText(float x, else offset = scrollInfo.m_totalWidth - scrollInfo.m_pixelPos; - std::vector renderColors; + std::vector renderColors; renderColors.reserve(colors.size()); for (const auto& color : colors) renderColors.emplace_back(context.MergeColor(color ? color : m_textColor)); @@ -228,7 +228,7 @@ void CGUIFont::DrawScrollingText(float x, if (shadowColor) { shadowColor = context.MergeColor(shadowColor); - std::vector shadowColors; + std::vector shadowColors; shadowColors.reserve(renderColors.size()); for (const auto& renderColor : renderColors) shadowColors.emplace_back((renderColor & 0xff000000) != 0 ? shadowColor : 0); diff --git a/xbmc/guilib/GUIFont.h b/xbmc/guilib/GUIFont.h index c14f8775ea2d1..1a855ca78d6dd 100644 --- a/xbmc/guilib/GUIFont.h +++ b/xbmc/guilib/GUIFont.h @@ -112,8 +112,8 @@ class CGUIFont public: CGUIFont(const std::string& strFontName, uint32_t style, - UTILS::COLOR::Color textColor, - UTILS::COLOR::Color shadowColor, + KODI::UTILS::COLOR::Color textColor, + KODI::UTILS::COLOR::Color shadowColor, float lineSpacing, float origHeight, CGUIFontTTF* font); @@ -123,29 +123,29 @@ class CGUIFont void DrawText(float x, float y, - UTILS::COLOR::Color color, - UTILS::COLOR::Color shadowColor, + KODI::UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color shadowColor, const vecText& text, uint32_t alignment, float maxPixelWidth) { - std::vector colors; + std::vector colors; colors.push_back(color); DrawText(x, y, colors, shadowColor, text, alignment, maxPixelWidth); }; void DrawText(float x, float y, - const std::vector& colors, - UTILS::COLOR::Color shadowColor, + const std::vector& colors, + KODI::UTILS::COLOR::Color shadowColor, const vecText& text, uint32_t alignment, float maxPixelWidth); void DrawScrollingText(float x, float y, - const std::vector& colors, - UTILS::COLOR::Color shadowColor, + const std::vector& colors, + KODI::UTILS::COLOR::Color shadowColor, const vecText& text, uint32_t alignment, float maxPixelWidth, @@ -174,8 +174,8 @@ class CGUIFont protected: std::string m_strFontName; uint32_t m_style; - UTILS::COLOR::Color m_shadowColor; - UTILS::COLOR::Color m_textColor; + KODI::UTILS::COLOR::Color m_shadowColor; + KODI::UTILS::COLOR::Color m_textColor; float m_lineSpacing; float m_origHeight; CGUIFontTTF* m_font; // the font object has the size information diff --git a/xbmc/guilib/GUIFontCache.cpp b/xbmc/guilib/GUIFontCache.cpp index 70f17acc39121..170b95332d8a8 100644 --- a/xbmc/guilib/GUIFontCache.cpp +++ b/xbmc/guilib/GUIFontCache.cpp @@ -84,7 +84,7 @@ class CGUIFontCacheImpl explicit CGUIFontCacheImpl(CGUIFontCache* parent) : m_parent(parent) {} Value& Lookup(const CGraphicContext& context, Position& pos, - const std::vector& colors, + const std::vector& colors, const vecText& text, uint32_t alignment, float maxPixelWidth, @@ -131,7 +131,7 @@ CGUIFontCache::~CGUIFontCache() = default; template Value& CGUIFontCache::Lookup(const CGraphicContext& context, Position& pos, - const std::vector& colors, + const std::vector& colors, const vecText& text, uint32_t alignment, float maxPixelWidth, @@ -147,20 +147,21 @@ Value& CGUIFontCache::Lookup(const CGraphicContext& context, } template -Value& CGUIFontCacheImpl::Lookup(const CGraphicContext& context, - Position& pos, - const std::vector& colors, - const vecText& text, - uint32_t alignment, - float maxPixelWidth, - bool scrolling, - std::chrono::steady_clock::time_point now, - bool& dirtyCache) +Value& CGUIFontCacheImpl::Lookup( + const CGraphicContext& context, + Position& pos, + const std::vector& colors, + const vecText& text, + uint32_t alignment, + float maxPixelWidth, + bool scrolling, + std::chrono::steady_clock::time_point now, + bool& dirtyCache) { - const CGUIFontCacheKey key(pos, const_cast&>(colors), - const_cast(text), alignment, maxPixelWidth, - scrolling, context.GetGUIMatrix(), context.GetGUIScaleX(), - context.GetGUIScaleY()); + const CGUIFontCacheKey key( + pos, const_cast&>(colors), const_cast(text), + alignment, maxPixelWidth, scrolling, context.GetGUIMatrix(), context.GetGUIScaleX(), + context.GetGUIScaleY()); auto i = m_list.FindKey(key); if (i == m_list.hashMap.end()) @@ -226,7 +227,7 @@ template CGUIFontCacheStaticValue& CGUIFontCache< CGUIFontCacheStaticPosition, CGUIFontCacheStaticValue>::Lookup(const CGraphicContext& context, CGUIFontCacheStaticPosition&, - const std::vector&, + const std::vector&, const vecText&, uint32_t, float, @@ -244,7 +245,7 @@ template CGUIFontCacheDynamicValue& CGUIFontCache< CGUIFontCacheDynamicPosition, CGUIFontCacheDynamicValue>::Lookup(const CGraphicContext& context, CGUIFontCacheDynamicPosition&, - const std::vector&, + const std::vector&, const vecText&, uint32_t, float, diff --git a/xbmc/guilib/GUIFontCache.h b/xbmc/guilib/GUIFontCache.h index 8a967fa4a2898..9d403d704dd6f 100644 --- a/xbmc/guilib/GUIFontCache.h +++ b/xbmc/guilib/GUIFontCache.h @@ -40,7 +40,7 @@ template struct CGUIFontCacheKey { Position m_pos; - std::vector& m_colors; + std::vector& m_colors; vecText& m_text; uint32_t m_alignment; float m_maxPixelWidth; @@ -50,7 +50,7 @@ struct CGUIFontCacheKey float m_scaleY; CGUIFontCacheKey(Position pos, - std::vector& colors, + std::vector& colors, vecText& text, uint32_t alignment, float maxPixelWidth, @@ -85,7 +85,7 @@ struct CGUIFontCacheEntry std::chrono::steady_clock::time_point now) : m_cache(cache), m_key(key.m_pos, - *new std::vector, + *new std::vector, *new vecText, key.m_alignment, key.m_maxPixelWidth, @@ -157,7 +157,7 @@ class CGUIFontCache Value& Lookup(const CGraphicContext& context, Position& pos, - const std::vector& colors, + const std::vector& colors, const vecText& text, uint32_t alignment, float maxPixelWidth, diff --git a/xbmc/guilib/GUIFontManager.cpp b/xbmc/guilib/GUIFontManager.cpp index 93f88666a0944..9b692540c8481 100644 --- a/xbmc/guilib/GUIFontManager.cpp +++ b/xbmc/guilib/GUIFontManager.cpp @@ -127,8 +127,8 @@ static bool CheckFont(std::string& strPath, const std::string& newPath, const st CGUIFont* GUIFontManager::LoadTTF(const std::string& strFontName, const std::string& strFilename, - UTILS::COLOR::Color textColor, - UTILS::COLOR::Color shadowColor, + KODI::UTILS::COLOR::Color textColor, + KODI::UTILS::COLOR::Color shadowColor, const int iSize, const int iStyle, bool border, @@ -394,7 +394,7 @@ CGUIFont* GUIFontManager::GetDefaultFont(bool border) { // create it const auto& font13 = m_vecFonts[font13index]; OrigFontInfo fontInfo = m_vecFontInfo[font13index]; - font13border = LoadTTF("__defaultborder__", fontInfo.fileName, UTILS::COLOR::BLACK, 0, + font13border = LoadTTF("__defaultborder__", fontInfo.fileName, KODI::UTILS::COLOR::BLACK, 0, fontInfo.size, font13->GetStyle(), true, 1.0f, fontInfo.aspect, &fontInfo.sourceRes, fontInfo.preserveAspect); } @@ -493,8 +493,8 @@ void GUIFontManager::LoadFonts(const TiXmlNode* fontNode) int iSize = 20; float aspect = 1.0f; float lineSpacing = 1.0f; - UTILS::COLOR::Color shadowColor = 0; - UTILS::COLOR::Color textColor = 0; + KODI::UTILS::COLOR::Color shadowColor = 0; + KODI::UTILS::COLOR::Color textColor = 0; int iStyle = FONT_STYLE_NORMAL; XMLUtils::GetString(fontNode, "name", fontName); @@ -551,11 +551,11 @@ void GUIFontManager::SettingOptionsFontsFiller(const SettingConstPtr& setting, CFileItemList items; // Find font files - XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::SYSTEM, itemsRoot, - UTILS::FONT::SUPPORTED_EXTENSIONS_MASK, + XFILE::CDirectory::GetDirectory(KODI::UTILS::FONT::FONTPATH::SYSTEM, itemsRoot, + KODI::UTILS::FONT::SUPPORTED_EXTENSIONS_MASK, XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO); - XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::USER, items, - UTILS::FONT::SUPPORTED_EXTENSIONS_MASK, + XFILE::CDirectory::GetDirectory(KODI::UTILS::FONT::FONTPATH::USER, items, + KODI::UTILS::FONT::SUPPORTED_EXTENSIONS_MASK, XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO); for (auto itItem = itemsRoot.rbegin(); itItem != itemsRoot.rend(); ++itItem) @@ -578,13 +578,13 @@ void GUIFontManager::Initialize() void GUIFontManager::LoadUserFonts() { - if (!XFILE::CDirectory::Exists(UTILS::FONT::FONTPATH::USER)) + if (!XFILE::CDirectory::Exists(KODI::UTILS::FONT::FONTPATH::USER)) return; CLog::LogF(LOGDEBUG, "Updating user fonts cache..."); CXBMCTinyXML xmlDoc; std::string userFontCacheFilepath = - URIUtils::AddFileToFolder(UTILS::FONT::FONTPATH::USER, XML_FONTCACHE_FILENAME); + URIUtils::AddFileToFolder(KODI::UTILS::FONT::FONTPATH::USER, XML_FONTCACHE_FILENAME); if (LoadXMLData(userFontCacheFilepath, xmlDoc)) { // Load in cache the fonts metadata previously stored in the XML @@ -614,8 +614,8 @@ void GUIFontManager::LoadUserFonts() size_t previousCacheSize = m_userFontsCache.size(); CFileItemList dirItems; // Get the current files list from user fonts folder - XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::USER, dirItems, - UTILS::FONT::SUPPORTED_EXTENSIONS_MASK, + XFILE::CDirectory::GetDirectory(KODI::UTILS::FONT::FONTPATH::USER, dirItems, + KODI::UTILS::FONT::SUPPORTED_EXTENSIONS_MASK, XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO); dirItems.SetFastLookup(true); @@ -623,7 +623,7 @@ void GUIFontManager::LoadUserFonts() auto it = m_userFontsCache.begin(); while (it != m_userFontsCache.end()) { - const std::string filePath = UTILS::FONT::FONTPATH::USER + (*it).m_filename; + const std::string filePath = KODI::UTILS::FONT::FONTPATH::USER + (*it).m_filename; if (!dirItems.Contains(filePath)) { it = m_userFontsCache.erase(it); @@ -650,7 +650,7 @@ void GUIFontManager::LoadUserFonts() continue; std::set familyNames; - if (UTILS::FONT::GetFontFamilyNames(filepath, familyNames)) + if (KODI::UTILS::FONT::GetFontFamilyNames(filepath, familyNames)) { m_userFontsCache.emplace_back(item->GetLabel(), familyNames); } diff --git a/xbmc/guilib/GUIFontManager.h b/xbmc/guilib/GUIFontManager.h index c28ad41d00de0..9dbed4682eed4 100644 --- a/xbmc/guilib/GUIFontManager.h +++ b/xbmc/guilib/GUIFontManager.h @@ -78,8 +78,8 @@ class GUIFontManager : public IMsgTargetCallback void LoadFonts(const std::string& fontSet); CGUIFont* LoadTTF(const std::string& strFontName, const std::string& strFilename, - UTILS::COLOR::Color textColor, - UTILS::COLOR::Color shadowColor, + KODI::UTILS::COLOR::Color textColor, + KODI::UTILS::COLOR::Color shadowColor, const int iSize, const int iStyle, bool border = false, diff --git a/xbmc/guilib/GUIFontTTF.cpp b/xbmc/guilib/GUIFontTTF.cpp index f9f0eec2a3ee2..5f491f6622896 100644 --- a/xbmc/guilib/GUIFontTTF.cpp +++ b/xbmc/guilib/GUIFontTTF.cpp @@ -365,7 +365,7 @@ void CGUIFontTTF::End() void CGUIFontTTF::DrawTextInternal(CGraphicContext& context, float x, float y, - const std::vector& colors, + const std::vector& colors, const vecText& text, uint32_t alignment, float maxPixelWidth, @@ -632,7 +632,7 @@ void CGUIFontTTF::DrawTextInternal(CGraphicContext& context, { // If starting text on a new line, determine justification effects // Get the current letter in the CStdString - UTILS::COLOR::Color color = (text[itGlyph->m_glyphInfo.cluster] & 0xff0000) >> 16; + KODI::UTILS::COLOR::Color color = (text[itGlyph->m_glyphInfo.cluster] & 0xff0000) >> 16; if (color >= colors.size()) color = 0; color = colors[color]; @@ -1124,7 +1124,7 @@ void CGUIFontTTF::RenderCharacter(CGraphicContext& context, float posX, float posY, const Character* ch, - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, bool roundX, std::vector& vertices) { diff --git a/xbmc/guilib/GUIFontTTF.h b/xbmc/guilib/GUIFontTTF.h index 02a0ac16df2f6..2420659d92a5d 100644 --- a/xbmc/guilib/GUIFontTTF.h +++ b/xbmc/guilib/GUIFontTTF.h @@ -160,7 +160,7 @@ class CGUIFontTTF void DrawTextInternal(CGraphicContext& context, float x, float y, - const std::vector& colors, + const std::vector& colors, const vecText& text, uint32_t alignment, float maxPixelWidth, @@ -177,7 +177,7 @@ class CGUIFontTTF float posX, float posY, const Character* ch, - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, bool roundX, std::vector& vertices); void ClearCharacterCache(); @@ -208,7 +208,7 @@ class CGUIFontTTF unsigned int GetTextureLineHeight() const; unsigned int GetMaxFontHeight() const; - UTILS::COLOR::Color m_color{UTILS::COLOR::NONE}; + KODI::UTILS::COLOR::Color m_color{KODI::UTILS::COLOR::NONE}; std::vector m_char; // our characters diff --git a/xbmc/guilib/GUILabel.cpp b/xbmc/guilib/GUILabel.cpp index 8db0fe526810c..91f4237f480f4 100644 --- a/xbmc/guilib/GUILabel.cpp +++ b/xbmc/guilib/GUILabel.cpp @@ -69,7 +69,7 @@ bool CGUILabel::SetColor(CGUILabel::COLOR color) return changed; } -UTILS::COLOR::Color CGUILabel::GetColor() const +KODI::UTILS::COLOR::Color CGUILabel::GetColor() const { switch (m_color) { @@ -107,7 +107,7 @@ bool CGUILabel::Process(unsigned int currentTime) void CGUILabel::Render() { - UTILS::COLOR::Color color = GetColor(); + KODI::UTILS::COLOR::Color color = GetColor(); bool renderSolid = (m_color == COLOR_DISABLED); bool overFlows = (m_renderRect.Width() + 0.5f < m_textLayout.GetTextWidth()); // 0.5f to deal with floating point rounding issues if (overFlows && m_scrolling && !renderSolid) @@ -178,7 +178,8 @@ bool CGUILabel::SetAlign(uint32_t align) return changed; } -bool CGUILabel::SetStyledText(const vecText& text, const std::vector& colors) +bool CGUILabel::SetStyledText(const vecText& text, + const std::vector& colors) { m_textLayout.UpdateStyled(text, colors, m_maxRect.Width()); m_invalid = false; diff --git a/xbmc/guilib/GUILabel.h b/xbmc/guilib/GUILabel.h index 6277ea68d1ea0..07133dd28a8ee 100644 --- a/xbmc/guilib/GUILabel.h +++ b/xbmc/guilib/GUILabel.h @@ -130,7 +130,7 @@ class CGUILabel \param colors colors referenced in the styled text. \sa SetText, SetTextW */ - bool SetStyledText(const vecText& text, const std::vector& colors); + bool SetStyledText(const vecText& text, const std::vector& colors); /*! \brief Set the color to use for the label Sets the color to be used for this label. Takes effect at the next render @@ -220,7 +220,7 @@ class CGUILabel static bool CheckAndCorrectOverlap(CGUILabel &label1, CGUILabel &label2); protected: - UTILS::COLOR::Color GetColor() const; + KODI::UTILS::COLOR::Color GetColor() const; /*! \brief Computes the final layout of the text Uses the maximal position and width of the text, as well as the text length diff --git a/xbmc/guilib/GUILabelControl.cpp b/xbmc/guilib/GUILabelControl.cpp index 5f57fbc3a64b4..d4da4f81b1a8e 100644 --- a/xbmc/guilib/GUILabelControl.cpp +++ b/xbmc/guilib/GUILabelControl.cpp @@ -75,10 +75,10 @@ void CGUILabelControl::UpdateInfo(const CGUIListItem *item) std::wstring utf16; g_charsetConverter.utf8ToW(label, utf16); vecText text; text.reserve(utf16.size()+1); - std::vector colors; + std::vector colors; colors.push_back(m_label.GetLabelInfo().textColor); colors.push_back(m_label.GetLabelInfo().disabledColor); - UTILS::COLOR::Color select = m_label.GetLabelInfo().selectedColor; + KODI::UTILS::COLOR::Color select = m_label.GetLabelInfo().selectedColor; if (!select) select = 0xFFFF0000; colors.push_back(select); diff --git a/xbmc/guilib/GUIMoverControl.cpp b/xbmc/guilib/GUIMoverControl.cpp index 4299fc0c5f7cf..122e95d99d4a4 100644 --- a/xbmc/guilib/GUIMoverControl.cpp +++ b/xbmc/guilib/GUIMoverControl.cpp @@ -26,7 +26,7 @@ CGUIMoverControl::CGUIMoverControl(int parentID, float height, const CTextureInfo& textureFocus, const CTextureInfo& textureNoFocus, - UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg) + KODI::UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg) : CGUIControl(parentID, controlID, posX, posY, width, height), m_imgFocus(CGUITexture::CreateTexture(posX, posY, width, height, textureFocus)), m_imgNoFocus(CGUITexture::CreateTexture(posX, posY, width, height, textureNoFocus)) diff --git a/xbmc/guilib/GUIMoverControl.h b/xbmc/guilib/GUIMoverControl.h index 9b1f2faa690e2..1ebaf4dda2754 100644 --- a/xbmc/guilib/GUIMoverControl.h +++ b/xbmc/guilib/GUIMoverControl.h @@ -36,7 +36,7 @@ class CGUIMoverControl : public CGUIControl float height, const CTextureInfo& textureFocus, const CTextureInfo& textureNoFocus, - UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg); + KODI::UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg); ~CGUIMoverControl(void) override = default; CGUIMoverControl* Clone() const override { return new CGUIMoverControl(*this); } @@ -67,7 +67,7 @@ class CGUIMoverControl : public CGUIControl std::unique_ptr m_imgFocus; std::unique_ptr m_imgNoFocus; unsigned int m_frameCounter; - UTILS::MOVING_SPEED::CMovingSpeed m_movingSpeed; + KODI::UTILS::MOVING_SPEED::CMovingSpeed m_movingSpeed; float m_fAnalogSpeed; int m_iX1, m_iX2, m_iY1, m_iY2; int m_iLocationX, m_iLocationY; diff --git a/xbmc/guilib/GUIRSSControl.cpp b/xbmc/guilib/GUIRSSControl.cpp index 8d511d6976c34..32dec47574468 100644 --- a/xbmc/guilib/GUIRSSControl.cpp +++ b/xbmc/guilib/GUIRSSControl.cpp @@ -163,7 +163,7 @@ void CGUIRSSControl::Render() if (m_label.font) { - std::vector colors; + std::vector colors; colors.push_back(m_label.textColor); colors.push_back(m_headlineColor); colors.push_back(m_channelColor); diff --git a/xbmc/guilib/GUIResizeControl.cpp b/xbmc/guilib/GUIResizeControl.cpp index 415e303f2d82d..d080ec0884f7e 100644 --- a/xbmc/guilib/GUIResizeControl.cpp +++ b/xbmc/guilib/GUIResizeControl.cpp @@ -25,7 +25,7 @@ CGUIResizeControl::CGUIResizeControl(int parentID, float height, const CTextureInfo& textureFocus, const CTextureInfo& textureNoFocus, - UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg) + KODI::UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg) : CGUIControl(parentID, controlID, posX, posY, width, height), m_imgFocus(CGUITexture::CreateTexture(posX, posY, width, height, textureFocus)), m_imgNoFocus(CGUITexture::CreateTexture(posX, posY, width, height, textureNoFocus)) diff --git a/xbmc/guilib/GUIResizeControl.h b/xbmc/guilib/GUIResizeControl.h index b2af51dee5308..457c6c3260c21 100644 --- a/xbmc/guilib/GUIResizeControl.h +++ b/xbmc/guilib/GUIResizeControl.h @@ -32,7 +32,7 @@ class CGUIResizeControl : public CGUIControl float height, const CTextureInfo& textureFocus, const CTextureInfo& textureNoFocus, - UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg); + KODI::UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg); ~CGUIResizeControl() override = default; CGUIResizeControl* Clone() const override { return new CGUIResizeControl(*this); } @@ -60,7 +60,7 @@ class CGUIResizeControl : public CGUIControl std::unique_ptr m_imgFocus; std::unique_ptr m_imgNoFocus; unsigned int m_frameCounter; - UTILS::MOVING_SPEED::CMovingSpeed m_movingSpeed; + KODI::UTILS::MOVING_SPEED::CMovingSpeed m_movingSpeed; float m_fAnalogSpeed; float m_x1, m_x2, m_y1, m_y2; diff --git a/xbmc/guilib/GUITextLayout.cpp b/xbmc/guilib/GUITextLayout.cpp index 11c671b110efa..e3d6010b188a3 100644 --- a/xbmc/guilib/GUITextLayout.cpp +++ b/xbmc/guilib/GUITextLayout.cpp @@ -52,8 +52,8 @@ void CGUITextLayout::SetWrap(bool bWrap) void CGUITextLayout::Render(float x, float y, float angle, - UTILS::COLOR::Color color, - UTILS::COLOR::Color shadowColor, + KODI::UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color shadowColor, uint32_t alignment, float maxWidth, bool solid) @@ -104,12 +104,11 @@ bool CGUITextLayout::UpdateScrollinfo(CScrollInfo &scrollInfo) return m_font->UpdateScrollInfo(m_lines[0].m_text, scrollInfo); } - void CGUITextLayout::RenderScrolling(float x, float y, float angle, - UTILS::COLOR::Color color, - UTILS::COLOR::Color shadowColor, + KODI::UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color shadowColor, uint32_t alignment, float maxWidth, const CScrollInfo& scrollInfo) @@ -152,8 +151,8 @@ void CGUITextLayout::RenderScrolling(float x, void CGUITextLayout::RenderOutline(float x, float y, - UTILS::COLOR::Color color, - UTILS::COLOR::Color outlineColor, + KODI::UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color outlineColor, uint32_t alignment, float maxWidth) { @@ -161,7 +160,7 @@ void CGUITextLayout::RenderOutline(float x, return; // set the outline color - std::vector outlineColors; + std::vector outlineColors; if (m_colors.size()) outlineColors.push_back(outlineColor); @@ -248,7 +247,7 @@ void CGUITextLayout::UpdateCommon(const std::wstring &text, float maxWidth, bool { // parse the text for style information vecText parsedText; - std::vector colors; + std::vector colors; ParseText(text, m_font ? m_font->GetStyle() : 0, m_textColor, colors, parsedText); // and update @@ -256,7 +255,7 @@ void CGUITextLayout::UpdateCommon(const std::wstring &text, float maxWidth, bool } void CGUITextLayout::UpdateStyled(const vecText& text, - const std::vector& colors, + const std::vector& colors, float maxWidth, bool forceLTRReadingOrder) { @@ -352,7 +351,7 @@ void CGUITextLayout::Filter(std::string &text) { std::wstring utf16; g_charsetConverter.utf8ToW(text, utf16, false); - std::vector colors; + std::vector colors; vecText parsedText; ParseText(utf16, 0, 0xffffffff, colors, parsedText); utf16.clear(); @@ -363,8 +362,8 @@ void CGUITextLayout::Filter(std::string &text) void CGUITextLayout::ParseText(const std::wstring& text, uint32_t defaultStyle, - UTILS::COLOR::Color defaultColor, - std::vector& colors, + KODI::UTILS::COLOR::Color defaultColor, + std::vector& colors, vecText& parsedText) { // run through the string, searching for: @@ -375,10 +374,10 @@ void CGUITextLayout::ParseText(const std::wstring& text, // [TABS] tab amount [/TABS] -> add tabulator space in view uint32_t currentStyle = defaultStyle; // start with the default font's style - UTILS::COLOR::Color currentColor = 0; + KODI::UTILS::COLOR::Color currentColor = 0; colors.push_back(defaultColor); - std::stack colorStack; + std::stack colorStack; colorStack.push(0); // these aren't independent, but that's probably not too much of an issue @@ -390,7 +389,7 @@ void CGUITextLayout::ParseText(const std::wstring& text, while (pos != std::string::npos && pos + 1 < text.size()) { uint32_t newStyle = 0; - UTILS::COLOR::Color newColor = currentColor; + KODI::UTILS::COLOR::Color newColor = currentColor; bool colorTagChange = false; bool newLine = false; int tabs = 0; @@ -469,7 +468,7 @@ void CGUITextLayout::ParseText(const std::wstring& text, { std::string t; g_charsetConverter.wToUTF8(text.substr(pos + 5, finish - pos - 5), t); - UTILS::COLOR::Color color = CServiceBroker::GetGUI()->GetColorManager().GetColor(t); + KODI::UTILS::COLOR::Color color = CServiceBroker::GetGUI()->GetColorManager().GetColor(t); const auto& it = std::find(colors.begin(), colors.end(), color); if (it == colors.end()) { // create new color @@ -734,8 +733,8 @@ std::string CGUITextLayout::GetText() const void CGUITextLayout::DrawText(CGUIFont* font, float x, float y, - UTILS::COLOR::Color color, - UTILS::COLOR::Color shadowColor, + KODI::UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color shadowColor, const std::string& text, uint32_t align) { diff --git a/xbmc/guilib/GUITextLayout.h b/xbmc/guilib/GUITextLayout.h index be365469d8d6e..1b01d1db8bf70 100644 --- a/xbmc/guilib/GUITextLayout.h +++ b/xbmc/guilib/GUITextLayout.h @@ -64,23 +64,23 @@ class CGUITextLayout void Render(float x, float y, float angle, - UTILS::COLOR::Color color, - UTILS::COLOR::Color shadowColor, + KODI::UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color shadowColor, uint32_t alignment, float maxWidth, bool solid = false); void RenderScrolling(float x, float y, float angle, - UTILS::COLOR::Color color, - UTILS::COLOR::Color shadowColor, + KODI::UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color shadowColor, uint32_t alignment, float maxWidth, const CScrollInfo& scrollInfo); void RenderOutline(float x, float y, - UTILS::COLOR::Color color, - UTILS::COLOR::Color outlineColor, + KODI::UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color outlineColor, uint32_t alignment, float maxWidth); @@ -109,7 +109,7 @@ class CGUITextLayout bool Update(const std::string &text, float maxWidth = 0, bool forceUpdate = false, bool forceLTRReadingOrder = false); bool UpdateW(const std::wstring &text, float maxWidth = 0, bool forceUpdate = false, bool forceLTRReadingOrder = false); - /*! \brief Update text from a pre-styled vecText/std::vector combination + /*! \brief Update text from a pre-styled vecText/std::vector combination Allows styled text to be passed directly to the text layout. \param text the styled text to set. \param colors the colors used on the text. @@ -117,7 +117,7 @@ class CGUITextLayout \param forceLTRReadingOrder whether to force left to right reading order, defaults to false. */ void UpdateStyled(const vecText& text, - const std::vector& colors, + const std::vector& colors, float maxWidth = 0, bool forceLTRReadingOrder = false); @@ -128,12 +128,11 @@ class CGUITextLayout void SetWrap(bool bWrap=true); void SetMaxHeight(float fHeight); - static void DrawText(CGUIFont* font, float x, float y, - UTILS::COLOR::Color color, - UTILS::COLOR::Color shadowColor, + KODI::UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color shadowColor, const std::string& text, uint32_t align); static void Filter(std::string &text); @@ -160,7 +159,7 @@ class CGUITextLayout void UseMonoFont(bool use) { m_font = use && m_monoFont ? m_monoFont : m_varFont; } // our text to render - std::vector m_colors; + std::vector m_colors; std::vector m_lines; typedef std::vector::iterator iLine; @@ -173,7 +172,7 @@ class CGUITextLayout bool m_wrap; // wrapping (true if justify is enabled!) float m_maxHeight; // the default color (may differ from the font objects defaults) - UTILS::COLOR::Color m_textColor; + KODI::UTILS::COLOR::Color m_textColor; std::string m_lastUtf8Text; std::wstring m_lastText; @@ -194,8 +193,8 @@ class CGUITextLayout static void AppendToUTF32(const std::wstring &utf16, character_t colStyle, vecText &utf32); static void ParseText(const std::wstring& text, uint32_t defaultStyle, - UTILS::COLOR::Color defaultColor, - std::vector& colors, + KODI::UTILS::COLOR::Color defaultColor, + std::vector& colors, vecText& parsedText); }; diff --git a/xbmc/guilib/GUITexture.cpp b/xbmc/guilib/GUITexture.cpp index 7a3e004ad911b..588860186e6de 100644 --- a/xbmc/guilib/GUITexture.cpp +++ b/xbmc/guilib/GUITexture.cpp @@ -51,7 +51,7 @@ CGUITexture* CGUITexture::CreateTexture( } void CGUITexture::DrawQuad(const CRect& coords, - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, CTexture* texture, const CRect* texCoords, const float depth, @@ -180,8 +180,8 @@ void CGUITexture::Render(int32_t depthOffset, int32_t overrideDepth) #define MIX_ALPHA(a,c) (((a * (c >> 24)) / 255) << 24) | (c & 0x00ffffff) // diffuse color - UTILS::COLOR::Color color = - (m_info.diffuseColor) ? (UTILS::COLOR::Color)m_info.diffuseColor : m_diffuseColor; + KODI::UTILS::COLOR::Color color = + (m_info.diffuseColor) ? (KODI::UTILS::COLOR::Color)m_info.diffuseColor : m_diffuseColor; // clang-format off if (m_alpha != 0xFF) color = MIX_ALPHA(m_alpha, color); @@ -597,7 +597,7 @@ bool CGUITexture::SetAlpha(unsigned char alpha) return changed; } -bool CGUITexture::SetDiffuseColor(UTILS::COLOR::Color color, +bool CGUITexture::SetDiffuseColor(KODI::UTILS::COLOR::Color color, const CGUIListItem* item /* = nullptr */) { bool changed = m_diffuseColor != color; diff --git a/xbmc/guilib/GUITexture.h b/xbmc/guilib/GUITexture.h index bbedc3aa212e5..fdc76a01474b3 100644 --- a/xbmc/guilib/GUITexture.h +++ b/xbmc/guilib/GUITexture.h @@ -68,7 +68,7 @@ class CGUITexture; using CreateGUITextureFunc = std::function; using DrawQuadFunc = std::functionLoadToGPU(); @@ -138,7 +138,7 @@ void CGUITextureD3D::Draw(float *x, float *y, float *z, const CRect &texture, co } void CGUITextureD3D::DrawQuad(const CRect& rect, - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, CTexture* texture, const CRect* texCoords, const float depth, diff --git a/xbmc/guilib/GUITextureD3D.h b/xbmc/guilib/GUITextureD3D.h index 87fd9a784865b..d01393356f960 100644 --- a/xbmc/guilib/GUITextureD3D.h +++ b/xbmc/guilib/GUITextureD3D.h @@ -19,7 +19,7 @@ class CGUITextureD3D : public CGUITexture float posX, float posY, float width, float height, const CTextureInfo& texture); static void DrawQuad(const CRect& coords, - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, CTexture* texture = nullptr, const CRect* texCoords = nullptr, const float depth = 1.0, @@ -31,13 +31,13 @@ class CGUITextureD3D : public CGUITexture CGUITextureD3D* Clone() const override; protected: - void Begin(UTILS::COLOR::Color color); + void Begin(KODI::UTILS::COLOR::Color color); void Draw(float *x, float *y, float *z, const CRect &texture, const CRect &diffuse, int orientation); void End(); private: CGUITextureD3D(const CGUITextureD3D& texture) = default; - UTILS::COLOR::Color m_col; + KODI::UTILS::COLOR::Color m_col; }; diff --git a/xbmc/guilib/GUITextureGL.cpp b/xbmc/guilib/GUITextureGL.cpp index c2f23ccdfb594..b9ce89934e021 100644 --- a/xbmc/guilib/GUITextureGL.cpp +++ b/xbmc/guilib/GUITextureGL.cpp @@ -43,7 +43,7 @@ CGUITextureGL* CGUITextureGL::Clone() const return new CGUITextureGL(*this); } -void CGUITextureGL::Begin(UTILS::COLOR::Color color) +void CGUITextureGL::Begin(KODI::UTILS::COLOR::Color color) { CTexture* texture = m_texture.m_textures[m_currentFrame].get(); texture->LoadToGPU(); @@ -256,7 +256,7 @@ void CGUITextureGL::Draw(float *x, float *y, float *z, const CRect &texture, con } void CGUITextureGL::DrawQuad(const CRect& rect, - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, CTexture* texture, const CRect* texCoords, const float depth, diff --git a/xbmc/guilib/GUITextureGL.h b/xbmc/guilib/GUITextureGL.h index 5850586a2222c..f057c7339cc7f 100644 --- a/xbmc/guilib/GUITextureGL.h +++ b/xbmc/guilib/GUITextureGL.h @@ -25,7 +25,7 @@ class CGUITextureGL : public CGUITexture float posX, float posY, float width, float height, const CTextureInfo& texture); static void DrawQuad(const CRect& coords, - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, CTexture* texture = nullptr, const CRect* texCoords = nullptr, const float depth = 1.0, @@ -37,7 +37,7 @@ class CGUITextureGL : public CGUITexture CGUITextureGL* Clone() const override; protected: - void Begin(UTILS::COLOR::Color color) override; + void Begin(KODI::UTILS::COLOR::Color color) override; void Draw(float *x, float *y, float *z, const CRect &texture, const CRect &diffuse, int orientation) override; void End() override; diff --git a/xbmc/guilib/GUITextureGLES.cpp b/xbmc/guilib/GUITextureGLES.cpp index 97fdb28679dfd..378d22beddf63 100644 --- a/xbmc/guilib/GUITextureGLES.cpp +++ b/xbmc/guilib/GUITextureGLES.cpp @@ -42,7 +42,7 @@ CGUITextureGLES* CGUITextureGLES::Clone() const return new CGUITextureGLES(*this); } -void CGUITextureGLES::Begin(UTILS::COLOR::Color color) +void CGUITextureGLES::Begin(KODI::UTILS::COLOR::Color color) { CTexture* texture = m_texture.m_textures[m_currentFrame].get(); texture->LoadToGPU(); @@ -235,7 +235,7 @@ void CGUITextureGLES::Draw(float *x, float *y, float *z, const CRect &texture, c } void CGUITextureGLES::DrawQuad(const CRect& rect, - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, CTexture* texture, const CRect* texCoords, const float depth, diff --git a/xbmc/guilib/GUITextureGLES.h b/xbmc/guilib/GUITextureGLES.h index ad563704e088d..efbda23159a5f 100644 --- a/xbmc/guilib/GUITextureGLES.h +++ b/xbmc/guilib/GUITextureGLES.h @@ -34,7 +34,7 @@ class CGUITextureGLES : public CGUITexture float posX, float posY, float width, float height, const CTextureInfo& texture); static void DrawQuad(const CRect& coords, - UTILS::COLOR::Color color, + KODI::UTILS::COLOR::Color color, CTexture* texture = nullptr, const CRect* texCoords = nullptr, const float depth = 1.0, @@ -46,7 +46,7 @@ class CGUITextureGLES : public CGUITexture CGUITextureGLES* Clone() const override; protected: - void Begin(UTILS::COLOR::Color color) override; + void Begin(KODI::UTILS::COLOR::Color color) override; void Draw(float* x, float* y, float* z, const CRect& texture, const CRect& diffuse, int orientation) override; void End() override; diff --git a/xbmc/guilib/GUIVideoControl.cpp b/xbmc/guilib/GUIVideoControl.cpp index ccb066a75477b..705c498fa1f30 100644 --- a/xbmc/guilib/GUIVideoControl.cpp +++ b/xbmc/guilib/GUIVideoControl.cpp @@ -61,7 +61,7 @@ void CGUIVideoControl::Render() TransformMatrix mat; CServiceBroker::GetWinSystem()->GetGfxContext().SetTransform(mat, 1.0, 1.0); - UTILS::COLOR::Color alpha = + KODI::UTILS::COLOR::Color alpha = CServiceBroker::GetWinSystem()->GetGfxContext().MergeAlpha(0xFF000000) >> 24; if (appPlayer->IsRenderingVideoLayer()) { diff --git a/xbmc/guilib/GUIWindow.cpp b/xbmc/guilib/GUIWindow.cpp index 26fae889b7750..5367362e3ee2a 100644 --- a/xbmc/guilib/GUIWindow.cpp +++ b/xbmc/guilib/GUIWindow.cpp @@ -1075,7 +1075,7 @@ void CGUIWindow::RunUnloadActions() const void CGUIWindow::ClearBackground() { m_clearBackground.Update(); - UTILS::COLOR::Color color = m_clearBackground; + KODI::UTILS::COLOR::Color color = m_clearBackground; if (color) CServiceBroker::GetWinSystem()->GetGfxContext().Clear(color); else if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiGeometryClear) diff --git a/xbmc/guilib/VisibleEffect.cpp b/xbmc/guilib/VisibleEffect.cpp index ef953a9537ebf..3d506d85d4dd9 100644 --- a/xbmc/guilib/VisibleEffect.cpp +++ b/xbmc/guilib/VisibleEffect.cpp @@ -177,11 +177,11 @@ CFadeEffect::CFadeEffect(const TiXmlElement* node, bool reverseDefaults, EFFECT_ const char* start = node->Attribute("start"); const char* end = node->Attribute("end"); if (start) - m_startColor = UTILS::COLOR::ConvertToFloats( + m_startColor = KODI::UTILS::COLOR::ConvertToFloats( CServiceBroker::GetGUI()->GetColorManager().GetColor(start)); if (end) - m_endColor = - UTILS::COLOR::ConvertToFloats(CServiceBroker::GetGUI()->GetColorManager().GetColor(end)); + m_endColor = KODI::UTILS::COLOR::ConvertToFloats( + CServiceBroker::GetGUI()->GetColorManager().GetColor(end)); } } @@ -191,13 +191,13 @@ CFadeEffect::CFadeEffect(float start, float end, unsigned int delay, unsigned in m_endAlpha = end; } -CFadeEffect::CFadeEffect(UTILS::COLOR::Color start, - UTILS::COLOR::Color end, +CFadeEffect::CFadeEffect(KODI::UTILS::COLOR::Color start, + KODI::UTILS::COLOR::Color end, unsigned int delay, unsigned int length) : CAnimEffect(delay, length, EFFECT_TYPE_FADE_DIFFUSE), - m_startColor(UTILS::COLOR::ConvertToFloats(start)), - m_endColor(UTILS::COLOR::ConvertToFloats(end)) + m_startColor(KODI::UTILS::COLOR::ConvertToFloats(start)), + m_endColor(KODI::UTILS::COLOR::ConvertToFloats(end)) { m_startAlpha = m_endAlpha = 1.0f; } diff --git a/xbmc/guilib/VisibleEffect.h b/xbmc/guilib/VisibleEffect.h index 1cc4a092ccd24..a5b19e294a97d 100644 --- a/xbmc/guilib/VisibleEffect.h +++ b/xbmc/guilib/VisibleEffect.h @@ -88,8 +88,8 @@ class CFadeEffect : public CAnimEffect public: CFadeEffect(const TiXmlElement* node, bool reverseDefaults, EFFECT_TYPE effect); CFadeEffect(float start, float end, unsigned int delay, unsigned int length); - CFadeEffect(UTILS::COLOR::Color start, - UTILS::COLOR::Color end, + CFadeEffect(KODI::UTILS::COLOR::Color start, + KODI::UTILS::COLOR::Color end, unsigned int delay, unsigned int length); ~CFadeEffect() override = default; @@ -98,8 +98,8 @@ class CFadeEffect : public CAnimEffect float m_startAlpha; float m_endAlpha; - UTILS::COLOR::ColorFloats m_startColor; - UTILS::COLOR::ColorFloats m_endColor; + KODI::UTILS::COLOR::ColorFloats m_startColor; + KODI::UTILS::COLOR::ColorFloats m_endColor; }; class CSlideEffect : public CAnimEffect diff --git a/xbmc/guilib/guiinfo/GUIInfoColor.cpp b/xbmc/guilib/guiinfo/GUIInfoColor.cpp index c20326d6ecaec..8d5b5bd0cb018 100644 --- a/xbmc/guilib/guiinfo/GUIInfoColor.cpp +++ b/xbmc/guilib/guiinfo/GUIInfoColor.cpp @@ -31,7 +31,7 @@ bool CGUIInfoColor::Update(const CGUIListItem* item /* = nullptr */) else infoLabel = CServiceBroker::GetGUI()->GetInfoManager().GetLabel(m_info, INFO::DEFAULT_CONTEXT); - UTILS::COLOR::Color color = + KODI::UTILS::COLOR::Color color = !infoLabel.empty() ? CServiceBroker::GetGUI()->GetColorManager().GetColor(infoLabel) : 0; if (m_color != color) { diff --git a/xbmc/guilib/guiinfo/GUIInfoColor.h b/xbmc/guilib/guiinfo/GUIInfoColor.h index a71104787d641..6d2a0527543b8 100644 --- a/xbmc/guilib/guiinfo/GUIInfoColor.h +++ b/xbmc/guilib/guiinfo/GUIInfoColor.h @@ -30,9 +30,9 @@ namespace GUIINFO class CGUIInfoColor { public: - constexpr CGUIInfoColor(UTILS::COLOR::Color color = 0) : m_color(color) {} + constexpr CGUIInfoColor(KODI::UTILS::COLOR::Color color = 0) : m_color(color) {} - constexpr operator UTILS::COLOR::Color() const { return m_color; } + constexpr operator KODI::UTILS::COLOR::Color() const { return m_color; } bool Update(const CGUIListItem* item = nullptr); void Parse(const std::string &label, int context); @@ -45,7 +45,7 @@ class CGUIInfoColor private: int m_info = 0; - UTILS::COLOR::Color m_color; + KODI::UTILS::COLOR::Color m_color; }; } // namespace GUIINFO diff --git a/xbmc/interfaces/legacy/Control.h b/xbmc/interfaces/legacy/Control.h index 0c56a353b5387..8779cad73575c 100644 --- a/xbmc/interfaces/legacy/Control.h +++ b/xbmc/interfaces/legacy/Control.h @@ -693,7 +693,7 @@ namespace XBMCAddon #endif #ifndef SWIG - UTILS::COLOR::Color color; + KODI::UTILS::COLOR::Color color; std::string strTextureUp; std::string strTextureDown; std::string strTextureUpFocus; @@ -841,8 +841,8 @@ namespace XBMCAddon std::string strFont; std::string strText; - UTILS::COLOR::Color textColor; - UTILS::COLOR::Color disabledColor; + KODI::UTILS::COLOR::Color textColor; + KODI::UTILS::COLOR::Color disabledColor; uint32_t align; bool bHasPath = false; int iAngle = 0; @@ -1038,8 +1038,8 @@ namespace XBMCAddon std::string strText; std::string strTextureFocus; std::string strTextureNoFocus; - UTILS::COLOR::Color textColor; - UTILS::COLOR::Color disabledColor; + KODI::UTILS::COLOR::Color textColor; + KODI::UTILS::COLOR::Color disabledColor; uint32_t align; CGUIControl* Create() override; @@ -1608,8 +1608,8 @@ namespace XBMCAddon std::string strFont; AddonClass::Ref pControlSpin; - UTILS::COLOR::Color textColor; - UTILS::COLOR::Color selectedColor; + KODI::UTILS::COLOR::Color textColor; + KODI::UTILS::COLOR::Color selectedColor; std::string strTextureButton; std::string strTextureButtonFocus; @@ -1761,7 +1761,7 @@ namespace XBMCAddon #ifndef SWIG std::string strFont; - UTILS::COLOR::Color textColor; + KODI::UTILS::COLOR::Color textColor; std::vector vecLabels; uint32_t align; @@ -1965,7 +1965,7 @@ namespace XBMCAddon #ifndef SWIG std::string strFont; - UTILS::COLOR::Color textColor; + KODI::UTILS::COLOR::Color textColor; CGUIControl* Create() override; @@ -2082,7 +2082,7 @@ namespace XBMCAddon std::string strFileName; int aspectRatio = 0; - UTILS::COLOR::Color colorDiffuse; + KODI::UTILS::COLOR::Color colorDiffuse; CGUIControl* Create() override; #endif @@ -2223,7 +2223,7 @@ namespace XBMCAddon std::string strTextureBg; std::string strTextureOverlay; int aspectRatio = 0; - UTILS::COLOR::Color colorDiffuse; + KODI::UTILS::COLOR::Color colorDiffuse; CGUIControl* Create() override; ControlProgress() = default; @@ -2434,10 +2434,10 @@ namespace XBMCAddon int textOffsetX = 0; int textOffsetY = 0; - UTILS::COLOR::Color align; + KODI::UTILS::COLOR::Color align; std::string strFont; - UTILS::COLOR::Color textColor; - UTILS::COLOR::Color disabledColor; + KODI::UTILS::COLOR::Color textColor; + KODI::UTILS::COLOR::Color disabledColor; int iAngle = 0; int shadowColor = 0; int focusedColor = 0; @@ -2738,14 +2738,14 @@ namespace XBMCAddon std::string strTextureRadioOffNoFocus; std::string strTextureRadioOnDisabled; std::string strTextureRadioOffDisabled; - UTILS::COLOR::Color textColor; - UTILS::COLOR::Color disabledColor; + KODI::UTILS::COLOR::Color textColor; + KODI::UTILS::COLOR::Color disabledColor; int textOffsetX = 0; int textOffsetY = 0; uint32_t align; int iAngle = 0; - UTILS::COLOR::Color shadowColor; - UTILS::COLOR::Color focusedColor; + KODI::UTILS::COLOR::Color shadowColor; + KODI::UTILS::COLOR::Color focusedColor; CGUIControl* Create() override; diff --git a/xbmc/pictures/SlideShowPicture.cpp b/xbmc/pictures/SlideShowPicture.cpp index b66c27520e533..64dbbea0d055e 100644 --- a/xbmc/pictures/SlideShowPicture.cpp +++ b/xbmc/pictures/SlideShowPicture.cpp @@ -36,6 +36,8 @@ static float zoomamount[10] = { 1.0f, 1.2f, 1.5f, 2.0f, 2.8f, 4.0f, 6.0f, 9.0f, 13.5f, 20.0f }; +using KODI::UTILS::COLOR::Color; + CSlideShowPic::CSlideShowPic() : m_pImage(nullptr) { m_bIsLoaded = false; @@ -694,7 +696,7 @@ void CSlideShowPic::UpdateAlpha() { assert(m_iCounter >= 0); - UTILS::COLOR::Color alpha = m_alpha; + Color alpha = m_alpha; if (m_iCounter < m_transitionStart.length) { // do start transition @@ -702,16 +704,15 @@ void CSlideShowPic::UpdateAlpha() { case CROSSFADE: // fade in at 1x speed - alpha = - static_cast(static_cast(m_iCounter + 1) / - static_cast(m_transitionStart.length) * 255.0f); + alpha = static_cast(static_cast(m_iCounter + 1) / + static_cast(m_transitionStart.length) * 255.0f); break; case FADEIN_FADEOUT: // fade in at 2x speed, then keep solid - alpha = std::min(static_cast( - static_cast(m_iCounter + 1) / - static_cast(m_transitionStart.length) * 255.0f * 2), - UTILS::COLOR::Color{255}); + alpha = + std::min(static_cast(static_cast(m_iCounter + 1) / + static_cast(m_transitionStart.length) * 255.0f * 2), + Color{255}); break; default: alpha = 255; // opaque @@ -724,17 +725,17 @@ void CSlideShowPic::UpdateAlpha() { case CROSSFADE: // fade in at 1x speed - alpha = 255 - static_cast( - static_cast(m_iCounter - m_transitionEnd.start + 1) / - static_cast(m_transitionEnd.length) * 255.0f); + alpha = + 255 - static_cast(static_cast(m_iCounter - m_transitionEnd.start + 1) / + static_cast(m_transitionEnd.length) * 255.0f); break; case FADEIN_FADEOUT: // fade in at 2x speed, then keep solid - alpha = std::min(static_cast( - static_cast(m_transitionEnd.length - m_iCounter + - m_transitionEnd.start + 1) / - static_cast(m_transitionEnd.length) * 255.0f * 2), - UTILS::COLOR::Color{255}); + alpha = + std::min(static_cast(static_cast(m_transitionEnd.length - m_iCounter + + m_transitionEnd.start + 1) / + static_cast(m_transitionEnd.length) * 255.0f * 2), + Color{255}); break; default: alpha = 255; // opaque diff --git a/xbmc/pictures/SlideShowPicture.h b/xbmc/pictures/SlideShowPicture.h index 6b480fb166211..0c368e429f148 100644 --- a/xbmc/pictures/SlideShowPicture.h +++ b/xbmc/pictures/SlideShowPicture.h @@ -82,7 +82,7 @@ class CSlideShowPic bool m_bCanMoveVertically; protected: - virtual void Render(float* x, float* y, CTexture* pTexture, UTILS::COLOR::Color color) = 0; + virtual void Render(float* x, float* y, CTexture* pTexture, KODI::UTILS::COLOR::Color color) = 0; private: void SetTexture_Internal(int iSlideNumber, @@ -102,7 +102,7 @@ class CSlideShowPic std::string m_strFileName; float m_fWidth; float m_fHeight; - UTILS::COLOR::Color m_alpha = 0; + KODI::UTILS::COLOR::Color m_alpha = 0; // stuff relative to middle position float m_fPosX; float m_fPosY; diff --git a/xbmc/pictures/SlideShowPictureDX.cpp b/xbmc/pictures/SlideShowPictureDX.cpp index 261cd31059032..b1ea44b00d85f 100644 --- a/xbmc/pictures/SlideShowPictureDX.cpp +++ b/xbmc/pictures/SlideShowPictureDX.cpp @@ -49,7 +49,10 @@ bool CSlideShowPicDX::UpdateVertexBuffer(Vertex* vertices) return false; } -void CSlideShowPicDX::Render(float* x, float* y, CTexture* pTexture, UTILS::COLOR::Color color) +void CSlideShowPicDX::Render(float* x, + float* y, + CTexture* pTexture, + KODI::UTILS::COLOR::Color color) { Vertex vertex[5]; for (int i = 0; i < 4; i++) diff --git a/xbmc/pictures/SlideShowPictureDX.h b/xbmc/pictures/SlideShowPictureDX.h index 0d5fb6212ea62..88f6d03826452 100644 --- a/xbmc/pictures/SlideShowPictureDX.h +++ b/xbmc/pictures/SlideShowPictureDX.h @@ -22,7 +22,7 @@ class CSlideShowPicDX : public CSlideShowPic ~CSlideShowPicDX() override = default; protected: - void Render(float* x, float* y, CTexture* pTexture, UTILS::COLOR::Color color) override; + void Render(float* x, float* y, CTexture* pTexture, KODI::UTILS::COLOR::Color color) override; private: bool UpdateVertexBuffer(Vertex* vertices); diff --git a/xbmc/pictures/SlideShowPictureGL.cpp b/xbmc/pictures/SlideShowPictureGL.cpp index ee7ddb2a9f637..8f38efd7d7a92 100644 --- a/xbmc/pictures/SlideShowPictureGL.cpp +++ b/xbmc/pictures/SlideShowPictureGL.cpp @@ -13,12 +13,14 @@ #include "rendering/gl/RenderSystemGL.h" #include "utils/GLUtils.h" +using KODI::UTILS::COLOR::Color; + std::unique_ptr CSlideShowPic::CreateSlideShowPicture() { return std::make_unique(); } -void CSlideShowPicGL::Render(float* x, float* y, CTexture* pTexture, UTILS::COLOR::Color color) +void CSlideShowPicGL::Render(float* x, float* y, CTexture* pTexture, Color color) { CRenderSystemGL* renderSystem = dynamic_cast(CServiceBroker::GetRenderSystem()); if (pTexture) diff --git a/xbmc/pictures/SlideShowPictureGL.h b/xbmc/pictures/SlideShowPictureGL.h index ec2a864934622..c9670732ee04e 100644 --- a/xbmc/pictures/SlideShowPictureGL.h +++ b/xbmc/pictures/SlideShowPictureGL.h @@ -19,5 +19,5 @@ class CSlideShowPicGL : public CSlideShowPic ~CSlideShowPicGL() override = default; protected: - void Render(float* x, float* y, CTexture* pTexture, UTILS::COLOR::Color color) override; + void Render(float* x, float* y, CTexture* pTexture, KODI::UTILS::COLOR::Color color) override; }; diff --git a/xbmc/pictures/SlideShowPictureGLES.cpp b/xbmc/pictures/SlideShowPictureGLES.cpp index de41bbe77fb0c..8dbaa13a09b3f 100644 --- a/xbmc/pictures/SlideShowPictureGLES.cpp +++ b/xbmc/pictures/SlideShowPictureGLES.cpp @@ -19,7 +19,10 @@ std::unique_ptr CSlideShowPic::CreateSlideShowPicture() return std::make_unique(); } -void CSlideShowPicGLES::Render(float* x, float* y, CTexture* pTexture, UTILS::COLOR::Color color) +void CSlideShowPicGLES::Render(float* x, + float* y, + CTexture* pTexture, + KODI::UTILS::COLOR::Color color) { CRenderSystemGLES* renderSystem = dynamic_cast(CServiceBroker::GetRenderSystem()); diff --git a/xbmc/pictures/SlideShowPictureGLES.h b/xbmc/pictures/SlideShowPictureGLES.h index b0cc0b6306397..8739ccfc8125f 100644 --- a/xbmc/pictures/SlideShowPictureGLES.h +++ b/xbmc/pictures/SlideShowPictureGLES.h @@ -19,5 +19,5 @@ class CSlideShowPicGLES : public CSlideShowPic ~CSlideShowPicGLES() override = default; protected: - void Render(float* x, float* y, CTexture* pTexture, UTILS::COLOR::Color color) override; + void Render(float* x, float* y, CTexture* pTexture, KODI::UTILS::COLOR::Color color) override; }; diff --git a/xbmc/rendering/RenderSystem.h b/xbmc/rendering/RenderSystem.h index 8e188cdf00f19..20549f5b6e4d3 100644 --- a/xbmc/rendering/RenderSystem.h +++ b/xbmc/rendering/RenderSystem.h @@ -45,7 +45,7 @@ class CRenderSystemBase virtual bool EndRender() = 0; virtual void PresentRender(bool rendered, bool videoLayer) = 0; virtual void InvalidateColorBuffer() {} - virtual bool ClearBuffers(UTILS::COLOR::Color color) = 0; + virtual bool ClearBuffers(KODI::UTILS::COLOR::Color color) = 0; virtual bool IsExtSupported(const char* extension) const = 0; virtual void SetViewPort(const CRect& viewPort) = 0; diff --git a/xbmc/rendering/dx/RenderSystemDX.cpp b/xbmc/rendering/dx/RenderSystemDX.cpp index dd369e284c141..071d2116ad5d1 100644 --- a/xbmc/rendering/dx/RenderSystemDX.cpp +++ b/xbmc/rendering/dx/RenderSystemDX.cpp @@ -326,7 +326,7 @@ bool CRenderSystemDX::EndRender() return true; } -bool CRenderSystemDX::ClearBuffers(UTILS::COLOR::Color color) +bool CRenderSystemDX::ClearBuffers(KODI::UTILS::COLOR::Color color) { if (!m_bRenderCreated) return false; diff --git a/xbmc/rendering/dx/RenderSystemDX.h b/xbmc/rendering/dx/RenderSystemDX.h index df5a7aa56064f..b1af48bb13098 100644 --- a/xbmc/rendering/dx/RenderSystemDX.h +++ b/xbmc/rendering/dx/RenderSystemDX.h @@ -34,7 +34,7 @@ class CRenderSystemDX : public CRenderSystemBase, DX::IDeviceNotify bool BeginRender() override; bool EndRender() override; void PresentRender(bool rendered, bool videoLayer) override; - bool ClearBuffers(UTILS::COLOR::Color color) override; + bool ClearBuffers(KODI::UTILS::COLOR::Color color) override; void SetViewPort(const CRect& viewPort) override; void GetViewPort(CRect& viewPort) override; void RestoreViewPort() override; diff --git a/xbmc/rendering/gl/RenderSystemGL.cpp b/xbmc/rendering/gl/RenderSystemGL.cpp index 597bb152e4942..467cc038c0eac 100644 --- a/xbmc/rendering/gl/RenderSystemGL.cpp +++ b/xbmc/rendering/gl/RenderSystemGL.cpp @@ -297,7 +297,7 @@ void CRenderSystemGL::InvalidateColorBuffer() glClear(GL_DEPTH_BUFFER_BIT); } -bool CRenderSystemGL::ClearBuffers(UTILS::COLOR::Color color) +bool CRenderSystemGL::ClearBuffers(KODI::UTILS::COLOR::Color color) { if (!m_bRenderCreated) return false; diff --git a/xbmc/rendering/gl/RenderSystemGL.h b/xbmc/rendering/gl/RenderSystemGL.h index 920024a37e2fa..2e6e72ea911e6 100644 --- a/xbmc/rendering/gl/RenderSystemGL.h +++ b/xbmc/rendering/gl/RenderSystemGL.h @@ -76,7 +76,7 @@ class CRenderSystemGL : public CRenderSystemBase bool EndRender() override; void PresentRender(bool rendered, bool videoLayer) override; void InvalidateColorBuffer() override; - bool ClearBuffers(UTILS::COLOR::Color color) override; + bool ClearBuffers(KODI::UTILS::COLOR::Color color) override; bool IsExtSupported(const char* extension) const override; void SetVSync(bool vsync); diff --git a/xbmc/rendering/gles/RenderSystemGLES.cpp b/xbmc/rendering/gles/RenderSystemGLES.cpp index d197caf167409..471938a7142b0 100644 --- a/xbmc/rendering/gles/RenderSystemGLES.cpp +++ b/xbmc/rendering/gles/RenderSystemGLES.cpp @@ -211,7 +211,7 @@ void CRenderSystemGLES::InvalidateColorBuffer() glClear(GL_DEPTH_BUFFER_BIT); } -bool CRenderSystemGLES::ClearBuffers(UTILS::COLOR::Color color) +bool CRenderSystemGLES::ClearBuffers(KODI::UTILS::COLOR::Color color) { if (!m_bRenderCreated) return false; diff --git a/xbmc/rendering/gles/RenderSystemGLES.h b/xbmc/rendering/gles/RenderSystemGLES.h index a6be00933ea1b..9c19bf6c28be9 100644 --- a/xbmc/rendering/gles/RenderSystemGLES.h +++ b/xbmc/rendering/gles/RenderSystemGLES.h @@ -86,7 +86,7 @@ class CRenderSystemGLES : public CRenderSystemBase bool EndRender() override; void PresentRender(bool rendered, bool videoLayer) override; void InvalidateColorBuffer() override; - bool ClearBuffers(UTILS::COLOR::Color color) override; + bool ClearBuffers(KODI::UTILS::COLOR::Color color) override; bool IsExtSupported(const char* extension) const override; void SetVSync(bool vsync); diff --git a/xbmc/settings/SettingConditions.cpp b/xbmc/settings/SettingConditions.cpp index 1f11a4995eef0..e4190a53f7ea5 100644 --- a/xbmc/settings/SettingConditions.cpp +++ b/xbmc/settings/SettingConditions.cpp @@ -159,7 +159,7 @@ bool HasSubtitlesFontExtensions(const std::string& condition, if (!settingStr) return false; - return UTILS::FONT::IsSupportedFontExtension(settingStr->GetValue()); + return KODI::UTILS::FONT::IsSupportedFontExtension(settingStr->GetValue()); } bool ProfileCanWriteDatabase(const std::string& condition, diff --git a/xbmc/utils/ColorUtils.cpp b/xbmc/utils/ColorUtils.cpp index dd8e4c03f24c4..0c861cfb2b4b8 100644 --- a/xbmc/utils/ColorUtils.cpp +++ b/xbmc/utils/ColorUtils.cpp @@ -14,7 +14,8 @@ #include #include -using namespace UTILS::COLOR; +namespace KODI::UTILS::COLOR +{ namespace { @@ -63,13 +64,13 @@ void GetHSLValues(ColorInfo& colorInfo) } // unnamed namespace -Color UTILS::COLOR::ChangeOpacity(const Color argb, const float opacity) +Color ChangeOpacity(const Color argb, const float opacity) { int newAlpha = static_cast(std::ceil(((argb >> 24) & 0xff) * opacity)); return (argb & 0x00FFFFFF) | (newAlpha << 24); }; -Color UTILS::COLOR::ConvertToRGBA(const Color argb) +Color ConvertToRGBA(const Color argb) { return ((argb & 0x00FF0000) << 8) | //RR______ ((argb & 0x0000FF00) << 8) | //__GG____ @@ -77,7 +78,7 @@ Color UTILS::COLOR::ConvertToRGBA(const Color argb) ((argb & 0xFF000000) >> 24); //______AA } -Color UTILS::COLOR::ConvertToARGB(const Color rgba) +Color ConvertToARGB(const Color rgba) { return ((rgba & 0x000000FF) << 24) | //AA_____ ((rgba & 0xFF000000) >> 8) | //__RR____ @@ -85,26 +86,26 @@ Color UTILS::COLOR::ConvertToARGB(const Color rgba) ((rgba & 0x0000FF00) >> 8); //______BB } -Color UTILS::COLOR::ConvertToBGR(const Color argb) +Color ConvertToBGR(const Color argb) { return (argb & 0x00FF0000) >> 16 | //____RR (argb & 0x0000FF00) | //__GG__ (argb & 0x000000FF) << 16; //BB____ } -Color UTILS::COLOR::ConvertHexToColor(const std::string& hexColor) +Color ConvertHexToColor(const std::string& hexColor) { Color value = 0; std::sscanf(hexColor.c_str(), "%x", &value); return value; } -Color UTILS::COLOR::ConvertIntToRGB(int r, int g, int b) +Color ConvertIntToRGB(int r, int g, int b) { return ((r & 0xff) << 16) + ((g & 0xff) << 8) + (b & 0xff); } -ColorInfo UTILS::COLOR::MakeColorInfo(const Color& argb) +ColorInfo MakeColorInfo(const Color& argb) { ColorInfo colorInfo; colorInfo.colorARGB = argb; @@ -112,7 +113,7 @@ ColorInfo UTILS::COLOR::MakeColorInfo(const Color& argb) return colorInfo; } -ColorInfo UTILS::COLOR::MakeColorInfo(const std::string& hexColor) +ColorInfo MakeColorInfo(const std::string& hexColor) { ColorInfo colorInfo; colorInfo.colorARGB = ConvertHexToColor(hexColor); @@ -120,8 +121,8 @@ ColorInfo UTILS::COLOR::MakeColorInfo(const std::string& hexColor) return colorInfo; } -bool UTILS::COLOR::comparePairColorInfo(const std::pair& a, - const std::pair& b) +bool comparePairColorInfo(const std::pair& a, + const std::pair& b) { if (a.second.hue == b.second.hue) { @@ -134,7 +135,7 @@ bool UTILS::COLOR::comparePairColorInfo(const std::pair& return (a.second.hue < b.second.hue); } -ColorFloats UTILS::COLOR::ConvertToFloats(const Color argb) +ColorFloats ConvertToFloats(const Color argb) { ColorFloats c; c.alpha = static_cast((argb >> 24) & 0xFF) * (1.0f / 255.0f); @@ -144,7 +145,9 @@ ColorFloats UTILS::COLOR::ConvertToFloats(const Color argb) return c; } -std::string UTILS::COLOR::ConvertToHexRGB(const Color argb) +std::string ConvertToHexRGB(const Color argb) { return StringUtils::Format("{:06X}", argb & ~0xFF000000); } + +} // namespace KODI::UTILS::COLOR \ No newline at end of file diff --git a/xbmc/utils/ColorUtils.h b/xbmc/utils/ColorUtils.h index 79c92e2db4b1b..cfa8452282d21 100644 --- a/xbmc/utils/ColorUtils.h +++ b/xbmc/utils/ColorUtils.h @@ -15,9 +15,7 @@ #include #include -namespace UTILS -{ -namespace COLOR +namespace KODI::UTILS::COLOR { typedef uint32_t Color; @@ -165,5 +163,4 @@ ColorFloats ConvertToFloats(const Color argb); */ std::string ConvertToHexRGB(const Color argb); -} // namespace COLOR -} // namespace UTILS +} // namespace KODI::UTILS::COLOR diff --git a/xbmc/utils/FontUtils.cpp b/xbmc/utils/FontUtils.cpp index 8e6af68907e14..c5e47698506eb 100644 --- a/xbmc/utils/FontUtils.cpp +++ b/xbmc/utils/FontUtils.cpp @@ -26,6 +26,9 @@ using namespace XFILE; +namespace KODI::UTILS::FONT +{ + namespace { // \brief Get font family from SFNT table entries @@ -81,8 +84,7 @@ std::string GetFamilyNameFromSfnt(FT_Face face) } } // unnamed namespace -bool UTILS::FONT::GetFontFamilyNames(const std::vector& buffer, - std::set& familyNames) +bool GetFontFamilyNames(const std::vector& buffer, std::set& familyNames) { FT_Library m_library{nullptr}; FT_Init_FreeType(&m_library); @@ -152,8 +154,7 @@ bool UTILS::FONT::GetFontFamilyNames(const std::vector& buffer, return true; } -bool UTILS::FONT::GetFontFamilyNames(const std::string& filepath, - std::set& familyNames) +bool GetFontFamilyNames(const std::string& filepath, std::set& familyNames) { std::vector buffer; if (filepath.empty()) @@ -167,7 +168,7 @@ bool UTILS::FONT::GetFontFamilyNames(const std::string& filepath, return GetFontFamilyNames(buffer, familyNames); } -std::string UTILS::FONT::GetFontFamily(std::vector& buffer) +std::string GetFontFamily(std::vector& buffer) { FT_Library m_library{nullptr}; FT_Init_FreeType(&m_library); @@ -204,7 +205,7 @@ std::string UTILS::FONT::GetFontFamily(std::vector& buffer) return familyName; } -std::string UTILS::FONT::GetFontFamily(const std::string& filepath) +std::string GetFontFamily(const std::string& filepath) { std::vector buffer; if (filepath.empty()) @@ -217,19 +218,18 @@ std::string UTILS::FONT::GetFontFamily(const std::string& filepath) return GetFontFamily(buffer); } -bool UTILS::FONT::IsSupportedFontExtension(const std::string& filepath) +bool IsSupportedFontExtension(const std::string& filepath) { - return URIUtils::HasExtension(filepath, UTILS::FONT::SUPPORTED_EXTENSIONS_MASK); + return URIUtils::HasExtension(filepath, SUPPORTED_EXTENSIONS_MASK); } -void UTILS::FONT::ClearTemporaryFonts() +void ClearTemporaryFonts() { - if (!CDirectory::Exists(UTILS::FONT::FONTPATH::TEMP)) + if (!CDirectory::Exists(FONTPATH::TEMP)) return; CFileItemList items; - CDirectory::GetDirectory(UTILS::FONT::FONTPATH::TEMP, items, - UTILS::FONT::SUPPORTED_EXTENSIONS_MASK, + CDirectory::GetDirectory(FONTPATH::TEMP, items, SUPPORTED_EXTENSIONS_MASK, DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_BYPASS_CACHE | DIR_FLAG_GET_HIDDEN); for (const auto& item : items) { @@ -240,10 +240,10 @@ void UTILS::FONT::ClearTemporaryFonts() } } -std::string UTILS::FONT::FONTPATH::GetSystemFontPath(const std::string& filename) +std::string FONTPATH::GetSystemFontPath(const std::string& filename) { - std::string fontPath = URIUtils::AddFileToFolder( - CSpecialProtocol::TranslatePath(UTILS::FONT::FONTPATH::SYSTEM), filename); + std::string fontPath = + URIUtils::AddFileToFolder(CSpecialProtocol::TranslatePath(FONTPATH::SYSTEM), filename); if (XFILE::CFile::Exists(fontPath)) { return CSpecialProtocol::TranslatePath(fontPath); @@ -252,3 +252,5 @@ std::string UTILS::FONT::FONTPATH::GetSystemFontPath(const std::string& filename CLog::LogF(LOGERROR, "Could not find application system font {}", filename); return ""; } + +} // namespace KODI::UTILS::FONT diff --git a/xbmc/utils/FontUtils.h b/xbmc/utils/FontUtils.h index e5291ce28c780..5836b9fbd0d87 100644 --- a/xbmc/utils/FontUtils.h +++ b/xbmc/utils/FontUtils.h @@ -13,9 +13,7 @@ #include #include -namespace UTILS -{ -namespace FONT +namespace KODI::UTILS::FONT { constexpr const char* SUPPORTED_EXTENSIONS_MASK = ".ttf|.ttc|.otf"; @@ -87,5 +85,4 @@ bool IsSupportedFontExtension(const std::string& filepath); */ void ClearTemporaryFonts(); -} // namespace FONT -} // namespace UTILS +} // namespace KODI::UTILS::FONT diff --git a/xbmc/utils/MovingSpeed.cpp b/xbmc/utils/MovingSpeed.cpp index 1e4269eed515f..050368aebc54d 100644 --- a/xbmc/utils/MovingSpeed.cpp +++ b/xbmc/utils/MovingSpeed.cpp @@ -12,21 +12,24 @@ #include "utils/TimeUtils.h" #include "utils/log.h" -void UTILS::MOVING_SPEED::CMovingSpeed::AddEventConfig(uint32_t eventId, - float acceleration, - float maxVelocity, - uint32_t resetTimeout) +namespace KODI::UTILS::MOVING_SPEED +{ + +void CMovingSpeed::AddEventConfig(uint32_t eventId, + float acceleration, + float maxVelocity, + uint32_t resetTimeout) { EventCfg eventCfg{acceleration, maxVelocity, resetTimeout}; m_eventsData.emplace(eventId, EventData{eventCfg}); } -void UTILS::MOVING_SPEED::CMovingSpeed::AddEventConfig(uint32_t eventId, EventCfg event) +void CMovingSpeed::AddEventConfig(uint32_t eventId, EventCfg event) { m_eventsData.emplace(eventId, EventData{event}); } -void UTILS::MOVING_SPEED::CMovingSpeed::AddEventMapConfig(MapEventConfig& configs) +void CMovingSpeed::AddEventMapConfig(MapEventConfig& configs) { for (auto& cfg : configs) { @@ -34,7 +37,7 @@ void UTILS::MOVING_SPEED::CMovingSpeed::AddEventMapConfig(MapEventConfig& config } } -void UTILS::MOVING_SPEED::CMovingSpeed::Reset() +void CMovingSpeed::Reset() { m_currentEventId = 0; for (auto& eventPair : m_eventsData) @@ -43,7 +46,7 @@ void UTILS::MOVING_SPEED::CMovingSpeed::Reset() } } -void UTILS::MOVING_SPEED::CMovingSpeed::Reset(uint32_t eventId) +void CMovingSpeed::Reset(uint32_t eventId) { auto mapIt = m_eventsData.find(eventId); if (mapIt == m_eventsData.end()) @@ -58,7 +61,7 @@ void UTILS::MOVING_SPEED::CMovingSpeed::Reset(uint32_t eventId) } } -float UTILS::MOVING_SPEED::CMovingSpeed::GetUpdatedDistance(uint32_t eventId) +float CMovingSpeed::GetUpdatedDistance(uint32_t eventId) { auto mapEventIt = m_eventsData.find(eventId); @@ -104,7 +107,7 @@ float UTILS::MOVING_SPEED::CMovingSpeed::GetUpdatedDistance(uint32_t eventId) return distance; } -UTILS::MOVING_SPEED::EventType UTILS::MOVING_SPEED::ParseEventType(std::string_view eventType) +EventType ParseEventType(std::string_view eventType) { if (eventType == "up") return EventType::UP; @@ -120,3 +123,5 @@ UTILS::MOVING_SPEED::EventType UTILS::MOVING_SPEED::ParseEventType(std::string_v return EventType::NONE; } } + +} // namespace KODI::UTILS::MOVING_SPEED \ No newline at end of file diff --git a/xbmc/utils/MovingSpeed.h b/xbmc/utils/MovingSpeed.h index 70214a6671d7e..076d43a2fda89 100644 --- a/xbmc/utils/MovingSpeed.h +++ b/xbmc/utils/MovingSpeed.h @@ -12,9 +12,7 @@ #include #include -namespace UTILS -{ -namespace MOVING_SPEED +namespace KODI::UTILS::MOVING_SPEED { struct EventCfg @@ -148,5 +146,4 @@ class CMovingSpeed */ EventType ParseEventType(std::string_view eventType); -} // namespace MOVING_SPEED -} // namespace UTILS +} // namespace KODI::UTILS::MOVING_SPEED diff --git a/xbmc/utils/TransformMatrix.h b/xbmc/utils/TransformMatrix.h index 0287ad55c534c..20e6a5291a862 100644 --- a/xbmc/utils/TransformMatrix.h +++ b/xbmc/utils/TransformMatrix.h @@ -259,17 +259,22 @@ class TransformMatrix return m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3]; } - inline UTILS::COLOR::Color TransformAlpha(UTILS::COLOR::Color color) const XBMC_FORCE_INLINE + inline KODI::UTILS::COLOR::Color TransformAlpha(KODI::UTILS::COLOR::Color color) const + XBMC_FORCE_INLINE { - return static_cast(color * alpha); + return static_cast(color * alpha); } - inline UTILS::COLOR::Color TransformColor(UTILS::COLOR::Color color) const XBMC_FORCE_INLINE + inline KODI::UTILS::COLOR::Color TransformColor(KODI::UTILS::COLOR::Color color) const + XBMC_FORCE_INLINE { - UTILS::COLOR::Color a = static_cast(((color >> 24) & 0xff) * alpha); - UTILS::COLOR::Color r = static_cast(((color >> 16) & 0xff) * red); - UTILS::COLOR::Color g = static_cast(((color >> 8) & 0xff) * green); - UTILS::COLOR::Color b = static_cast(((color)&0xff) * blue); + KODI::UTILS::COLOR::Color a = + static_cast(((color >> 24) & 0xff) * alpha); + KODI::UTILS::COLOR::Color r = + static_cast(((color >> 16) & 0xff) * red); + KODI::UTILS::COLOR::Color g = + static_cast(((color >> 8) & 0xff) * green); + KODI::UTILS::COLOR::Color b = static_cast(((color) & 0xff) * blue); if (a > 255) a = 255; if (r > 255) diff --git a/xbmc/utils/guilib/GUIContentUtils.cpp b/xbmc/utils/guilib/GUIContentUtils.cpp index d98fd66a2f643..6e403a6de2f86 100644 --- a/xbmc/utils/guilib/GUIContentUtils.cpp +++ b/xbmc/utils/guilib/GUIContentUtils.cpp @@ -17,7 +17,7 @@ #include "video/VideoInfoTag.h" #include "video/dialogs/GUIDialogVideoInfo.h" -using namespace UTILS::GUILIB; +using namespace KODI::UTILS::GUILIB; bool CGUIContentUtils::HasInfoForItem(const CFileItem& item) { diff --git a/xbmc/utils/guilib/GUIContentUtils.h b/xbmc/utils/guilib/GUIContentUtils.h index d395ecbb81ebe..2b2eea9a43b2c 100644 --- a/xbmc/utils/guilib/GUIContentUtils.h +++ b/xbmc/utils/guilib/GUIContentUtils.h @@ -10,9 +10,7 @@ class CFileItem; -namespace UTILS -{ -namespace GUILIB +namespace KODI::UTILS::GUILIB { class CGUIContentUtils { @@ -29,5 +27,4 @@ class CGUIContentUtils */ static bool ShowInfoForItem(const CFileItem& item); }; -} // namespace GUILIB -} // namespace UTILS +} // namespace KODI::UTILS::GUILIB diff --git a/xbmc/video/PlayerController.h b/xbmc/video/PlayerController.h index b58300cbb2897..37e3d08d8f485 100644 --- a/xbmc/video/PlayerController.h +++ b/xbmc/video/PlayerController.h @@ -53,5 +53,5 @@ class CPlayerController : public ISliderCallback, public KODI::ACTION::IActionLi void ShowSlider(int action, int label, float value, float min, float delta, float max, bool modal = false); int m_sliderAction = 0; ///< \brief set to the action id for a slider being displayed \sa ShowSlider - UTILS::MOVING_SPEED::CMovingSpeed m_movingSpeed; + KODI::UTILS::MOVING_SPEED::CMovingSpeed m_movingSpeed; }; diff --git a/xbmc/video/Teletext.cpp b/xbmc/video/Teletext.cpp index 80add43ad055e..471218d5d6f7a 100644 --- a/xbmc/video/Teletext.cpp +++ b/xbmc/video/Teletext.cpp @@ -27,6 +27,7 @@ #include using namespace std::chrono_literals; +using KODI::UTILS::COLOR::Color; static inline void SDL_memset4(uint32_t* dst, uint32_t val, size_t len) { @@ -685,7 +686,7 @@ bool CTeletextDecoder::InitDecoder() /* set variable screeninfo for double buffering */ m_YOffset = 0; - m_TextureBuffer = new UTILS::COLOR::Color[4 * m_RenderInfo.Height * m_RenderInfo.Width]; + m_TextureBuffer = new Color[4 * m_RenderInfo.Height * m_RenderInfo.Width]; ClearFB(GetColorRGB(TXT_ColorTransp)); ClearBB(GetColorRGB(TXT_ColorTransp)); /* initialize backbuffer */ @@ -1914,9 +1915,9 @@ void CTeletextDecoder::RenderCharBB(int Char, TextPageAttr_t *Attribute) void CTeletextDecoder::CopyBB2FB() { - UTILS::COLOR::Color *src, *dst, *topsrc; + Color *src, *dst, *topsrc; int screenwidth; - UTILS::COLOR::Color fillcolor; + Color fillcolor; std::unique_lock lock(m_txtCache->m_critSection); @@ -2019,44 +2020,42 @@ void CTeletextDecoder::SetPosX(int column) m_RenderInfo.PosX += GetCurFontWidth(); } -void CTeletextDecoder::ClearBB(UTILS::COLOR::Color Color) +void CTeletextDecoder::ClearBB(Color Color) { SDL_memset4(m_TextureBuffer + (m_RenderInfo.Height-m_YOffset)*m_RenderInfo.Width, Color, m_RenderInfo.Width*m_RenderInfo.Height); } -void CTeletextDecoder::ClearFB(UTILS::COLOR::Color Color) +void CTeletextDecoder::ClearFB(Color Color) { SDL_memset4(m_TextureBuffer + m_RenderInfo.Width*m_YOffset, Color, m_RenderInfo.Width*m_RenderInfo.Height); } -void CTeletextDecoder::FillBorder(UTILS::COLOR::Color Color) +void CTeletextDecoder::FillBorder(Color Color) { FillRect(m_TextureBuffer + (m_RenderInfo.Height-m_YOffset)*m_RenderInfo.Width, m_RenderInfo.Width, 0, 25*m_RenderInfo.FontHeight, m_RenderInfo.Width, m_RenderInfo.Height-(25*m_RenderInfo.FontHeight), Color); FillRect(m_TextureBuffer + m_RenderInfo.Width*m_YOffset, m_RenderInfo.Width, 0, 25*m_RenderInfo.FontHeight, m_RenderInfo.Width, m_RenderInfo.Height-(25*m_RenderInfo.FontHeight), Color); } -void CTeletextDecoder::FillRect( - UTILS::COLOR::Color* buffer, int xres, int x, int y, int w, int h, UTILS::COLOR::Color Color) +void CTeletextDecoder::FillRect(Color* buffer, int xres, int x, int y, int w, int h, Color color) { if (!buffer) return; - UTILS::COLOR::Color* p = buffer + x + y * xres; + Color* p = buffer + x + y * xres; if (w > 0) { for ( ; h > 0 ; h--) { - SDL_memset4(p, Color, w); + SDL_memset4(p, color, w); p += xres; } } } -void CTeletextDecoder::DrawVLine( - UTILS::COLOR::Color* lfb, int xres, int x, int y, int l, UTILS::COLOR::Color color) +void CTeletextDecoder::DrawVLine(Color* lfb, int xres, int x, int y, int l, Color color) { if (!lfb) return; - UTILS::COLOR::Color* p = lfb + x + y * xres; + Color* p = lfb + x + y * xres; for ( ; l > 0 ; l--) { @@ -2065,8 +2064,7 @@ void CTeletextDecoder::DrawVLine( } } -void CTeletextDecoder::DrawHLine( - UTILS::COLOR::Color* lfb, int xres, int x, int y, int l, UTILS::COLOR::Color color) +void CTeletextDecoder::DrawHLine(Color* lfb, int xres, int x, int y, int l, Color color) { if (!lfb) return; if (l > 0) @@ -2076,10 +2074,10 @@ void CTeletextDecoder::DrawHLine( void CTeletextDecoder::RenderDRCS( int xres, unsigned char* s, /* pointer to char data, parity undecoded */ - UTILS::COLOR::Color* d, /* pointer to frame buffer of top left pixel */ + Color* d, /* pointer to frame buffer of top left pixel */ unsigned char* ax, /* array[0..12] of x-offsets, array[0..10] of y-offsets for each pixel */ - UTILS::COLOR::Color fgcolor, - UTILS::COLOR::Color bgcolor) + Color fgcolor, + Color bgcolor) { if (d == NULL) return; @@ -2099,8 +2097,8 @@ void CTeletextDecoder::RenderDRCS( bit; bit >>= 1, x++) /* bit mask (MSB left), column counter */ { - UTILS::COLOR::Color f1 = (c1 & bit) ? fgcolor : bgcolor; - UTILS::COLOR::Color f2 = (c2 & bit) ? fgcolor : bgcolor; + Color f1 = (c1 & bit) ? fgcolor : bgcolor; + Color f2 = (c2 & bit) ? fgcolor : bgcolor; for (int i = 0; i < h; i++) { if (ax[x+1] > ax[x]) @@ -2115,15 +2113,8 @@ void CTeletextDecoder::RenderDRCS( } } -void CTeletextDecoder::FillRectMosaicSeparated(UTILS::COLOR::Color* lfb, - int xres, - int x, - int y, - int w, - int h, - UTILS::COLOR::Color fgcolor, - UTILS::COLOR::Color bgcolor, - int set) +void CTeletextDecoder::FillRectMosaicSeparated( + Color* lfb, int xres, int x, int y, int w, int h, Color fgcolor, Color bgcolor, int set) { if (!lfb) return; FillRect(lfb,xres,x, y, w, h, bgcolor); @@ -2133,17 +2124,10 @@ void CTeletextDecoder::FillRectMosaicSeparated(UTILS::COLOR::Color* lfb, } } -void CTeletextDecoder::FillTrapez(UTILS::COLOR::Color* lfb, - int xres, - int x0, - int y0, - int l0, - int xoffset1, - int h, - int l1, - UTILS::COLOR::Color color) +void CTeletextDecoder::FillTrapez( + Color* lfb, int xres, int x0, int y0, int l0, int xoffset1, int h, int l1, Color color) { - UTILS::COLOR::Color* p = lfb + x0 + y0 * xres; + Color* p = lfb + x0 + y0 * xres; int xoffset, l; for (int yoffset = 0; yoffset < h; yoffset++) @@ -2156,10 +2140,10 @@ void CTeletextDecoder::FillTrapez(UTILS::COLOR::Color* lfb, } } -void CTeletextDecoder::FlipHorz(UTILS::COLOR::Color* lfb, int xres, int x, int y, int w, int h) +void CTeletextDecoder::FlipHorz(Color* lfb, int xres, int x, int y, int w, int h) { - UTILS::COLOR::Color buf[2048]; - UTILS::COLOR::Color* p = lfb + x + y * xres; + Color buf[2048]; + Color* p = lfb + x + y * xres; int w1,h1; for (h1 = 0 ; h1 < h ; h1++) @@ -2173,10 +2157,10 @@ void CTeletextDecoder::FlipHorz(UTILS::COLOR::Color* lfb, int xres, int x, int y } } -void CTeletextDecoder::FlipVert(UTILS::COLOR::Color* lfb, int xres, int x, int y, int w, int h) +void CTeletextDecoder::FlipVert(Color* lfb, int xres, int x, int y, int w, int h) { - UTILS::COLOR::Color buf[2048]; - UTILS::COLOR::Color *p = lfb + x + y * xres, *p1, *p2; + Color buf[2048]; + Color *p = lfb + x + y * xres, *p1, *p2; int h1; for (h1 = 0 ; h1 < h/2 ; h1++) @@ -2216,7 +2200,7 @@ int CTeletextDecoder::ShapeCoord(int param, int curfontwidth, int curFontHeight) } } -void CTeletextDecoder::DrawShape(UTILS::COLOR::Color* lfb, +void CTeletextDecoder::DrawShape(Color* lfb, int xres, int x, int y, @@ -2224,8 +2208,8 @@ void CTeletextDecoder::DrawShape(UTILS::COLOR::Color* lfb, int curfontwidth, int FontHeight, int curFontHeight, - UTILS::COLOR::Color fgcolor, - UTILS::COLOR::Color bgcolor, + Color fgcolor, + Color bgcolor, bool clear) { if (!lfb || shapenumber < 0x20 || shapenumber > 0x7e || (shapenumber == 0x7e && clear)) @@ -2312,7 +2296,7 @@ void CTeletextDecoder::RenderCharIntern(TextRenderInfo_t* RenderInfo, int Char, { int Row, Pitch; int glyph; - UTILS::COLOR::Color bgcolor, fgcolor; + Color bgcolor, fgcolor; int factor, xfactor; unsigned char *sbitbuffer; @@ -2404,7 +2388,7 @@ void CTeletextDecoder::RenderCharIntern(TextRenderInfo_t* RenderInfo, int Char, if (national_subset_local == NAT_AR) m_RenderInfo.TTFShiftY = backupTTFshiftY - 2; // for arabic TTF font should be shifted up slightly - UTILS::COLOR::Color* p; + Color* p; int f; /* running counter for zoom factor */ int he = m_sBit->height; // sbit->height should not be altered, I guess Row = factor * (m_Ascender - m_sBit->top + m_RenderInfo.TTFShiftY); @@ -2427,7 +2411,7 @@ void CTeletextDecoder::RenderCharIntern(TextRenderInfo_t* RenderInfo, int Char, for (Row = he; Row; Row--) /* row counts up, but down may be a little faster :) */ { int pixtodo = m_sBit->width; - UTILS::COLOR::Color* pstart = p; + Color* pstart = p; for (int Bit = xfactor * (m_sBit->left + m_RenderInfo.TTFShiftX); Bit > 0; Bit--) /* fill left margin */ { @@ -2440,7 +2424,7 @@ void CTeletextDecoder::RenderCharIntern(TextRenderInfo_t* RenderInfo, int Char, { for (int Bit = 0x80; Bit; Bit >>= 1) { - UTILS::COLOR::Color color; + Color color; if (--pixtodo < 0) break; @@ -2497,7 +2481,7 @@ void CTeletextDecoder::RenderCharIntern(TextRenderInfo_t* RenderInfo, int Char, } int CTeletextDecoder::RenderChar( - UTILS::COLOR::Color* buffer, // pointer to render buffer, min. FontHeight*2*xres + Color* buffer, // pointer to render buffer, min. FontHeight*2*xres int xres, // length of 1 line in render buffer int Char, // character to render int* @@ -2512,7 +2496,7 @@ int CTeletextDecoder::RenderChar( unsigned char* axdrcs, // width and height of DRCS-chars int Ascender) // Ascender of font { - UTILS::COLOR::Color bgcolor, fgcolor; + Color bgcolor, fgcolor; int factor, xfactor; std::unique_lock lock(m_txtCache->m_critSection); @@ -2648,7 +2632,7 @@ int CTeletextDecoder::RenderChar( if (buffer) { int x,y,f,c; - UTILS::COLOR::Color* p = buffer + *pPosX + PosY * xres; + Color* p = buffer + *pPosX + PosY * xres; for (y=0; y(teletextFadeAmount * 2.55f) & 0xff) << 24 | 0xFFFFFF; + KODI::UTILS::COLOR::Color color = + (static_cast(teletextFadeAmount * 2.55f) & 0xff) << 24 | 0xFFFFFF; CGUITexture::DrawQuad(m_vertCoords, color, m_pTxtTexture.get(), nullptr, -1.0f); CGUIDialog::Render(); diff --git a/xbmc/windowing/GraphicContext.cpp b/xbmc/windowing/GraphicContext.cpp index ebaec5b715a6a..888be5bf2809f 100644 --- a/xbmc/windowing/GraphicContext.cpp +++ b/xbmc/windowing/GraphicContext.cpp @@ -29,6 +29,8 @@ #include #include +using KODI::UTILS::COLOR::Color; + CGraphicContext::CGraphicContext() = default; CGraphicContext::~CGraphicContext() = default; @@ -585,7 +587,7 @@ void CGraphicContext::Clear() CServiceBroker::GetRenderSystem()->InvalidateColorBuffer(); } -void CGraphicContext::Clear(UTILS::COLOR::Color color) +void CGraphicContext::Clear(Color color) { CServiceBroker::GetRenderSystem()->ClearBuffers(color); } @@ -922,14 +924,14 @@ float CGraphicContext::GetGUIScaleY() const return m_finalTransform.scaleY; } -UTILS::COLOR::Color CGraphicContext::MergeAlpha(UTILS::COLOR::Color color) const +Color CGraphicContext::MergeAlpha(Color color) const { - UTILS::COLOR::Color alpha = m_finalTransform.matrix.TransformAlpha((color >> 24) & 0xff); + Color alpha = m_finalTransform.matrix.TransformAlpha((color >> 24) & 0xff); if (alpha > 255) alpha = 255; return ((alpha << 24) & 0xff000000) | (color & 0xffffff); } -UTILS::COLOR::Color CGraphicContext::MergeColor(UTILS::COLOR::Color color) const +Color CGraphicContext::MergeColor(Color color) const { return m_finalTransform.matrix.TransformColor(color); } diff --git a/xbmc/windowing/GraphicContext.h b/xbmc/windowing/GraphicContext.h index 36aff346084f0..62d38061b5b4d 100644 --- a/xbmc/windowing/GraphicContext.h +++ b/xbmc/windowing/GraphicContext.h @@ -115,7 +115,7 @@ class CGraphicContext : public CCriticalSection a defined color buffer value. Has to be called at the beginning of a frame. \param color the specified color. */ - void Clear(UTILS::COLOR::Color color); + void Clear(KODI::UTILS::COLOR::Color color); void GetAllowedResolutions(std::vector &res); /*! \brief Sets the direction of the current rendering pass. \param renderOrder direction of the pass @@ -154,8 +154,8 @@ class CGraphicContext : public CCriticalSection const TransformMatrix &GetGUIMatrix() const; float GetGUIScaleX() const; float GetGUIScaleY() const; - UTILS::COLOR::Color MergeAlpha(UTILS::COLOR::Color color) const; - UTILS::COLOR::Color MergeColor(UTILS::COLOR::Color color) const; + KODI::UTILS::COLOR::Color MergeAlpha(KODI::UTILS::COLOR::Color color) const; + KODI::UTILS::COLOR::Color MergeColor(KODI::UTILS::COLOR::Color color) const; void SetOrigin(float x, float y); void RestoreOrigin(); void SetCameraPosition(const CPoint &camera); diff --git a/xbmc/windows/GUIWindowScreensaverDim.cpp b/xbmc/windows/GUIWindowScreensaverDim.cpp index f617fba40504d..7bc360f18def0 100644 --- a/xbmc/windows/GUIWindowScreensaverDim.cpp +++ b/xbmc/windows/GUIWindowScreensaverDim.cpp @@ -69,7 +69,8 @@ void CGUIWindowScreensaverDim::Process(unsigned int currentTime, CDirtyRegionLis void CGUIWindowScreensaverDim::Render() { // draw a translucent black quad - fading is handled by the window animation - UTILS::COLOR::Color color = (static_cast(m_dimLevel * 2.55f) & 0xff) << 24; + KODI::UTILS::COLOR::Color color = + (static_cast(m_dimLevel * 2.55f) & 0xff) << 24; color = CServiceBroker::GetWinSystem()->GetGfxContext().MergeAlpha(color); CRect rect(0, 0, (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()); CGUITexture::DrawQuad(rect, color); From 2a72a0800f672bb8643cee1474fd3d6b9a944884 Mon Sep 17 00:00:00 2001 From: Philipp Kerling Date: Sun, 2 Jun 2024 15:03:49 +0200 Subject: [PATCH 041/651] Move StringUtils to KODI::UTILS namespace --- xbmc/utils/StringUtils.cpp | 5 +++++ xbmc/utils/StringUtils.h | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/xbmc/utils/StringUtils.cpp b/xbmc/utils/StringUtils.cpp index b74217fe8c7fc..c2c36010c354c 100644 --- a/xbmc/utils/StringUtils.cpp +++ b/xbmc/utils/StringUtils.cpp @@ -53,6 +53,9 @@ #define FORMAT_BLOCK_SIZE 512 // # of bytes for initial allocation for printf +namespace KODI::UTILS +{ + namespace { /*! @@ -1947,3 +1950,5 @@ std::string StringUtils::CreateFromCString(const char* cstr) { return cstr != nullptr ? std::string(cstr) : std::string(); } + +} // namespace KODI::UTILS diff --git a/xbmc/utils/StringUtils.h b/xbmc/utils/StringUtils.h index 56aade1ca7ba9..dc30c8cdbda8e 100644 --- a/xbmc/utils/StringUtils.h +++ b/xbmc/utils/StringUtils.h @@ -55,6 +55,9 @@ DEF_TO_STR_VALUE(foo) // outputs "4" #define DEF_TO_STR_NAME(x) #x #define DEF_TO_STR_VALUE(x) DEF_TO_STR_NAME(x) +namespace KODI::UTILS +{ + template, int> = 0> constexpr decltype(auto) EnumToInt(T&& arg) noexcept { @@ -452,3 +455,18 @@ struct sortstringbyname return StringUtils::CompareNoCase(strItem1, strItem2) < 0; } }; + +} // namespace KODI::UTILS + +// We make these utilities globally available by default, as it would be a massive +// change to the codebase to refer to them by their fully qualified names or import +// them everywhere. However, it is important that the symbols are declared inside the +// KODI::UTILS namespace in order to avoid a name clash with p8platform. p8platform +// declares StringUtils in the global namespace. If we declare StringUtils without a +// namespace as well, this is a violation of the One Definition Rule and therefore +// undefined behavior. With the switch to C++20 in Kodi and recent versions of the clang compiler, +// this can cause crashes due to the `Empty` static constant being present in different ELF +// sections depending on the compiler mode. +// See also: https://github.com/llvm/llvm-project/issues/72361 +using KODI::UTILS::sortstringbyname; +using KODI::UTILS::StringUtils; From 84491a05764c925711eaf48f0c7e7b2acc7d5f43 Mon Sep 17 00:00:00 2001 From: Anirudh Srinivasan Date: Thu, 13 Jun 2024 23:55:39 -0500 Subject: [PATCH 042/651] Add bitrate detection from tags for files missing bitrate information in headers --- .../DVDDemuxers/DVDDemuxFFmpeg.cpp | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index 1aebc3dcbc6c0..ca0e56b94ac88 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -1623,7 +1623,17 @@ CDemuxStream* CDVDDemuxFFmpeg::AddStream(int streamIdx) st->iChannelLayout = codecparChannelLayout; st->iSampleRate = pStream->codecpar->sample_rate; st->iBlockAlign = pStream->codecpar->block_align; - st->iBitRate = static_cast(pStream->codecpar->bit_rate); + + // Read bitrate from BPS tag for MKVs missing bitrate info in stream header + if (pStream->codecpar->bit_rate == 0 && av_dict_get(pStream->metadata, "BPS", NULL, 0)) + { + st->iBitRate = static_cast( + strtol(av_dict_get(pStream->metadata, "BPS", NULL, 0)->value, NULL, 10)); + } + else + { + st->iBitRate = static_cast(pStream->codecpar->bit_rate); + } st->iBitsPerSample = pStream->codecpar->bits_per_raw_sample; char buf[32] = {}; // https://github.com/FFmpeg/FFmpeg/blob/6ccc3989d15/doc/APIchanges#L50-L53 @@ -1684,7 +1694,17 @@ CDemuxStream* CDVDDemuxFFmpeg::AddStream(int streamIdx) st->fAspect *= (double)pStream->codecpar->width / pStream->codecpar->height; st->iOrientation = 0; st->iBitsPerPixel = pStream->codecpar->bits_per_coded_sample; - st->iBitRate = static_cast(pStream->codecpar->bit_rate); + + // Read bitrate from BPS tag for MKVs missing bitrate info in stream header + if (pStream->codecpar->bit_rate == 0 && av_dict_get(pStream->metadata, "BPS", NULL, 0)) + { + st->iBitRate = static_cast( + strtol(av_dict_get(pStream->metadata, "BPS", NULL, 0)->value, NULL, 10)); + } + else + { + st->iBitRate = static_cast(pStream->codecpar->bit_rate); + } st->bitDepth = 8; const AVPixFmtDescriptor* desc = av_pix_fmt_desc_get(static_cast(pStream->codecpar->format)); From 76b1823fce115c79150ca4a42024d9da7faaafa0 Mon Sep 17 00:00:00 2001 From: Frank Howie Date: Thu, 13 Jun 2024 16:41:32 +0200 Subject: [PATCH 043/651] Update FreeBSD docs --- docs/README.FreeBSD.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/README.FreeBSD.md b/docs/README.FreeBSD.md index 7b3867c0fd04a..ef2783a2f6138 100644 --- a/docs/README.FreeBSD.md +++ b/docs/README.FreeBSD.md @@ -1,7 +1,7 @@ ![Kodi Logo](resources/banner_slim.png) # FreeBSD build guide -This guide has been tested with FreeBSD 12.1 x86_64. Please read it in full before you proceed to familiarize yourself with the build procedure. +This guide has been tested with FreeBSD 14.1 x86_64. Please read it in full before you proceed to familiarize yourself with the build procedure. Several other distributions have **[specific build guides](README.md)** and a general **[Linux build guide](README.Linux.md)** is also available. @@ -38,9 +38,9 @@ Commands that contain strings enclosed in angle brackets denote something you ne git clone -b https://github.com/xbmc/xbmc kodi ``` -**Example:** Clone Kodi's current Krypton branch: +**Example:** Clone Kodi's current Omega branch: ``` -git clone -b Krypton https://github.com/xbmc/xbmc kodi +git clone -b Omega https://github.com/xbmc/xbmc kodi ``` Several different strategies are used to draw your attention to certain pieces of information. In order of how critical the information is, these items are marked as a note, tip, or warning. For example: @@ -82,7 +82,7 @@ If you get a `package not found` type of message with the below command, remove Install build dependencies: ``` -sudo pkg install autoconf automake avahi-app binutils cmake curl dbus doxygen e2fsprogs-libuuid enca encodings flac flatbuffers font-util fontconfig freetype2 fribidi fstrcmp gawk gettext-tools giflib git glew gmake gmp gnutls googletest gperf gstreamer1-vaapi hal jpeg-turbo libaacs libass libbdplus libbluray libcapn libcdio libcec libedit libfmt libgcrypt libgpg-error libidn libinotify libmicrohttpd libnfs libogg libplist librtmp libtool libudev-devd libva libvdpau libvorbis libxslt lirc lzo2 m4 mesa-libs mysql57-client nasm openjdk8 p8-platform pkgconf python3 rapidjson shairplay sndio sqlite3 swig30 taglib tiff tinyxml tinyxml2 xf86-input-keyboard xf86-input-mouse xorg-server xrandr zip +sudo pkg install autoconf automake avahi-app binutils cmake curl dbus doxygen e2fsprogs-libuuid enca encodings evdev-proto ffmpeg flac flatbuffers font-util fontconfig freetype2 fribidi fstrcmp gawk gettext-tools giflib git glew gmake gmp gnutls googletest gperf gstreamer1-vaapi jpeg-turbo libaacs libass libbdplus libbluray libcapn libcdio libcec libdisplay-info libedit libfmt libgcrypt libgpg-error libidn libinotify libinput libmicrohttpd libnfs libogg libplist librtmp libtool libudev-devd libva libvdpau libvorbis libxkbcommon libxslt lirc lzo2 m4 mariadb-connector-c-3.3.8_1 mesa-libs nasm openjdk21 p8-platform pkgconf python3 rapidjson shairplay sndio spdlog sqlite3 swig taglib tiff tinyxml tinyxml2 wayland-protocols waylandpp xf86-input-keyboard xf86-input-mouse xorg-server xrandr zip ``` > [!WARNING] @@ -125,8 +125,11 @@ cd $HOME/kodi-build Configure build: ``` -cmake ../kodi -DCMAKE_INSTALL_PREFIX=/usr/local +cmake ../kodi -DCMAKE_INSTALL_PREFIX=/usr/local -DENABLE_INTERNAL_PCRE=ON -DAPP_RENDER_SYSTEM="gl" ``` +> [!NOTE] +> You can use `gles` instead of `gl` if you want to build with `GLES`. +> Building internal PCRE is recommended since system pcre libs are EOLed by upstream. ### 4.2. Build ``` @@ -137,7 +140,7 @@ cmake --build . -- VERBOSE=1 -j$(sysctl hw.ncpu | awk '{print $2}') After the build process completes successfully you can test your shiny new Kodi build while in the build directory: ``` -./kodi-x11 +./kodi.bin ``` If everything was OK during your test you can now install the binaries to their place, in this example */usr/local*. From 2220fad74fb9dfe2f908b7754ea58dcbe24408de Mon Sep 17 00:00:00 2001 From: Frank Howie Date: Fri, 14 Jun 2024 11:26:22 +0200 Subject: [PATCH 044/651] [buildsteps] build internal pcre / use system tinyxml2 (BSD) hopefully system tinyxml2 is install on our build slave :) --- tools/buildsteps/freebsd/configure-xbmc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/buildsteps/freebsd/configure-xbmc b/tools/buildsteps/freebsd/configure-xbmc index e8136882bb6b3..1044b688a60f5 100644 --- a/tools/buildsteps/freebsd/configure-xbmc +++ b/tools/buildsteps/freebsd/configure-xbmc @@ -4,4 +4,4 @@ XBMC_PLATFORM_DIR=freebsd mkdir -p $WORKSPACE/build cd $WORKSPACE/build -cmake -DCMAKE_BUILD_TYPE=$Configuration -DENABLE_INTERNAL_TINYXML2=ON -DAPP_RENDER_SYSTEM=gl .. +cmake -DCMAKE_BUILD_TYPE=$Configuration -DENABLE_INTERNAL_PCRE=ON -DAPP_RENDER_SYSTEM=gl .. From 778865431aabc9a0c84a5df223f7fc0ec79c1391 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Fri, 14 Jun 2024 17:50:07 +0200 Subject: [PATCH 045/651] [Android] bump Gson 2.11.0 --- tools/android/packaging/xbmc/build.gradle.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/android/packaging/xbmc/build.gradle.in b/tools/android/packaging/xbmc/build.gradle.in index 71f94a8ffa240..c00e6e5b352cf 100644 --- a/tools/android/packaging/xbmc/build.gradle.in +++ b/tools/android/packaging/xbmc/build.gradle.in @@ -60,5 +60,5 @@ project.afterEvaluate { dependencies { // New support library to for channels/programs development. implementation 'androidx.tvprovider:tvprovider:1.1.0-alpha01' - implementation 'com.google.code.gson:gson:2.10.1' + implementation 'com.google.code.gson:gson:2.11.0' } From 058932593843123bbd00b509e7b34bc30b48ded7 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 15 Jun 2024 13:21:41 +1000 Subject: [PATCH 046/651] [tools/depends][native] rustup 1.79 --- tools/depends/native/rustup/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/depends/native/rustup/Makefile b/tools/depends/native/rustup/Makefile index 83f993c158631..ef3d16905010f 100644 --- a/tools/depends/native/rustup/Makefile +++ b/tools/depends/native/rustup/Makefile @@ -17,7 +17,7 @@ APP=$(PLATFORM)/bin/$(APPNAME) export RUSTUP_HOME=$(PREFIX)/.rustup export CARGO_HOME=$(PREFIX)/.cargo -RUST_TOOLCHAIN_VERSION=1.78.0 +RUST_TOOLCHAIN_VERSION=1.79.0 RUSTUP_ENV_VARS = RUSTUP_HOME=$(PREFIX)/.rustup \ CARGO_HOME=$(PREFIX)/.cargo RUSTUP = $(RUSTUP_ENV_VARS) $(PREFIX)/bin/rustup From 49815d4cd1a0992443621211d1429ec013d6d280 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 15 Jun 2024 13:22:24 +1000 Subject: [PATCH 047/651] [tools/depends][native] cargo-c add Cargo.lock --- tools/depends/native/cargo-c/Cargo.lock | 3652 +++++++++++++++++++++++ tools/depends/native/cargo-c/Makefile | 8 +- 2 files changed, 3657 insertions(+), 3 deletions(-) create mode 100644 tools/depends/native/cargo-c/Cargo.lock diff --git a/tools/depends/native/cargo-c/Cargo.lock b/tools/depends/native/cargo-c/Cargo.lock new file mode 100644 index 0000000000000..f1ef6c1e28925 --- /dev/null +++ b/tools/depends/native/cargo-c/Cargo.lock @@ -0,0 +1,3652 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "annotate-snippets" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9b665789884a7e8fb06c84b295e923b03ca51edbb7d08f91a6a50322ecbfe6" +dependencies = [ + "anstyle", + "unicode-width", +] + +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "regex-automata 0.4.7", + "serde", +] + +[[package]] +name = "btoi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" +dependencies = [ + "num-traits", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "bytesize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" + +[[package]] +name = "cargo" +version = "0.78.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6305e39d08315644d79a5ae09a0745dfb3a43b5b5e318e55dbda3f12031c5dc" +dependencies = [ + "annotate-snippets", + "anstream", + "anstyle", + "anyhow", + "base64", + "bytesize", + "cargo-credential", + "cargo-credential-libsecret", + "cargo-credential-macos-keychain", + "cargo-credential-wincred", + "cargo-platform", + "cargo-util", + "cargo-util-schemas", + "clap", + "color-print", + "crates-io", + "curl", + "curl-sys", + "filetime", + "flate2", + "git2", + "git2-curl", + "gix", + "gix-features", + "glob", + "hex", + "hmac", + "home", + "http-auth", + "humantime", + "ignore", + "im-rc", + "indexmap 2.2.6", + "itertools", + "jobserver", + "lazycell", + "libc", + "libgit2-sys", + "memchr", + "opener", + "openssl", + "os_info", + "pasetors", + "pathdiff", + "rand", + "regex", + "rusqlite", + "rustfix", + "semver", + "serde", + "serde-untagged", + "serde_ignored", + "serde_json", + "sha1", + "shell-escape", + "supports-hyperlinks", + "tar", + "tempfile", + "time", + "toml 0.8.14", + "toml_edit 0.21.1", + "tracing", + "tracing-subscriber", + "unicase", + "unicode-width", + "url", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "cargo-c" +version = "0.9.31+cargo-0.78.0" +dependencies = [ + "anyhow", + "cargo", + "cargo-util", + "cbindgen", + "cc", + "clap", + "glob", + "itertools", + "log", + "regex", + "semver", + "serde", + "serde_derive", + "serde_json", + "toml 0.8.14", + "windows-sys 0.52.0", +] + +[[package]] +name = "cargo-credential" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e5c02daf38715e60a9f59155bc3154c3e0bf55ee7bf34ddc090e8818c8f75e3" +dependencies = [ + "anyhow", + "libc", + "serde", + "serde_json", + "thiserror", + "time", + "windows-sys 0.52.0", +] + +[[package]] +name = "cargo-credential-libsecret" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d73108520871d1ce2f3ba723f24cf13f89fcbab33424b408fc03f31fdc9a7f0" +dependencies = [ + "anyhow", + "cargo-credential", + "libloading", +] + +[[package]] +name = "cargo-credential-macos-keychain" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a58a3a28c131a750309387c3a05d25b992cf7d2648463ac70bd9e22c705e0868" +dependencies = [ + "cargo-credential", + "security-framework", +] + +[[package]] +name = "cargo-credential-wincred" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15e5fee4548ab1b6b5459ab91fed7d79170a4dda38232d2b6263de28becf9f3" +dependencies = [ + "cargo-credential", + "windows-sys 0.52.0", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-util" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e977de2867ec90a1654882ff95ca5849a526e893bab588f84664cfcdb11c0a" +dependencies = [ + "anyhow", + "core-foundation", + "filetime", + "hex", + "ignore", + "jobserver", + "libc", + "miow", + "same-file", + "sha2", + "shell-escape", + "tempfile", + "tracing", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "cargo-util-schemas" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63d2780ac94487eb9f1fea7b0d56300abc9eb488800854ca217f102f5caccca" +dependencies = [ + "semver", + "serde", + "serde-untagged", + "serde-value", + "thiserror", + "toml 0.8.14", + "unicode-xid", + "url", +] + +[[package]] +name = "cbindgen" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49" +dependencies = [ + "heck 0.4.1", + "indexmap 1.9.3", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "tempfile", + "toml 0.5.11", +] + +[[package]] +name = "cc" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "clap_lex" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + +[[package]] +name = "clru" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59" + +[[package]] +name = "color-print" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee543c60ff3888934877a5671f45494dd27ed4ba25c6670b9a7576b7ed7a8c0" +dependencies = [ + "color-print-proc-macro", +] + +[[package]] +name = "color-print-proc-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ff1a80c5f3cb1ca7c06ffdd71b6a6dd6d8f896c42141fbd43f50ed28dcdb93" +dependencies = [ + "nom", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crates-io" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1768fc088f5ad5a83a2860626b2b2ba71f31cdbe1295284980ceb191a7ae86d8" +dependencies = [ + "curl", + "percent-encoding", + "serde", + "serde_json", + "thiserror", + "url", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + +[[package]] +name = "curl" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "curl-sys" +version = "0.4.72+curl-8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29cbdc8314c447d11e8fd156dcdd031d9e02a7a976163e396b548c03153bc9ea" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "windows-sys 0.52.0", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519-compact" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" +dependencies = [ + "getrandom", +] + +[[package]] +name = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erased-serde" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "faster-hex" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", +] + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "libz-sys", + "miniz_oxide", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "git2" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" +dependencies = [ + "bitflags 2.5.0", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url", +] + +[[package]] +name = "git2-curl" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78e26b61608c573ffd26fc79061a823aa5147449a1afe1f61679a21e2031f7c3" +dependencies = [ + "curl", + "git2", + "log", + "url", +] + +[[package]] +name = "gix" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd025382892c7b500a9ce1582cd803f9c2ebfe44aff52e9c7f86feee7ced75e" +dependencies = [ + "gix-actor", + "gix-attributes", + "gix-command", + "gix-commitgraph", + "gix-config", + "gix-credentials", + "gix-date", + "gix-diff", + "gix-discover", + "gix-features", + "gix-filter", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-hashtable", + "gix-ignore", + "gix-index", + "gix-lock", + "gix-macros", + "gix-negotiate", + "gix-object", + "gix-odb", + "gix-pack", + "gix-path", + "gix-pathspec", + "gix-prompt", + "gix-protocol", + "gix-ref", + "gix-refspec", + "gix-revision", + "gix-revwalk", + "gix-sec", + "gix-submodule", + "gix-tempfile", + "gix-trace", + "gix-transport", + "gix-traverse", + "gix-url", + "gix-utils", + "gix-validate", + "gix-worktree", + "once_cell", + "parking_lot", + "prodash", + "smallvec", + "thiserror", + "unicode-normalization", +] + +[[package]] +name = "gix-actor" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da27b5ab4ab5c75ff891dccd48409f8cc53c28a79480f1efdd33184b2dc1d958" +dependencies = [ + "bstr", + "btoi", + "gix-date", + "itoa", + "thiserror", + "winnow 0.5.40", +] + +[[package]] +name = "gix-attributes" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6de7603d6bcefcf9a1d87779c4812b14665f71bc870df7ce9ca4c4b309de18" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-quote", + "gix-trace", + "kstring", + "smallvec", + "thiserror", + "unicode-bom", +] + +[[package]] +name = "gix-bitmap" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a371db66cbd4e13f0ed9dc4c0fea712d7276805fccc877f77e96374d317e87ae" +dependencies = [ + "thiserror", +] + +[[package]] +name = "gix-chunk" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c8751169961ba7640b513c3b24af61aa962c967aaf04116734975cd5af0c52" +dependencies = [ + "thiserror", +] + +[[package]] +name = "gix-command" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c22e086314095c43ffe5cdc5c0922d5439da4fd726f3b0438c56147c34dc225" +dependencies = [ + "bstr", + "gix-path", + "gix-trace", + "shell-words", +] + +[[package]] +name = "gix-commitgraph" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8dcbf434951fa477063e05fea59722615af70dc2567377e58c2f7853b010fc" +dependencies = [ + "bstr", + "gix-chunk", + "gix-features", + "gix-hash", + "memmap2", + "thiserror", +] + +[[package]] +name = "gix-config" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367304855b369cadcac4ee5fb5a3a20da9378dd7905106141070b79f85241079" +dependencies = [ + "bstr", + "gix-config-value", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", + "memchr", + "once_cell", + "smallvec", + "thiserror", + "unicode-bom", + "winnow 0.5.40", +] + +[[package]] +name = "gix-config-value" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbd06203b1a9b33a78c88252a625031b094d9e1b647260070c25b09910c0a804" +dependencies = [ + "bitflags 2.5.0", + "bstr", + "gix-path", + "libc", + "thiserror", +] + +[[package]] +name = "gix-credentials" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380cf3a7c31763743ae6403ec473281d54bfa05628331d09518a350ad5a0971f" +dependencies = [ + "bstr", + "gix-command", + "gix-config-value", + "gix-path", + "gix-prompt", + "gix-sec", + "gix-trace", + "gix-url", + "thiserror", +] + +[[package]] +name = "gix-date" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367ee9093b0c2b04fd04c5c7c8b6a1082713534eab537597ae343663a518fa99" +dependencies = [ + "bstr", + "itoa", + "thiserror", + "time", +] + +[[package]] +name = "gix-diff" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6a0454f8c42d686f17e7f084057c717c082b7dbb8209729e4e8f26749eb93a" +dependencies = [ + "bstr", + "gix-hash", + "gix-object", + "thiserror", +] + +[[package]] +name = "gix-discover" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d7b2896edc3d899d28a646ccc6df729827a6600e546570b2783466404a42d6" +dependencies = [ + "bstr", + "dunce", + "gix-hash", + "gix-path", + "gix-ref", + "gix-sec", + "thiserror", +] + +[[package]] +name = "gix-features" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50270e8dcc665f30ba0735b17984b9535bdf1e646c76e638e007846164d57af" +dependencies = [ + "bytes", + "crc32fast", + "crossbeam-channel", + "flate2", + "gix-hash", + "gix-trace", + "libc", + "once_cell", + "parking_lot", + "prodash", + "sha1_smol", + "thiserror", + "walkdir", +] + +[[package]] +name = "gix-filter" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f598c1d688bf9d57f428ed7ee70c3e786d6f0cc7ed1aeb3c982135af41f6e516" +dependencies = [ + "bstr", + "encoding_rs", + "gix-attributes", + "gix-command", + "gix-hash", + "gix-object", + "gix-packetline-blocking", + "gix-path", + "gix-quote", + "gix-trace", + "gix-utils", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-fs" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7555c23a005537434bbfcb8939694e18cad42602961d0de617f8477cc2adecdd" +dependencies = [ + "gix-features", +] + +[[package]] +name = "gix-glob" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6232f18b262770e343dcdd461c0011c9b9ae27f0c805e115012aa2b902c1b8" +dependencies = [ + "bitflags 2.5.0", + "bstr", + "gix-features", + "gix-path", +] + +[[package]] +name = "gix-hash" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e" +dependencies = [ + "faster-hex", + "thiserror", +] + +[[package]] +name = "gix-hashtable" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ddf80e16f3c19ac06ce415a38b8591993d3f73aede049cb561becb5b3a8e242" +dependencies = [ + "gix-hash", + "hashbrown 0.14.5", + "parking_lot", +] + +[[package]] +name = "gix-ignore" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f356ce440c60aedb7e72f3447f352f9c5e64352135c8cf33e838f49760fd2643" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "unicode-bom", +] + +[[package]] +name = "gix-index" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e50e63df6c8d4137f7fb882f27643b3a9756c468a1a2cdbe1ce443010ca8778" +dependencies = [ + "bitflags 2.5.0", + "bstr", + "btoi", + "filetime", + "gix-bitmap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-traverse", + "itoa", + "libc", + "memmap2", + "rustix", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-lock" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40a439397f1e230b54cf85d52af87e5ea44cc1e7748379785d3f6d03d802b00" +dependencies = [ + "gix-tempfile", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-macros" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999ce923619f88194171a67fb3e6d613653b8d4d6078b529b15a765da0edcc17" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "gix-negotiate" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6820bb5e9e259f6ad052826037452ca023d4f248c5d710dce067d89685dd582" +dependencies = [ + "bitflags 2.5.0", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-object" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c89402e8faa41b49fde348665a8f38589e461036475af43b6b70615a6a313a2" +dependencies = [ + "bstr", + "btoi", + "gix-actor", + "gix-date", + "gix-features", + "gix-hash", + "gix-validate", + "itoa", + "smallvec", + "thiserror", + "winnow 0.5.40", +] + +[[package]] +name = "gix-odb" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46ae6da873de41c6c2b73570e82c571b69df5154dcd8f46dfafc6687767c33b1" +dependencies = [ + "arc-swap", + "gix-date", + "gix-features", + "gix-hash", + "gix-object", + "gix-pack", + "gix-path", + "gix-quote", + "parking_lot", + "tempfile", + "thiserror", +] + +[[package]] +name = "gix-pack" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "782b4d42790a14072d5c400deda9851f5765f50fe72bca6dece0da1cd6f05a9a" +dependencies = [ + "clru", + "gix-chunk", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-path", + "gix-tempfile", + "memmap2", + "parking_lot", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-packetline" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b70486beda0903b6d5b65dfa6e40585098cdf4e6365ca2dff4f74c387354a515" +dependencies = [ + "bstr", + "faster-hex", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-packetline-blocking" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31d42378a3d284732e4d589979930d0d253360eccf7ec7a80332e5ccb77e14a" +dependencies = [ + "bstr", + "faster-hex", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-path" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23623cf0f475691a6d943f898c4d0b89f5c1a2a64d0f92bce0e0322ee6528783" +dependencies = [ + "bstr", + "gix-trace", + "home", + "once_cell", + "thiserror", +] + +[[package]] +name = "gix-pathspec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cdb0ee9517c04f89bcaf6366fe893a17154ecb02d88b5c8174f27f1091d1247" +dependencies = [ + "bitflags 2.5.0", + "bstr", + "gix-attributes", + "gix-config-value", + "gix-glob", + "gix-path", + "thiserror", +] + +[[package]] +name = "gix-prompt" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fddabbc7c51c241600ab3c4623b19fa53bde7c1a2f637f61043ed5fcadf000cc" +dependencies = [ + "gix-command", + "gix-config-value", + "parking_lot", + "rustix", + "thiserror", +] + +[[package]] +name = "gix-protocol" +version = "0.43.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca52738435991105f3bbd7f3a3a42cdf84c9992a78b9b7b1de528b3c022cfdd" +dependencies = [ + "bstr", + "btoi", + "gix-credentials", + "gix-date", + "gix-features", + "gix-hash", + "gix-transport", + "maybe-async", + "thiserror", + "winnow 0.5.40", +] + +[[package]] +name = "gix-quote" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbff4f9b9ea3fa7a25a70ee62f545143abef624ac6aa5884344e70c8b0a1d9ff" +dependencies = [ + "bstr", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-ref" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d9bd1984638d8f3511a2fcbe84fcedb8a5b5d64df677353620572383f42649" +dependencies = [ + "gix-actor", + "gix-date", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-validate", + "memmap2", + "thiserror", + "winnow 0.5.40", +] + +[[package]] +name = "gix-refspec" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be219df5092c1735abb2a53eccdf775e945eea6986ee1b6e7a5896dccc0be704" +dependencies = [ + "bstr", + "gix-hash", + "gix-revision", + "gix-validate", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-revision" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa78e1df3633bc937d4db15f8dca2abdb1300ca971c0fabcf9fa97e38cf4cd9f" +dependencies = [ + "bstr", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-revwalk" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702de5fe5c2bbdde80219f3a8b9723eb927466e7ecd187cfd1b45d986408e45f" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-sec" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fddc27984a643b20dd03e97790555804f98cf07404e0e552c0ad8133266a79a1" +dependencies = [ + "bitflags 2.5.0", + "gix-path", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "gix-submodule" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d438409222de24dffcc9897f04a9f97903a19fe4835b598ab3bb9b6e0f5e35" +dependencies = [ + "bstr", + "gix-config", + "gix-path", + "gix-pathspec", + "gix-refspec", + "gix-url", + "thiserror", +] + +[[package]] +name = "gix-tempfile" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8ef376d718b1f5f119b458e21b00fbf576bc9d4e26f8f383d29f5ffe3ba3eaa" +dependencies = [ + "gix-fs", + "libc", + "once_cell", + "parking_lot", + "tempfile", +] + +[[package]] +name = "gix-trace" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f924267408915fddcd558e3f37295cc7d6a3e50f8bd8b606cee0808c3915157e" + +[[package]] +name = "gix-transport" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be01a22053e9395a409fcaeed879d94f4fcffeb4f46de7143275fbf5e5b39770" +dependencies = [ + "base64", + "bstr", + "curl", + "gix-command", + "gix-credentials", + "gix-features", + "gix-packetline", + "gix-quote", + "gix-sec", + "gix-url", + "thiserror", +] + +[[package]] +name = "gix-traverse" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65109e445ba7a409b48f34f570a4d7db72eade1dc1bcff81990a490e86c07161" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-url" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0f17cceb7552a231d1fec690bc2740c346554e3be6f5d2c41dfa809594dc44" +dependencies = [ + "bstr", + "gix-features", + "gix-path", + "home", + "thiserror", + "url", +] + +[[package]] +name = "gix-utils" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35192df7fd0fa112263bad8021e2df7167df4cc2a6e6d15892e1e55621d3d4dc" +dependencies = [ + "fastrand", + "unicode-normalization", +] + +[[package]] +name = "gix-validate" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c27dd34a49b1addf193c92070bcbf3beaf6e10f16a78544de6372e146a0acf" +dependencies = [ + "bstr", + "thiserror", +] + +[[package]] +name = "gix-worktree" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53982f8abff0789a9599e644108a1914da61a4d0dede8e45037e744dcb008d52" +dependencies = [ + "bstr", + "gix-attributes", + "gix-features", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http-auth" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643c9bbf6a4ea8a656d6b4cd53d34f79e3f841ad5203c1a55fb7d761923bc255" +dependencies = [ + "memchr", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "idna" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" +dependencies = [ + "icu_normalizer", + "icu_properties", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "ignore" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.7", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "im-rc" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" +dependencies = [ + "bitmaps", + "rand_core", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kstring" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libgit2-sys" +version = "0.16.2+1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + +[[package]] +name = "libnghttp2-sys" +version = "0.1.10+1.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "959c25552127d2e1fa72f0e52548ec04fc386e827ba71a7bd01db46a447dc135" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libssh2-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "maybe-async" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +dependencies = [ + "adler", +] + +[[package]] +name = "miow" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "359f76430b20a79f9e20e115b3428614e654f04fab314482fc0fda0ebd3c6044" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "normpath" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5831952a9476f2fed74b77d74182fa5ddc4d21c72ec45a333b250e3ed0272804" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opener" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c62dcb6174f9cb326eac248f07e955d5d559c272730b6c03e396b443b562788" +dependencies = [ + "bstr", + "normpath", + "winapi", +] + +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "300.3.1+3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "orion" +version = "0.17.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abdb10181903c8c4b016ba45d6d6d5af1a1e2a461aa4763a83b87f5df4695e5" +dependencies = [ + "fiat-crypto", + "subtle", + "zeroize", +] + +[[package]] +name = "os_info" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +dependencies = [ + "log", + "serde", + "windows-sys 0.52.0", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.2", + "smallvec", + "windows-targets 0.52.5", +] + +[[package]] +name = "pasetors" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b36d47c66f2230dd1b7143d9afb2b4891879020210eddf2ccb624e529b96dba" +dependencies = [ + "ct-codecs", + "ed25519-compact", + "getrandom", + "orion", + "p384", + "rand_core", + "regex", + "serde", + "serde_json", + "sha2", + "subtle", + "time", + "zeroize", +] + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prodash" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rusqlite" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d" +dependencies = [ + "bitflags 2.5.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustfix" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b338d50bbf36e891c7e40337c8d4cf654094a14d50c3583c6022793c01a259c" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags 2.5.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2676ba99bd82f75cae5cbd2c8eda6fa0b8760f18978ea840e980dd5567b5c5b6" +dependencies = [ + "erased-serde", + "serde", + "typeid", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "serde_ignored" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e319a36d1b52126a0d608f24e93b2d81297091818cd70625fcf50a15d84ddf" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shell-escape" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "supports-hyperlinks" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d" +dependencies = [ + "is-terminal", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "tar" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +dependencies = [ + "filetime", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.14", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap 2.2.6", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +dependencies = [ + "indexmap 2.2.6", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.13", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typeid" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bom" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.66", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +dependencies = [ + "memchr", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] diff --git a/tools/depends/native/cargo-c/Makefile b/tools/depends/native/cargo-c/Makefile index 9dc8ab7f8dca0..4ab94565c69d0 100644 --- a/tools/depends/native/cargo-c/Makefile +++ b/tools/depends/native/cargo-c/Makefile @@ -1,5 +1,6 @@ include ../../Makefile.include CARGO-C-VERSION ../../download-files.include -DEPS = ../../Makefile.include Makefile CARGO-C-VERSION ../../download-files.include +DEPS = ../../Makefile.include Makefile CARGO-C-VERSION ../../download-files.include \ + Cargo.lock PREFIX=$(NATIVEPREFIX) PLATFORM=$(NATIVEPLATFORM) @@ -21,12 +22,13 @@ all: .installed-$(PLATFORM) $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM) cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) + cp Cargo.lock $(PLATFORM)/Cargo.lock $(APP): $(PLATFORM) - $(CARGO) build --release --manifest-path $(PLATFORM)/Cargo.toml + $(CARGO) build --release --manifest-path $(PLATFORM)/Cargo.toml --locked .installed-$(PLATFORM): $(APP) - $(CARGO) install --profile release --path $(PLATFORM) + $(CARGO) install --profile release --path $(PLATFORM) --locked touch $@ From 528aa324aecbf736ab2d34d90a0bfb2d8bacfeb4 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 15 Jun 2024 13:22:59 +1000 Subject: [PATCH 048/651] [tools/depends][target] libdovi add Cargo.lock --- tools/depends/target/libdovi/Makefile | 12 +- .../target/libdovi/dolby_vision-Cargo.lock | 770 ++++++++++++++++++ 2 files changed, 776 insertions(+), 6 deletions(-) create mode 100644 tools/depends/target/libdovi/dolby_vision-Cargo.lock diff --git a/tools/depends/target/libdovi/Makefile b/tools/depends/target/libdovi/Makefile index 202a894f57d0c..ae12c99ca3a67 100644 --- a/tools/depends/target/libdovi/Makefile +++ b/tools/depends/target/libdovi/Makefile @@ -1,10 +1,11 @@ include ../../Makefile.include LIBDOVI-VERSION ../../download-files.include -DEPS = ../../Makefile.include Makefile LIBDOVI-VERSION ../../download-files.include +DEPS = ../../Makefile.include Makefile LIBDOVI-VERSION ../../download-files.include \ + dolby_vision-Cargo.lock LIBDYLIB=$(PLATFORM)/target/$(HOST)/release/$(BYPRODUCT) CARGO_ENV_VARS = RUSTUP_HOME=$(NATIVEPREFIX)/.rustup \ - CARGO_HOME=$(NATIVEPREFIX)/.cargo + CARGO_HOME=$(NATIVEPREFIX)/.cargo CARGO = $(CARGO_ENV_VARS) $(NATIVEPREFIX)/bin/cargo CARGO_BASE_OPTS = --manifest-path $(PLATFORM)/dolby_vision/Cargo.toml @@ -18,8 +19,7 @@ ifeq ($(CROSS_COMPILING),yes) CARGO_BASE_OPTS += --target $(RUST_TARGET) endif -CARGO_BUILD_OPTS = --offline \ - --frozen \ +CARGO_BUILD_OPTS = --locked \ --library-type staticlib \ --profile release \ --prefix $(PREFIX) \ @@ -30,8 +30,8 @@ all: .installed-$(PLATFORM) $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM) cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) - cd $(PLATFORM); - $(CARGO) fetch $(CARGO_BASE_OPTS) + cp dolby_vision-Cargo.lock $(PLATFORM)/dolby_vision/Cargo.lock + $(CARGO) fetch --locked $(CARGO_BASE_OPTS) $(LIBDYLIB): $(PLATFORM) $(CARGO) cbuild $(CARGO_BUILD_OPTS) diff --git a/tools/depends/target/libdovi/dolby_vision-Cargo.lock b/tools/depends/target/libdovi/dolby_vision-Cargo.lock new file mode 100644 index 0000000000000..6c6d48fafd16c --- /dev/null +++ b/tools/depends/target/libdovi/dolby_vision-Cargo.lock @@ -0,0 +1,770 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitstream-io" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac55ccd165b210af261b1812cf29a0b9e6303b480ff0df603c037129ff357fe" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bitvec_helpers" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c810ea0801e8aabb86ded7f207b0d5a7f23c804cd1b7719aba2b4970899c099a" +dependencies = [ + "bitstream-io", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "bitflags", + "clap_lex", + "indexmap 1.9.3", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "criterion" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +dependencies = [ + "anes", + "atty", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "dolby_vision" +version = "3.1.2" +dependencies = [ + "anyhow", + "bitvec", + "bitvec_helpers", + "crc", + "criterion", + "libc", + "roxmltree", + "serde", + "serde_json", +] + +[[package]] +name = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "plotters" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" + +[[package]] +name = "plotters-svg" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "proc-macro2" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "roxmltree" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862340e351ce1b271a378ec53f304a5558f7db87f3769dc655a8f6ecbb68b302" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "indexmap 2.2.6", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" From 4f9f37bd0791ea05a6fc5a729b41b59fc9affb8e Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 15 Jun 2024 23:41:57 +0200 Subject: [PATCH 049/651] [PVR] CPVRClient: Request TV groups and members only if the addon supports TV. Same for radio. --- xbmc/pvr/addons/PVRClient.cpp | 46 +++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 16ea68b8ab00f..84b97815e3244 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -723,31 +723,41 @@ PVR_ERROR CPVRClient::GetChannelGroupsAmount(int& iGroups) const PVR_ERROR CPVRClient::GetChannelGroups(CPVRChannelGroups* groups) const { - return DoAddonCall(__func__, - [this, groups](const AddonInstance* addon) { - PVR_HANDLE_STRUCT handle = {}; - handle.callerAddress = this; - handle.dataAddress = groups; - return addon->toAddon->GetChannelGroups(addon, &handle, groups->IsRadio()); - }, - m_clientCapabilities.SupportsChannelGroups()); + const bool radio{groups->IsRadio()}; + return DoAddonCall( + __func__, + [this, groups](const AddonInstance* addon) + { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = groups; + return addon->toAddon->GetChannelGroups(addon, &handle, groups->IsRadio()); + }, + m_clientCapabilities.SupportsChannelGroups() && + ((radio && m_clientCapabilities.SupportsRadio()) || + (!radio && m_clientCapabilities.SupportsTV()))); } PVR_ERROR CPVRClient::GetChannelGroupMembers( CPVRChannelGroup* group, std::vector>& groupMembers) const { - return DoAddonCall(__func__, - [this, group, &groupMembers](const AddonInstance* addon) { - PVR_HANDLE_STRUCT handle = {}; - handle.callerAddress = this; - handle.dataAddress = &groupMembers; + const bool radio{group->IsRadio()}; + return DoAddonCall( + __func__, + [this, group, &groupMembers](const AddonInstance* addon) + { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = &groupMembers; - PVR_CHANNEL_GROUP tag; - group->FillAddonData(tag); - return addon->toAddon->GetChannelGroupMembers(addon, &handle, &tag); - }, - m_clientCapabilities.SupportsChannelGroups()); + PVR_CHANNEL_GROUP tag; + group->FillAddonData(tag); + return addon->toAddon->GetChannelGroupMembers(addon, &handle, &tag); + }, + m_clientCapabilities.SupportsChannelGroups() && + ((radio && m_clientCapabilities.SupportsRadio()) || + (!radio && m_clientCapabilities.SupportsTV()))); } PVR_ERROR CPVRClient::GetProvidersAmount(int& iProviders) const From 8fa7adb00652ecbfc40307505ad6879bc881b3ff Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sun, 16 Jun 2024 17:19:43 +0100 Subject: [PATCH 050/651] [CharsetConverter] Fix crash if iconv returns EINVAL --- xbmc/utils/CharsetConverter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/utils/CharsetConverter.cpp b/xbmc/utils/CharsetConverter.cpp index 89976ee7c5d56..c8ec0529ccb93 100644 --- a/xbmc/utils/CharsetConverter.cpp +++ b/xbmc/utils/CharsetConverter.cpp @@ -462,7 +462,7 @@ bool CCharsetConverter::CInnerConverter::convert(iconv_t type, int multiplier, c const typename OUTPUT::size_type sizeInChars = (typename OUTPUT::size_type) (outBufSize - outBytesAvail) / sizeof(typename OUTPUT::value_type); typename OUTPUT::const_pointer strPtr = (typename OUTPUT::const_pointer) outBuf; /* Make sure that all buffer is assigned and string is stopped at end of buffer */ - if (strPtr[sizeInChars-1] == 0 && strSource[strSource.length()-1] != 0) + if (sizeInChars > 0 && strPtr[sizeInChars - 1] == 0 && strSource[strSource.length() - 1] != 0) strDest.assign(strPtr, sizeInChars-1); else strDest.assign(strPtr, sizeInChars); From 6f766bb4d6cd4cf165ff80b17ec030e88fc7b4b9 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 3 Jun 2024 13:56:56 +1000 Subject: [PATCH 051/651] [windows] Split target platform from build tools folder location We currently use the same folder for win32 target arch, and for the native build tools extraction folder. We need to avoid NATIVEPREFIX being the same as DEPENDS_PATH. For now, we can just locate the build tools to an non specific arch folder --- cmake/scripts/windows/ArchSetup.cmake | 2 +- cmake/scripts/windowsstore/ArchSetup.cmake | 2 +- tools/buildsteps/windows/download-dependencies.bat | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cmake/scripts/windows/ArchSetup.cmake b/cmake/scripts/windows/ArchSetup.cmake index 4b00257f530ed..b19789d5bbbb9 100644 --- a/cmake/scripts/windows/ArchSetup.cmake +++ b/cmake/scripts/windows/ArchSetup.cmake @@ -38,7 +38,7 @@ set(DEPS_FOLDER_RELATIVE project/BuildDependencies) # ToDo: currently host build tools are hardcoded to win32 # If we ever allow package.native other than 0_package.native-win32.list we will want to # adapt this based on host -set(NATIVEPREFIX ${CMAKE_SOURCE_DIR}/${DEPS_FOLDER_RELATIVE}/win32) +set(NATIVEPREFIX ${CMAKE_SOURCE_DIR}/${DEPS_FOLDER_RELATIVE}/tools) set(DEPENDS_PATH ${CMAKE_SOURCE_DIR}/${DEPS_FOLDER_RELATIVE}/${ARCH}) set(MINGW_LIBS_DIR ${CMAKE_SOURCE_DIR}/${DEPS_FOLDER_RELATIVE}/mingwlibs/${ARCH}) diff --git a/cmake/scripts/windowsstore/ArchSetup.cmake b/cmake/scripts/windowsstore/ArchSetup.cmake index 10422968b43c8..c3e580aa69764 100644 --- a/cmake/scripts/windowsstore/ArchSetup.cmake +++ b/cmake/scripts/windowsstore/ArchSetup.cmake @@ -54,7 +54,7 @@ set(DEPS_FOLDER_RELATIVE project/BuildDependencies) # ToDo: currently host build tools are hardcoded to win32 # If we ever allow package.native other than 0_package.native-win32.list we will want to # adapt this based on host -set(NATIVEPREFIX ${CMAKE_SOURCE_DIR}/${DEPS_FOLDER_RELATIVE}/win32) +set(NATIVEPREFIX ${CMAKE_SOURCE_DIR}/${DEPS_FOLDER_RELATIVE}/tools) set(DEPENDS_PATH ${CMAKE_SOURCE_DIR}/${DEPS_FOLDER_RELATIVE}/win10-${ARCH}) set(MINGW_LIBS_DIR ${CMAKE_SOURCE_DIR}/${DEPS_FOLDER_RELATIVE}/mingwlibs/win10-${ARCH}) diff --git a/tools/buildsteps/windows/download-dependencies.bat b/tools/buildsteps/windows/download-dependencies.bat index 697c616751aea..6a509c3eee0c6 100644 --- a/tools/buildsteps/windows/download-dependencies.bat +++ b/tools/buildsteps/windows/download-dependencies.bat @@ -9,6 +9,10 @@ POPD SET TARGETPLATFORM=%1 SET NATIVEPLATFORM=%2 +REM Build tools location. We may want to add an extra folder for HOST ARCH +REM if we want end up having build tools other than win32 arch +SET HOST_BUILDTOOLS=tools + IF "%TARGETPLATFORM%" == "" SET TARGETPLATFORM=win32 IF "%NATIVEPLATFORM%" == "" SET NATIVEPLATFORM=win32 @@ -22,7 +26,7 @@ echo Downloading from mirror %KODI_MIRROR% REM Locate the BuildDependencies directory, based on the path of this script SET BUILD_DEPS_PATH=%WORKSPACE%\project\BuildDependencies SET APP_PATH=%WORKSPACE%\project\BuildDependencies\%TARGETPLATFORM% -SET NATIVE_PATH=%WORKSPACE%\project\BuildDependencies\%NATIVEPLATFORM% +SET NATIVE_PATH=%WORKSPACE%\project\BuildDependencies\%HOST_BUILDTOOLS% SET TMP_PATH=%BUILD_DEPS_PATH%\scripts\tmp REM Change to the BuildDependencies directory, if we're not there already From 214cb38c6686b480b8ddd710ce5ed78016782223 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 2 Jun 2024 17:38:47 +1000 Subject: [PATCH 052/651] [cmake] Remove NATIVEPREFIX from search paths for build target searches We dont want our build target find searches to search in nativeprefix. This can inadvertently use config packages that are available in both native and target prefixes from the wrong location. --- CMakeLists.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index aa674c7c4fb4b..6c52b76a401cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -199,6 +199,11 @@ set(optional_buildtools CCache core_optional_dep(${optional_buildtools}) core_require_dep(${required_buildtools}) +# We want to explicitly ignore NATIVEPREFIX for any find calls to build target dependencies +if(NATIVEPREFIX) + set(CMAKE_IGNORE_PREFIX_PATH ${NATIVEPREFIX}) +endif() + # Required dependencies. Keep in alphabetical order please set(required_deps ASS>=0.15.0 Cdio @@ -290,6 +295,13 @@ if(ENABLE_AIRTUNES) endif() endif() +# We unset this after the bulk of our find calls are complete. Ideally we would want this enabled +# for anything that is build target related, and only remove for native buildtools, however +# thats more complicated for the need right now. +if(NATIVEPREFIX) + unset(CMAKE_IGNORE_PREFIX_PATH) +endif() + # find all folders containing addon.xml.in # used to define ADDON_XML_OUTPUTS, ADDON_XML_DEPENDS and ADDON_INSTALL_DATA # Function defined in ./cmake/scripts/common/Macros.cmake From e458cbb2fe23643489aeb135a068b3f6ad4b9ac1 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 2 Jun 2024 18:43:03 +1000 Subject: [PATCH 053/651] [cmake] remove unnecessary CMAKE_SYSTEM_PREFIX_PATH append --- cmake/scripts/darwin_embedded/ArchSetup.cmake | 2 -- cmake/scripts/osx/ArchSetup.cmake | 2 -- 2 files changed, 4 deletions(-) diff --git a/cmake/scripts/darwin_embedded/ArchSetup.cmake b/cmake/scripts/darwin_embedded/ArchSetup.cmake index 2079c221cbf29..a9d43283c8f55 100644 --- a/cmake/scripts/darwin_embedded/ArchSetup.cmake +++ b/cmake/scripts/darwin_embedded/ArchSetup.cmake @@ -32,8 +32,6 @@ else() message(SEND_ERROR "Currently only OpenGLES rendering is supported. Please set APP_RENDER_SYSTEM to \"gles\"") endif() -list(APPEND CMAKE_SYSTEM_PREFIX_PATH ${NATIVEPREFIX}) - list(APPEND DEPLIBS "-framework CoreFoundation" "-framework CoreVideo" "-framework CoreAudio" "-framework AudioToolbox" "-framework QuartzCore" "-framework MediaPlayer" diff --git a/cmake/scripts/osx/ArchSetup.cmake b/cmake/scripts/osx/ArchSetup.cmake index 0e712926e8de5..7b046b5be40d3 100644 --- a/cmake/scripts/osx/ArchSetup.cmake +++ b/cmake/scripts/osx/ArchSetup.cmake @@ -46,8 +46,6 @@ set(CMAKE_OSX_ARCHITECTURES ${CPU}) # Additional SYSTEM_DEFINES list(APPEND SYSTEM_DEFINES -DHAS_POSIX_NETWORK -DHAS_OSX_NETWORK -DHAS_ZEROCONF) -list(APPEND CMAKE_SYSTEM_PREFIX_PATH ${NATIVEPREFIX}) - list(APPEND DEPLIBS "-framework DiskArbitration" "-framework IOKit" "-framework IOSurface" "-framework SystemConfiguration" "-framework ApplicationServices" "-framework AppKit" From 5df2e57368cf5867f588d00a503eb624e5121460 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 2 Jun 2024 12:13:01 +1000 Subject: [PATCH 054/651] [utils] RegExp remove unused methods --- xbmc/utils/RegExp.cpp | 30 ------------------------------ xbmc/utils/RegExp.h | 5 ----- xbmc/utils/test/TestRegExp.cpp | 17 ----------------- 3 files changed, 52 deletions(-) diff --git a/xbmc/utils/RegExp.cpp b/xbmc/utils/RegExp.cpp index 9667b646a91c3..40263a28eb8d4 100644 --- a/xbmc/utils/RegExp.cpp +++ b/xbmc/utils/RegExp.cpp @@ -485,11 +485,6 @@ int CRegExp::GetSubStart(int iSub) const return m_iOvector[iSub*2] + m_offset; } -int CRegExp::GetSubStart(const std::string& subName) const -{ - return GetSubStart(GetNamedSubPatternNumber(subName.c_str())); -} - int CRegExp::GetSubLength(int iSub) const { if (!IsValidSubNumber(iSub)) @@ -498,11 +493,6 @@ int CRegExp::GetSubLength(int iSub) const return m_iOvector[(iSub*2)+1] - m_iOvector[(iSub*2)]; } -int CRegExp::GetSubLength(const std::string& subName) const -{ - return GetSubLength(GetNamedSubPatternNumber(subName.c_str())); -} - std::string CRegExp::GetMatch(int iSub /* = 0 */) const { if (!IsValidSubNumber(iSub)) @@ -516,26 +506,6 @@ std::string CRegExp::GetMatch(int iSub /* = 0 */) const return m_subject.substr(pos, len); } -std::string CRegExp::GetMatch(const std::string& subName) const -{ - return GetMatch(GetNamedSubPatternNumber(subName.c_str())); -} - -bool CRegExp::GetNamedSubPattern(const char* strName, std::string& strMatch) const -{ - strMatch.clear(); - int iSub = pcre_get_stringnumber(m_re, strName); - if (!IsValidSubNumber(iSub)) - return false; - strMatch = GetMatch(iSub); - return true; -} - -int CRegExp::GetNamedSubPatternNumber(const char* strName) const -{ - return pcre_get_stringnumber(m_re, strName); -} - void CRegExp::DumpOvector(int iLog /* = LOGDEBUG */) { if (iLog < LOGDEBUG || iLog > LOGNONE) diff --git a/xbmc/utils/RegExp.h b/xbmc/utils/RegExp.h index 53f6019a8ff60..feea89cd0d841 100644 --- a/xbmc/utils/RegExp.h +++ b/xbmc/utils/RegExp.h @@ -111,15 +111,10 @@ class CRegExp }; int GetSubCount() const { return m_iMatchCount - 1; } // PCRE returns the number of sub-patterns + 1 int GetSubStart(int iSub) const; - int GetSubStart(const std::string& subName) const; int GetSubLength(int iSub) const; - int GetSubLength(const std::string& subName) const; int GetCaptureTotal() const; std::string GetMatch(int iSub = 0) const; - std::string GetMatch(const std::string& subName) const; const std::string& GetPattern() const { return m_pattern; } - bool GetNamedSubPattern(const char* strName, std::string& strMatch) const; - int GetNamedSubPatternNumber(const char* strName) const; void DumpOvector(int iLog); /** * Check is RegExp object is ready for matching diff --git a/xbmc/utils/test/TestRegExp.cpp b/xbmc/utils/test/TestRegExp.cpp index d75712741714f..9435a46aa4324 100644 --- a/xbmc/utils/test/TestRegExp.cpp +++ b/xbmc/utils/test/TestRegExp.cpp @@ -96,19 +96,6 @@ TEST(TestRegExp, GetPattern) EXPECT_STREQ("^(Test)\\s*(.*)\\.", regex.GetPattern().c_str()); } -TEST(TestRegExp, GetNamedSubPattern) -{ - CRegExp regex; - std::string match; - - EXPECT_TRUE(regex.RegComp("^(?Test)\\s*(?.*)\\.")); - EXPECT_EQ(0, regex.RegFind("Test string.")); - EXPECT_TRUE(regex.GetNamedSubPattern("first", match)); - EXPECT_STREQ("Test", match.c_str()); - EXPECT_TRUE(regex.GetNamedSubPattern("second", match)); - EXPECT_STREQ("string", match.c_str()); -} - TEST(TestRegExp, operatorEqual) { CRegExp regex, regexcopy; @@ -117,10 +104,6 @@ TEST(TestRegExp, operatorEqual) EXPECT_TRUE(regex.RegComp("^(?Test)\\s*(?.*)\\.")); regexcopy = regex; EXPECT_EQ(0, regexcopy.RegFind("Test string.")); - EXPECT_TRUE(regexcopy.GetNamedSubPattern("first", match)); - EXPECT_STREQ("Test", match.c_str()); - EXPECT_TRUE(regexcopy.GetNamedSubPattern("second", match)); - EXPECT_STREQ("string", match.c_str()); } class TestRegExpLog : public testing::Test From 397b095ba287d63bff551b15763bc3dbf391f192 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 2 Jun 2024 12:21:06 +1000 Subject: [PATCH 055/651] [utils][Tests] RegExp introduce 3 tests --- xbmc/utils/test/TestRegExp.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/xbmc/utils/test/TestRegExp.cpp b/xbmc/utils/test/TestRegExp.cpp index 9435a46aa4324..e88ce247970ba 100644 --- a/xbmc/utils/test/TestRegExp.cpp +++ b/xbmc/utils/test/TestRegExp.cpp @@ -30,6 +30,29 @@ TEST(TestRegExp, RegFind) EXPECT_EQ(-1, regex.RegFind("Test string.")); } +TEST(TestRegExp, InvalidPattern) +{ + CRegExp regex; + + EXPECT_FALSE(regex.RegComp("+")); +} + +TEST(TestRegExp, Unicode) +{ + CRegExp regex; + + EXPECT_TRUE(regex.RegComp("Бог!$")); + EXPECT_EQ(12, regex.RegFind("С нами Бог!")); +} + +TEST(TestRegExp, JIT) +{ + CRegExp regex; + + EXPECT_TRUE(regex.RegComp(".JIT.", CRegExp::StudyWithJitComp)); + EXPECT_EQ(12, regex.RegFind("Test string, JIT-matched.")); +} + TEST(TestRegExp, GetReplaceString) { CRegExp regex; From 5c24bf262e5cd75b413fcb4c2c5c144d6971d7cc Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 2 Jun 2024 13:24:57 +1000 Subject: [PATCH 056/651] [dependencies] Introduce PCRE2 dependency create PCRE2 in tools/depends/target Create Find module for PCRE2 --- cmake/modules/FindPCRE.cmake | 182 ------------------ cmake/modules/FindPCRE2.cmake | 151 +++++++++++++++ .../target/pcre/001-all-cmakeconfig.patch | 43 ----- .../target/pcre/002-all-enable_docs_pc.patch | 21 -- .../depends/target/pcre/003-all-postfix.patch | 13 -- tools/depends/target/pcre/004-win-pdb.patch | 19 -- tools/depends/target/pcre/Makefile | 60 ------ tools/depends/target/pcre/PCRE-VERSION | 6 - .../depends/target/pcre/ios-clear_cache.patch | 20 -- tools/depends/target/pcre/jit_aarch64.patch | 11 -- .../target/pcre/tvos-bitcode-fix.patch | 19 -- .../target/pcre2/001-all-enable_docs_pc.patch | 18 ++ .../002-all-cmake-config-installdir.patch | 11 ++ tools/depends/target/pcre2/Makefile | 46 +++++ tools/depends/target/pcre2/PCRE2-VERSION | 7 + 15 files changed, 233 insertions(+), 394 deletions(-) delete mode 100644 cmake/modules/FindPCRE.cmake create mode 100644 cmake/modules/FindPCRE2.cmake delete mode 100644 tools/depends/target/pcre/001-all-cmakeconfig.patch delete mode 100644 tools/depends/target/pcre/002-all-enable_docs_pc.patch delete mode 100644 tools/depends/target/pcre/003-all-postfix.patch delete mode 100644 tools/depends/target/pcre/004-win-pdb.patch delete mode 100644 tools/depends/target/pcre/Makefile delete mode 100644 tools/depends/target/pcre/PCRE-VERSION delete mode 100644 tools/depends/target/pcre/ios-clear_cache.patch delete mode 100644 tools/depends/target/pcre/jit_aarch64.patch delete mode 100644 tools/depends/target/pcre/tvos-bitcode-fix.patch create mode 100644 tools/depends/target/pcre2/001-all-enable_docs_pc.patch create mode 100644 tools/depends/target/pcre2/002-all-cmake-config-installdir.patch create mode 100644 tools/depends/target/pcre2/Makefile create mode 100644 tools/depends/target/pcre2/PCRE2-VERSION diff --git a/cmake/modules/FindPCRE.cmake b/cmake/modules/FindPCRE.cmake deleted file mode 100644 index e662d82116cef..0000000000000 --- a/cmake/modules/FindPCRE.cmake +++ /dev/null @@ -1,182 +0,0 @@ -#.rst: -# FindPCRE -# -------- -# Finds the PCRE library -# -# This will define the following targets: -# -# PCRE::PCRE - The PCRE library - -macro(buildPCRE) - set(PCRE_VERSION ${${MODULE}_VER}) - set(PCRE_DEBUG_POSTFIX d) - - set(patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-all-cmakeconfig.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/002-all-enable_docs_pc.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/003-all-postfix.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/004-win-pdb.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/jit_aarch64.patch") - - if(CORE_SYSTEM_NAME STREQUAL darwin_embedded) - list(APPEND patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/tvos-bitcode-fix.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/ios-clear_cache.patch") - endif() - - generate_patchcommand("${patches}") - - set(CMAKE_ARGS -DPCRE_NEWLINE=ANYCRLF - -DPCRE_NO_RECURSE=ON - -DPCRE_MATCH_LIMIT_RECURSION=1500 - -DPCRE_SUPPORT_JIT=ON - -DPCRE_SUPPORT_PCREGREP_JIT=ON - -DPCRE_SUPPORT_UTF=ON - -DPCRE_SUPPORT_UNICODE_PROPERTIES=ON - -DPCRE_SUPPORT_LIBZ=OFF - -DPCRE_SUPPORT_LIBBZ2=OFF - -DPCRE_BUILD_PCRECPP=OFF - -DPCRE_BUILD_PCREGREP=OFF - -DPCRE_BUILD_TESTS=OFF) - - if(WIN32 OR WINDOWS_STORE) - list(APPEND CMAKE_ARGS -DINSTALL_MSVC_PDB=ON) - - # PCRE fails due to C4703 and C4146 when /SDL is enabled - set(PCRE_C_FLAGS "/sdl-") - set(PCRE_CXX_FLAGS "/sdl-") - elseif(CORE_SYSTEM_NAME STREQUAL android) - # CMake CheckFunctionExists incorrectly detects strtoq for android - list(APPEND CMAKE_ARGS -DHAVE_STRTOQ=0) - endif() - - # populate PCRE lib without a separate module - if(NOT CORE_SYSTEM_NAME MATCHES windows) - # Non windows platforms have a lib prefix for the lib artifact - set(_libprefix "lib") - endif() - - BUILD_DEP_TARGET() -endmacro() - -if(NOT PCRE::pcre) - - include(cmake/scripts/common/ModuleHelpers.cmake) - - set(MODULE_LC pcre) - - SETUP_BUILD_VARS() - - # Check for existing PCRE. If version >= PCRE-VERSION file version, dont build - find_package(PCRE CONFIG QUIET - HINTS ${DEPENDS_PATH}/lib/cmake - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) - - if((PCRE_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_PCRE) OR - ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_PCRE)) - - buildPCRE() - - else() - if(NOT TARGET PCRE::pcre) - find_package(PkgConfig) - if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_PCRE pcre QUIET) - endif() - - find_path(PCRE_INCLUDE_DIR pcre.h - HINTS ${DEPENDS_PATH}/include ${PC_PCRE_INCLUDEDIR} - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} - NO_CACHE) - find_library(PCRE_LIBRARY_RELEASE NAMES pcre - HINTS ${DEPENDS_PATH}/lib ${PC_PCRE_LIBDIR} - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} - NO_CACHE) - find_library(PCRE_LIBRARY_DEBUG NAMES pcred - HINTS ${DEPENDS_PATH}/lib ${PC_PCRE_LIBDIR} - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} - NO_CACHE) - set(PCRE_VERSION ${PC_PCRE_VERSION}) - else() - - # Populate variables for find_package_handle_standard_args usage - get_target_property(_PCRE_CONFIGURATIONS PCRE::pcre IMPORTED_CONFIGURATIONS) - foreach(_pcre_config IN LISTS _PCRE_CONFIGURATIONS) - # Just set to RELEASE var so select_library_configurations can continue to work its magic - if((NOT ${_pcre_config} STREQUAL "RELEASE") AND - (NOT ${_pcre_config} STREQUAL "DEBUG")) - get_target_property(PCRE_LIBRARY_RELEASE PCRE::pcre IMPORTED_LOCATION_${_pcre_config}) - else() - get_target_property(PCRE_LIBRARY_${_pcre_config} PCRE::pcre IMPORTED_LOCATION_${_pcre_config}) - endif() - endforeach() - - # ToDo: patch PCRE cmake to include includedir in config file - find_path(PCRE_INCLUDE_DIR pcre.h - HINTS ${DEPENDS_PATH}/include ${PC_PCRE_INCLUDEDIR} - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} - NO_CACHE) - - set_target_properties(PCRE::pcre PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${PCRE_INCLUDE_DIR}") - endif() - endif() - - if(TARGET PCRE::pcre) - get_target_property(PCRE_INCLUDE_DIR PCRE::pcre INTERFACE_INCLUDE_DIRECTORIES) - endif() - - include(SelectLibraryConfigurations) - select_library_configurations(PCRE) - unset(PCRE_LIBRARIES) - - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(PCRE - REQUIRED_VARS PCRE_LIBRARY PCRE_INCLUDE_DIR - VERSION_VAR PCRE_VERSION) - - if(PCRE_FOUND) - if(NOT TARGET PCRE::pcre) - add_library(PCRE::pcre UNKNOWN IMPORTED) - if(PCRE_LIBRARY_RELEASE) - set_target_properties(PCRE::pcre PROPERTIES - IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION_RELEASE "${PCRE_LIBRARY_RELEASE}") - endif() - if(PCRE_LIBRARY_DEBUG) - set_target_properties(PCRE::pcre PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION_DEBUG "${PCRE_LIBRARY_DEBUG}") - endif() - set_target_properties(PCRE::pcre PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${PCRE_INCLUDE_DIR}") - endif() - - # Wee need to explicitly add this define. The cmake config does not propagate this info - if(WIN32) - set_property(TARGET PCRE::pcre APPEND PROPERTY - INTERFACE_COMPILE_DEFINITIONS "PCRE_STATIC=1") - endif() - - if(TARGET pcre) - add_dependencies(PCRE::pcre pcre) - endif() - - # Add internal build target when a Multi Config Generator is used - # We cant add a dependency based off a generator expression for targeted build types, - # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 - # therefore if the find heuristics only find the library, we add the internal build - # target to the project to allow user to manually trigger for any build type they need - # in case only a specific build type is actually available (eg Release found, Debug Required) - # This is mainly targeted for windows who required different runtime libs for different - # types, and they arent compatible - if(_multiconfig_generator) - if(NOT TARGET pcre) - buildPCRE() - set_target_properties(pcre PROPERTIES EXCLUDE_FROM_ALL TRUE) - endif() - add_dependencies(build_internal_depends pcre) - endif() - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP PCRE::pcre) - - endif() -endif() diff --git a/cmake/modules/FindPCRE2.cmake b/cmake/modules/FindPCRE2.cmake new file mode 100644 index 0000000000000..7e50d5f2c1e86 --- /dev/null +++ b/cmake/modules/FindPCRE2.cmake @@ -0,0 +1,151 @@ +#.rst: +# FindPCRE2 +# -------- +# Finds the PCRE2 library +# +# This will define the following imported target:: +# +# ${APP_NAME_LC}::PCRE2 - The PCRE2 library + +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + + macro(buildPCRE2) + set(PCRE2_VERSION ${${MODULE}_VER}) + if(WIN32) + set(PCRE_DEBUG_POSTFIX d) + endif() + + set(patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-all-enable_docs_pc.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/002-all-cmake-config-installdir.patch") + + generate_patchcommand("${patches}") + + set(CMAKE_ARGS -DBUILD_STATIC_LIBS=ON + -DPCRE2_BUILD_PCRE2_8=ON + -DPCRE2_BUILD_PCRE2_16=OFF + -DPCRE2_BUILD_PCRE2_32=OFF + -DPCRE2_SUPPORT_JIT=ON + -DPCRE2_SUPPORT_UNICODE=ON + -DPCRE2_BUILD_PCRE2GREP=OFF + -DPCRE2_BUILD_TESTS=OFF + -DENABLE_DOCS=OFF) + + if(CORE_SYSTEM_NAME STREQUAL darwin_embedded) + list(APPEND CMAKE_ARGS -DPCRE2_SUPPORT_JIT=OFF) + endif() + + set(${CMAKE_FIND_PACKAGE_NAME}_COMPILEDEFINITIONS PCRE2_STATIC) + + BUILD_DEP_TARGET() + endmacro() + + include(cmake/scripts/common/ModuleHelpers.cmake) + + set(MODULE_LC pcre2) + + SETUP_BUILD_VARS() + + if(KODI_DEPENDSBUILD OR (WIN32 OR WINDOWS_STORE)) + set(PCRE2_USE_STATIC_LIBS ON) + endif() + + # Check for existing PCRE2. If version >= PCRE2-VERSION file version, dont build + find_package(PCRE2 CONFIG COMPONENTS 8BIT QUIET) + + if((PCRE2_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_PCRE2) OR + ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_PCRE2)) + buildPCRE2() + else() + # if PCRE2::8BIT target exists, it meets version requirements + # we only do a pkgconfig search when a suitable cmake config returns nothing + if(TARGET PCRE2::8BIT) + get_target_property(_PCRE2_CONFIGURATIONS PCRE2::8BIT IMPORTED_CONFIGURATIONS) + if(_PCRE2_CONFIGURATIONS) + foreach(_pcre2_config IN LISTS _PCRE2_CONFIGURATIONS) + # Just set to RELEASE var so select_library_configurations can continue to work its magic + string(TOUPPER ${_pcre2_config} _pcre2_config_UPPER) + if((NOT ${_pcre2_config_UPPER} STREQUAL "RELEASE") AND + (NOT ${_pcre2_config_UPPER} STREQUAL "DEBUG")) + get_target_property(PCRE2_LIBRARY_RELEASE PCRE2::8BIT IMPORTED_LOCATION_${_pcre2_config_UPPER}) + else() + get_target_property(PCRE2_LIBRARY_${_pcre2_config_UPPER} PCRE2::8BIT IMPORTED_LOCATION_${_pcre2_config_UPPER}) + endif() + endforeach() + else() + get_target_property(PCRE2_LIBRARY_RELEASE PCRE2::8BIT IMPORTED_LOCATION) + endif() + get_target_property(PCRE2_INCLUDE_DIR PCRE2::8BIT INTERFACE_INCLUDE_DIRECTORIES) + else() + # ToDo: use pkgconfig data imported and drop manual find_path/find_library + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_PCRE2 libpcre2-8 QUIET) + endif() + + find_path(PCRE2_INCLUDE_DIR pcre2.h + HINTS ${PC_PCRE2_INCLUDEDIR}) + find_library(PCRE2_LIBRARY_RELEASE NAMES pcre2-8 + HINTS ${PC_PCRE2_LIBDIR}) + set(PCRE2_VERSION ${PC_PCRE2_VERSION}) + endif() + endif() + + include(SelectLibraryConfigurations) + select_library_configurations(PCRE2) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(PCRE2 + REQUIRED_VARS PCRE2_LIBRARY PCRE2_INCLUDE_DIR + VERSION_VAR PCRE2_VERSION) + + if(PCRE2_FOUND) + if(TARGET PCRE2::8BIT AND NOT TARGET pcre2) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS PCRE2::8BIT) + else() + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + if(PCRE2_LIBRARY_RELEASE) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${PCRE2_LIBRARY_RELEASE}") + endif() + if(PCRE2_LIBRARY_DEBUG) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION_DEBUG "${PCRE2_LIBRARY_DEBUG}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + endif() + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PCRE2_INCLUDE_DIR}") + + # Add interface compile definitions. This will usually come from an INTERNAL build being required. + if(${CMAKE_FIND_PACKAGE_NAME}_COMPILEDEFINITIONS) + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS ${${CMAKE_FIND_PACKAGE_NAME}_COMPILEDEFINITIONS}) + endif() + endif() + if(TARGET pcre2) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} pcre2) + endif() + + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + if(NOT TARGET pcre2) + buildPCRE2() + set_target_properties(pcre2 PROPERTIES EXCLUDE_FROM_ALL TRUE) + endif() + add_dependencies(build_internal_depends pcre2) + endif() + + else() + if(PCRE2_FIND_REQUIRED) + message(FATAL_ERROR "PCRE2 not found. Possibly use -DENABLE_INTERNAL_PCRE2=ON to build PCRE2") + endif() + endif() +endif() diff --git a/tools/depends/target/pcre/001-all-cmakeconfig.patch b/tools/depends/target/pcre/001-all-cmakeconfig.patch deleted file mode 100644 index 5babbdf597a2e..0000000000000 --- a/tools/depends/target/pcre/001-all-cmakeconfig.patch +++ /dev/null @@ -1,43 +0,0 @@ ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -934,7 +934,7 @@ - # Installation - SET(CMAKE_INSTALL_ALWAYS 1) - --INSTALL(TARGETS ${targets} -+INSTALL(TARGETS ${targets} EXPORT pcre - RUNTIME DESTINATION bin - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) -@@ -941,6 +941,27 @@ - - INSTALL(FILES ${PCRE_HEADERS} ${PCREPOSIX_HEADERS} DESTINATION include) - -+include(CMakePackageConfigHelpers) -+write_basic_package_version_file( -+ ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake -+ VERSION ${PACKAGE_VERSION} -+ COMPATIBILITY AnyNewerVersion -+) -+ -+install(EXPORT pcre -+ NAMESPACE -+ ${PROJECT_NAME}:: -+ DESTINATION -+ lib/cmake/${PROJECT_NAME} -+) -+ -+install(FILES -+ ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}-config.cmake -+ ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake -+ DESTINATION -+ lib/cmake/${PROJECT_NAME} -+) -+ - FILE(GLOB html ${PROJECT_SOURCE_DIR}/doc/html/*.html) - FILE(GLOB man1 ${PROJECT_SOURCE_DIR}/doc/*.1) - FILE(GLOB man3 ${PROJECT_SOURCE_DIR}/doc/*.3) ---- /dev/null -+++ b/cmake/PCRE-config.cmake -@@ -0,0 +1 @@ -+include(${CMAKE_CURRENT_LIST_DIR}/pcre.cmake) diff --git a/tools/depends/target/pcre/002-all-enable_docs_pc.patch b/tools/depends/target/pcre/002-all-enable_docs_pc.patch deleted file mode 100644 index 4848e2c783f85..0000000000000 --- a/tools/depends/target/pcre/002-all-enable_docs_pc.patch +++ /dev/null @@ -1,21 +0,0 @@ ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -980,14 +980,18 @@ - SET(man3 ${man3_new}) - ENDIF(PCRE_BUILD_PCRECPP) - -+if(ENABLE_DOCS) - INSTALL(FILES ${man1} DESTINATION man/man1) - INSTALL(FILES ${man3} DESTINATION man/man3) - INSTALL(FILES ${html} DESTINATION share/doc/pcre/html) -+endif() -+if(ENABLE_PKGCONFIG) - INSTALL(FILES ${pc} DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) - INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/pcre-config" - DESTINATION bin - # Set 0755 permissions - PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -+endif() - - IF(MSVC AND INSTALL_MSVC_PDB) - INSTALL(FILES ${PROJECT_BINARY_DIR}/pcre.pdb diff --git a/tools/depends/target/pcre/003-all-postfix.patch b/tools/depends/target/pcre/003-all-postfix.patch deleted file mode 100644 index bfa8ce344d1af..0000000000000 --- a/tools/depends/target/pcre/003-all-postfix.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -661,9 +661,7 @@ - SET(CMAKE_INCLUDE_CURRENT_DIR 1) - # needed to make sure to not link debug libs - # against release libs and vice versa --IF(WIN32) -- SET(CMAKE_DEBUG_POSTFIX "d") --ENDIF(WIN32) -+SET(CMAKE_DEBUG_POSTFIX "d") - - SET(targets) - diff --git a/tools/depends/target/pcre/004-win-pdb.patch b/tools/depends/target/pcre/004-win-pdb.patch deleted file mode 100644 index 7c1fb26c74736..0000000000000 --- a/tools/depends/target/pcre/004-win-pdb.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -969,12 +969,12 @@ - PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) - - IF(MSVC AND INSTALL_MSVC_PDB) -- INSTALL(FILES ${PROJECT_BINARY_DIR}/pcre.pdb -- ${PROJECT_BINARY_DIR}/pcreposix.pdb -+ INSTALL(FILES ${PROJECT_BINARY_DIR}/$/pcre.pdb -+ ${PROJECT_BINARY_DIR}/$/pcreposix.pdb - DESTINATION bin - CONFIGURATIONS RelWithDebInfo) -- INSTALL(FILES ${PROJECT_BINARY_DIR}/pcred.pdb -- ${PROJECT_BINARY_DIR}/pcreposixd.pdb -+ INSTALL(FILES ${PROJECT_BINARY_DIR}/$/pcred.pdb -+ ${PROJECT_BINARY_DIR}/$/pcreposixd.pdb - DESTINATION bin - CONFIGURATIONS Debug) - ENDIF(MSVC AND INSTALL_MSVC_PDB) diff --git a/tools/depends/target/pcre/Makefile b/tools/depends/target/pcre/Makefile deleted file mode 100644 index 679a6c058bb0b..0000000000000 --- a/tools/depends/target/pcre/Makefile +++ /dev/null @@ -1,60 +0,0 @@ -include ../../Makefile.include PCRE-VERSION ../../download-files.include -DEPS = ../../Makefile.include Makefile PCRE-VERSION ../../download-files.include \ - 001-all-cmakeconfig.patch \ - 002-all-enable_docs_pc.patch \ - 003-all-postfix.patch \ - 004-win-pdb.patch \ - jit_aarch64.patch \ - ios-clear_cache.patch \ - tvos-bitcode-fix.patch - -# configuration settings -CMAKE_OPTIONS=-DPCRE_NEWLINE=ANYCRLF \ - -DPCRE_NO_RECURSE=ON \ - -DPCRE_MATCH_LIMIT_RECURSION=1500 \ - -DPCRE_SUPPORT_JIT=ON \ - -DPCRE_SUPPORT_PCREGREP_JIT=ON \ - -DPCRE_SUPPORT_UTF=ON \ - -DPCRE_SUPPORT_UNICODE_PROPERTIES=ON \ - -DPCRE_SUPPORT_LIBZ=OFF \ - -DPCRE_SUPPORT_LIBBZ2=OFF \ - -DPCRE_BUILD_PCRECPP=OFF \ - -DPCRE_BUILD_PCREGREP=OFF \ - -DPCRE_BUILD_TESTS=OFF \ - -DCMAKE_BUILD_TYPE=Release - -ifeq ($(OS),android) - # CMake CheckFunctionExists incorrectly detects strtoq for android - CMAKE_OPTIONS+= -DHAVE_STRTOQ=0 -endif - -LIBDYLIB=$(PLATFORM)/build/$(BYPRODUCT) - -all: .installed-$(PLATFORM) - -$(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) - rm -rf $(PLATFORM); mkdir -p $(PLATFORM) - cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) - cd $(PLATFORM); mkdir -p build - cd $(PLATFORM); patch -p1 -i ../001-all-cmakeconfig.patch - cd $(PLATFORM); patch -p1 -i ../002-all-enable_docs_pc.patch - cd $(PLATFORM); patch -p1 -i ../003-all-postfix.patch - cd $(PLATFORM); patch -p1 -i ../004-win-pdb.patch - cd $(PLATFORM); patch -p1 -i ../tvos-bitcode-fix.patch - cd $(PLATFORM); patch -p1 -i ../jit_aarch64.patch - cd $(PLATFORM); patch -p1 -i ../ios-clear_cache.patch - cd $(PLATFORM)/build; $(CMAKE) $(CMAKE_OPTIONS) .. - -$(LIBDYLIB): $(PLATFORM) - $(MAKE) -C $(PLATFORM)/build - -.installed-$(PLATFORM): $(LIBDYLIB) - $(MAKE) -C $(PLATFORM)/build install - touch $@ - -clean: - $(MAKE) -C $(PLATFORM)/build clean - rm -f .installed-$(PLATFORM) - -distclean:: - rm -rf $(PLATFORM) .installed-$(PLATFORM) diff --git a/tools/depends/target/pcre/PCRE-VERSION b/tools/depends/target/pcre/PCRE-VERSION deleted file mode 100644 index af533d2bc27ee..0000000000000 --- a/tools/depends/target/pcre/PCRE-VERSION +++ /dev/null @@ -1,6 +0,0 @@ -LIBNAME=pcre -VERSION=8.45 -ARCHIVE=$(LIBNAME)-$(VERSION).tar.bz2 -SHA512=91bff52eed4a2dfc3f3bfdc9c672b88e7e2ffcf3c4b121540af8a4ae8c1ce05178430aa6b8000658b9bb7b4252239357250890e20ceb84b79cdfcde05154061a -BYPRODUCT=libpcre.a -BYPRODUCT_WIN=pcre.lib diff --git a/tools/depends/target/pcre/ios-clear_cache.patch b/tools/depends/target/pcre/ios-clear_cache.patch deleted file mode 100644 index 53d83c0990278..0000000000000 --- a/tools/depends/target/pcre/ios-clear_cache.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/sljit/sljitConfigInternal.h -+++ b/sljit/sljitConfigInternal.h -@@ -279,12 +279,16 @@ - #define SLJIT_API_FUNC_ATTRIBUTE - #endif /* (defined SLJIT_CONFIG_STATIC && SLJIT_CONFIG_STATIC) */ - -+#if defined(__APPLE__) -+#include -+#endif -+ - /****************************/ - /* Instruction cache flush. */ - /****************************/ - - #if (!defined SLJIT_CACHE_FLUSH && defined __has_builtin) --#if __has_builtin(__builtin___clear_cache) -+#if __has_builtin(__builtin___clear_cache) && !defined(__APPLE__) - - #define SLJIT_CACHE_FLUSH(from, to) \ - __builtin___clear_cache((char*)from, (char*)to) diff --git a/tools/depends/target/pcre/jit_aarch64.patch b/tools/depends/target/pcre/jit_aarch64.patch deleted file mode 100644 index ce7a90504c16b..0000000000000 --- a/tools/depends/target/pcre/jit_aarch64.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/sljit/sljitConfigInternal.h -+++ b/sljit/sljitConfigInternal.h -@@ -320,7 +320,7 @@ - #define SLJIT_CACHE_FLUSH(from, to) \ - __builtin___clear_cache((char*)from, (char*)to) - --#elif defined __ANDROID__ -+#elif (defined __ANDROID__ && !defined SLJIT_CONFIG_ARM_64) - - /* Android lacks __clear_cache; instead, cacheflush should be used. */ - diff --git a/tools/depends/target/pcre/tvos-bitcode-fix.patch b/tools/depends/target/pcre/tvos-bitcode-fix.patch deleted file mode 100644 index 8b217fb45c2a4..0000000000000 --- a/tools/depends/target/pcre/tvos-bitcode-fix.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/ltmain.sh -+++ b/ltmain.sh -@@ -10427,16 +10427,6 @@ - - case $host in - *-*-darwin*) -- # Don't allow lazy linking, it breaks C++ global constructors -- # But is supposedly fixed on 10.4 or later (yay!). -- if test CXX = "$tagname"; then -- case ${MACOSX_DEPLOYMENT_TARGET-10.0} in -- 10.[0123]) -- func_append compile_command " $wl-bind_at_load" -- func_append finalize_command " $wl-bind_at_load" -- ;; -- esac -- fi - # Time to change all our "foo.ltframework" stuff back to "-framework foo" - compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` - finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` diff --git a/tools/depends/target/pcre2/001-all-enable_docs_pc.patch b/tools/depends/target/pcre2/001-all-enable_docs_pc.patch new file mode 100644 index 0000000000000..ad6fc0cb7c3af --- /dev/null +++ b/tools/depends/target/pcre2/001-all-enable_docs_pc.patch @@ -0,0 +1,18 @@ +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -1095,6 +1095,7 @@ + configure_file(${PCRE2_CONFIG_VERSION_IN} ${PCRE2_CONFIG_VERSION_OUT} @ONLY) + install(FILES ${PCRE2_CONFIG_OUT} ${PCRE2_CONFIG_VERSION_OUT} DESTINATION cmake) + ++if(ENABLE_DOCS) + FILE(GLOB html ${PROJECT_SOURCE_DIR}/doc/html/*.html) + FILE(GLOB man1 ${PROJECT_SOURCE_DIR}/doc/*.1) + FILE(GLOB man3 ${PROJECT_SOURCE_DIR}/doc/*.3) +@@ -1102,6 +1103,7 @@ + INSTALL(FILES ${man1} DESTINATION man/man1) + INSTALL(FILES ${man3} DESTINATION man/man3) + INSTALL(FILES ${html} DESTINATION share/doc/pcre2/html) ++endif() + + IF(MSVC AND INSTALL_MSVC_PDB) + INSTALL(FILES ${dll_pdb_files} DESTINATION bin CONFIGURATIONS RelWithDebInfo) diff --git a/tools/depends/target/pcre2/002-all-cmake-config-installdir.patch b/tools/depends/target/pcre2/002-all-cmake-config-installdir.patch new file mode 100644 index 0000000000000..21fb4126810ca --- /dev/null +++ b/tools/depends/target/pcre2/002-all-cmake-config-installdir.patch @@ -0,0 +1,11 @@ +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -1093,7 +1093,7 @@ + set(PCRE2_CONFIG_VERSION_IN ${CMAKE_CURRENT_SOURCE_DIR}/cmake/pcre2-config-version.cmake.in) + set(PCRE2_CONFIG_VERSION_OUT ${CMAKE_CURRENT_BINARY_DIR}/cmake/pcre2-config-version.cmake) + configure_file(${PCRE2_CONFIG_VERSION_IN} ${PCRE2_CONFIG_VERSION_OUT} @ONLY) +-install(FILES ${PCRE2_CONFIG_OUT} ${PCRE2_CONFIG_VERSION_OUT} DESTINATION cmake) ++install(FILES ${PCRE2_CONFIG_OUT} ${PCRE2_CONFIG_VERSION_OUT} DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) + + if(ENABLE_DOCS) + FILE(GLOB html ${PROJECT_SOURCE_DIR}/doc/html/*.html) diff --git a/tools/depends/target/pcre2/Makefile b/tools/depends/target/pcre2/Makefile new file mode 100644 index 0000000000000..936deef69107a --- /dev/null +++ b/tools/depends/target/pcre2/Makefile @@ -0,0 +1,46 @@ +include ../../Makefile.include PCRE2-VERSION ../../download-files.include +DEPS = ../../Makefile.include Makefile PCRE2-VERSION ../../download-files.include \ + 001-all-enable_docs_pc.patch \ + 002-all-cmake-config-installdir.patch + +# configuration settings +CMAKE_OPTIONS=-DBUILD_STATIC_LIBS=ON \ + -DPCRE2_BUILD_PCRE2_8=ON \ + -DPCRE2_BUILD_PCRE2_16=OFF \ + -DPCRE2_BUILD_PCRE2_32=OFF \ + -DPCRE2_SUPPORT_JIT=ON \ + -DPCRE2_SUPPORT_UNICODE=ON \ + -DPCRE2_BUILD_PCRE2GREP=OFF \ + -DPCRE2_BUILD_TESTS=OFF \ + -DENABLE_DOCS=OFF + +ifeq ($(OS),darwin_embedded) + # build default is off, look to see if we want to enable this for non darwin_embedded platforms + CMAKE_OPTIONS+= -DPCRE2_SUPPORT_JIT=OFF +endif + +LIBDYLIB=$(PLATFORM)/build/$(BYPRODUCT) + +all: .installed-$(PLATFORM) + +$(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) + rm -rf $(PLATFORM); mkdir -p $(PLATFORM) + cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) + cd $(PLATFORM); mkdir -p build + cd $(PLATFORM); patch -p1 -i ../001-all-enable_docs_pc.patch + cd $(PLATFORM); patch -p1 -i ../002-all-cmake-config-installdir.patch + cd $(PLATFORM)/build; $(CMAKE) $(CMAKE_OPTIONS) .. + +$(LIBDYLIB): $(PLATFORM) + $(MAKE) -C $(PLATFORM)/build + +.installed-$(PLATFORM): $(LIBDYLIB) + $(MAKE) -C $(PLATFORM)/build install + touch $@ + +clean: + $(MAKE) -C $(PLATFORM)/build clean + rm -f .installed-$(PLATFORM) + +distclean:: + rm -rf $(PLATFORM) .installed-$(PLATFORM) diff --git a/tools/depends/target/pcre2/PCRE2-VERSION b/tools/depends/target/pcre2/PCRE2-VERSION new file mode 100644 index 0000000000000..800eebf0b9737 --- /dev/null +++ b/tools/depends/target/pcre2/PCRE2-VERSION @@ -0,0 +1,7 @@ +LIBNAME=pcre2 +VERSION=10.42 +ARCHIVE=$(LIBNAME)-$(VERSION).tar.bz2 +SHA512=72fbde87fecec3aa4b47225dd919ea1d55e97f2cbcf02aba26e5a0d3b1ffb58c25a80a9ef069eb99f9cf4e41ba9604ad06a7ec159870e1e875d86820e12256d3 +BYPRODUCT=libpcre2-8.a +BYPRODUCT_WIN=pcre2-8-static.lib + From 8e0c4fd22a0b1f9c54b0536960752263f0d5a04f Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 2 Jun 2024 13:56:07 +1000 Subject: [PATCH 057/651] Migrate PCRE to PCRE2 and remove PCRE --- CMakeLists.txt | 4 +- docs/README.Fedora.md | 2 +- docs/README.FreeBSD.md | 5 +- docs/README.Linux.md | 2 +- docs/README.Ubuntu.md | 2 +- docs/README.openSUSE.md | 2 +- tools/buildsteps/freebsd/configure-xbmc | 2 +- xbmc/utils/RegExp.cpp | 202 ++++++++++++------------ xbmc/utils/RegExp.h | 22 +-- xbmc/utils/StringUtils.cpp | 6 +- 10 files changed, 118 insertions(+), 131 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c52b76a401cc..d0ce337b0a4d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,7 +93,7 @@ dependent_option(ENABLE_INTERNAL_CURL "Enable internal libcurl?") dependent_option(ENABLE_INTERNAL_FLATBUFFERS "Enable internal flatbuffers?") dependent_option(ENABLE_INTERNAL_FMT "Enable internal fmt?") dependent_option(ENABLE_INTERNAL_NFS "Enable internal libnfs?") -dependent_option(ENABLE_INTERNAL_PCRE "Enable internal pcre?") +dependent_option(ENABLE_INTERNAL_PCRE2 "Enable internal pcre2?") dependent_option(ENABLE_INTERNAL_RapidJSON "Enable internal rapidjson?") # If ENABLE_INTERNAL_FMT is ON, we force ENABLE_INTERNAL_SPDLOG ON as it has a hard @@ -220,7 +220,7 @@ set(required_deps ASS>=0.15.0 LibDvd Lzo2 OpenSSL>=1.1.0 - PCRE + PCRE2 RapidJSON>=1.0.2 Spdlog Sqlite3 diff --git a/docs/README.Fedora.md b/docs/README.Fedora.md index 2d89f8f2d1842..5f6bf877eb490 100644 --- a/docs/README.Fedora.md +++ b/docs/README.Fedora.md @@ -71,7 +71,7 @@ If you get a `package not found` type of message with the below command, remove Install build dependencies: ``` -sudo dnf install alsa-lib-devel autoconf automake avahi-compat-libdns_sd-devel avahi-devel bluez-libs-devel bzip2-devel cmake curl dbus-devel flatbuffers flatbuffers-devel fmt-devel fontconfig-devel freetype-devel fribidi-devel fstrcmp-devel gawk gcc gcc-c++ gettext gettext-devel giflib-devel gperf gtest-devel java-11-openjdk-headless jre lcms2-devel libao-devel libass-devel libbluray-devel libcap-devel libcdio-devel libcec-devel libcurl-devel libidn2-devel libjpeg-turbo-devel libmicrohttpd-devel libmpc-devel libnfs-devel libplist-devel libpng12-devel libsmbclient-devel libtool libtool-ltdl-devel libudev-devel libunistring libunistring-devel libusb-devel libuuid-devel libva-devel libvdpau-devel libxkbcommon-devel libxml2-devel libXmu-devel libXrandr-devel libxslt-devel libXt-devel lirc-devel lzo-devel make mariadb-devel mesa-libEGL-devel mesa-libGL-devel mesa-libGLU-devel mesa-libGLw-devel mesa-libOSMesa-devel nasm openssl-devel openssl-libs patch pcre-devel pulseaudio-libs-devel python3-devel python3-pillow rapidjson-devel shairplay-devel spdlog-devel sqlite-devel swig taglib-devel tinyxml-devel tinyxml2-devel trousers-devel uuid-devel zlib-devel +sudo dnf install alsa-lib-devel autoconf automake avahi-compat-libdns_sd-devel avahi-devel bluez-libs-devel bzip2-devel cmake curl dbus-devel flatbuffers flatbuffers-devel fmt-devel fontconfig-devel freetype-devel fribidi-devel fstrcmp-devel gawk gcc gcc-c++ gettext gettext-devel giflib-devel gperf gtest-devel java-11-openjdk-headless jre lcms2-devel libao-devel libass-devel libbluray-devel libcap-devel libcdio-devel libcec-devel libcurl-devel libidn2-devel libjpeg-turbo-devel libmicrohttpd-devel libmpc-devel libnfs-devel libplist-devel libpng12-devel libsmbclient-devel libtool libtool-ltdl-devel libudev-devel libunistring libunistring-devel libusb-devel libuuid-devel libva-devel libvdpau-devel libxkbcommon-devel libxml2-devel libXmu-devel libXrandr-devel libxslt-devel libXt-devel lirc-devel lzo-devel make mariadb-devel mesa-libEGL-devel mesa-libGL-devel mesa-libGLU-devel mesa-libGLw-devel mesa-libOSMesa-devel nasm openssl-devel openssl-libs patch pcre2-devel pulseaudio-libs-devel python3-devel python3-pillow rapidjson-devel shairplay-devel spdlog-devel sqlite-devel swig taglib-devel tinyxml-devel tinyxml2-devel trousers-devel uuid-devel zlib-devel ``` > [!WARNING] diff --git a/docs/README.FreeBSD.md b/docs/README.FreeBSD.md index ef2783a2f6138..0d36409914172 100644 --- a/docs/README.FreeBSD.md +++ b/docs/README.FreeBSD.md @@ -82,7 +82,7 @@ If you get a `package not found` type of message with the below command, remove Install build dependencies: ``` -sudo pkg install autoconf automake avahi-app binutils cmake curl dbus doxygen e2fsprogs-libuuid enca encodings evdev-proto ffmpeg flac flatbuffers font-util fontconfig freetype2 fribidi fstrcmp gawk gettext-tools giflib git glew gmake gmp gnutls googletest gperf gstreamer1-vaapi jpeg-turbo libaacs libass libbdplus libbluray libcapn libcdio libcec libdisplay-info libedit libfmt libgcrypt libgpg-error libidn libinotify libinput libmicrohttpd libnfs libogg libplist librtmp libtool libudev-devd libva libvdpau libvorbis libxkbcommon libxslt lirc lzo2 m4 mariadb-connector-c-3.3.8_1 mesa-libs nasm openjdk21 p8-platform pkgconf python3 rapidjson shairplay sndio spdlog sqlite3 swig taglib tiff tinyxml tinyxml2 wayland-protocols waylandpp xf86-input-keyboard xf86-input-mouse xorg-server xrandr zip +sudo pkg install autoconf automake avahi-app binutils cmake curl dbus doxygen e2fsprogs-libuuid enca encodings evdev-proto ffmpeg flac flatbuffers font-util fontconfig freetype2 fribidi fstrcmp gawk gettext-tools giflib git glew gmake gmp gnutls googletest gperf gstreamer1-vaapi jpeg-turbo libaacs libass libbdplus libbluray libcapn libcdio libcec libdisplay-info libedit libfmt libgcrypt libgpg-error libidn libinotify libinput libmicrohttpd libnfs libogg libplist librtmp libtool libudev-devd libva libvdpau libvorbis libxkbcommon libxslt lirc lzo2 m4 mariadb-connector-c-3.3.8_1 mesa-libs nasm openjdk21 p8-platform pcre2 pkgconf python3 rapidjson shairplay sndio spdlog sqlite3 swig taglib tiff tinyxml tinyxml2 wayland-protocols waylandpp xf86-input-keyboard xf86-input-mouse xorg-server xrandr zip ``` > [!WARNING] @@ -125,11 +125,10 @@ cd $HOME/kodi-build Configure build: ``` -cmake ../kodi -DCMAKE_INSTALL_PREFIX=/usr/local -DENABLE_INTERNAL_PCRE=ON -DAPP_RENDER_SYSTEM="gl" +cmake ../kodi -DCMAKE_INSTALL_PREFIX=/usr/local -DAPP_RENDER_SYSTEM="gl" ``` > [!NOTE] > You can use `gles` instead of `gl` if you want to build with `GLES`. -> Building internal PCRE is recommended since system pcre libs are EOLed by upstream. ### 4.2. Build ``` diff --git a/docs/README.Linux.md b/docs/README.Linux.md index 98dd5b9e9469b..45d4d4928d3e6 100644 --- a/docs/README.Linux.md +++ b/docs/README.Linux.md @@ -80,7 +80,7 @@ The following is the list of packages that are used to build Kodi on Debian/Ubun > [!NOTE] > Kodi requires a compiler with C++17 support, i.e. gcc >= 7 or clang >= 5 -* autoconf, automake, autopoint, gettext, autotools-dev, cmake, curl, default-jre | openjdk-6-jre | openjdk-7-jre, gawk, gcc (>= 7) | gcc-7, g++ (>= 7) | g++-7, cpp (>= 7) | cpp-7, flatbuffers, gdc, gperf, libasound2-dev | libasound-dev, libass-dev (>= 0.9.8), libavahi-client-dev, libavahi-common-dev, libbluetooth-dev, libbluray-dev, libbz2-dev, libcdio-dev, libcec4-dev | libcec-dev, libp8-platform-dev, libcrossguid-dev, libcurl4-openssl-dev | libcurl4-gnutls-dev | libcurl-dev, libcwiid-dev, libdbus-1-dev, libegl1-mesa-dev, libenca-dev, libflac-dev, libfontconfig-dev, libfmt3-dev | libfmt-dev, libfreetype6-dev, libfribidi-dev, libfstrcmp-dev, libgcrypt-dev, libgif-dev (>= 5.0.5), libgles2-mesa-dev [armel] | libgl1-mesa-dev | libgl-dev, libglew-dev, libglu1-mesa-dev | libglu-dev, libgnutls-dev | libgnutls28-dev, libgpg-error-dev, libgtest-dev, libiso9660-dev, libjpeg-dev, liblcms2-dev, liblirc-dev, libltdl-dev, liblzo2-dev, libmicrohttpd-dev, libmysqlclient-dev, libnfs-dev, libogg-dev, libomxil-bellagio-dev [armel], libpcre3-dev, libplist-dev, libpng12-dev | libpng-dev, libpulse-dev, libshairplay-dev, libsmbclient-dev, libspdlog-dev, libsqlite3-dev, libssl-dev, libtag1-dev (>= 1.8) | libtag1x8, libtiff5-dev | libtiff-dev | libtiff4-dev, libtinyxml-dev, libtinyxml2-dev, libtool, libudev-dev, libunistring-dev, libva-dev, libvdpau-dev, libvorbis-dev, libxkbcommon-dev, libxmu-dev, libxrandr-dev, libxslt1-dev | libxslt-dev, libxt-dev, waylandpp-dev | netcat, wayland-protocols | wipe, lsb-release, meson (>= 0.47.0), nasm (>= 2.14), ninja-build, python3-dev, python3-pil | python-imaging, python-support | python3-minimal, rapidjson-dev, swig, unzip, uuid-dev, zip, zlib1g-dev +* autoconf, automake, autopoint, gettext, autotools-dev, cmake, curl, default-jre | openjdk-6-jre | openjdk-7-jre, gawk, gcc (>= 7) | gcc-7, g++ (>= 7) | g++-7, cpp (>= 7) | cpp-7, flatbuffers, gdc, gperf, libasound2-dev | libasound-dev, libass-dev (>= 0.9.8), libavahi-client-dev, libavahi-common-dev, libbluetooth-dev, libbluray-dev, libbz2-dev, libcdio-dev, libcec4-dev | libcec-dev, libp8-platform-dev, libcrossguid-dev, libcurl4-openssl-dev | libcurl4-gnutls-dev | libcurl-dev, libcwiid-dev, libdbus-1-dev, libegl1-mesa-dev, libenca-dev, libflac-dev, libfontconfig-dev, libfmt3-dev | libfmt-dev, libfreetype6-dev, libfribidi-dev, libfstrcmp-dev, libgcrypt-dev, libgif-dev (>= 5.0.5), libgles2-mesa-dev [armel] | libgl1-mesa-dev | libgl-dev, libglew-dev, libglu1-mesa-dev | libglu-dev, libgnutls-dev | libgnutls28-dev, libgpg-error-dev, libgtest-dev, libiso9660-dev, libjpeg-dev, liblcms2-dev, liblirc-dev, libltdl-dev, liblzo2-dev, libmicrohttpd-dev, libmysqlclient-dev, libnfs-dev, libogg-dev, libomxil-bellagio-dev [armel], libpcre2-dev, libplist-dev, libpng12-dev | libpng-dev, libpulse-dev, libshairplay-dev, libsmbclient-dev, libspdlog-dev, libsqlite3-dev, libssl-dev, libtag1-dev (>= 1.8) | libtag1x8, libtiff5-dev | libtiff-dev | libtiff4-dev, libtinyxml-dev, libtinyxml2-dev, libtool, libudev-dev, libunistring-dev, libva-dev, libvdpau-dev, libvorbis-dev, libxkbcommon-dev, libxmu-dev, libxrandr-dev, libxslt1-dev | libxslt-dev, libxt-dev, waylandpp-dev | netcat, wayland-protocols | wipe, lsb-release, meson (>= 0.47.0), nasm (>= 2.14), ninja-build, python3-dev, python3-pil | python-imaging, python-support | python3-minimal, rapidjson-dev, swig, unzip, uuid-dev, zip, zlib1g-dev ### 3.1. Build missing dependencies Some packages may be missing or outdated in older distributions. Notably `crossguid`, `libfmt`, `libspdlog`, `waylandpp`, `wayland-protocols`, etc. are known to be outdated or missing. Fortunately there is an easy way to build individual dependencies with **[Kodi's unified depends build system](../tools/depends/README.md)**. diff --git a/docs/README.Ubuntu.md b/docs/README.Ubuntu.md index 236f3a34f1f7e..6b991db76e6b1 100644 --- a/docs/README.Ubuntu.md +++ b/docs/README.Ubuntu.md @@ -126,7 +126,7 @@ If you get a `package not found` type of message with the below command, remove Install build dependencies manually: ``` -sudo apt install debhelper autoconf automake autopoint gettext autotools-dev cmake curl default-jre doxygen gawk gcc gdc gperf libasound2-dev libass-dev libavahi-client-dev libavahi-common-dev libbluetooth-dev libbluray-dev libbz2-dev libcdio-dev libp8-platform-dev libcrossguid-dev libcurl4-openssl-dev libcwiid-dev libdbus-1-dev libdrm-dev libegl1-mesa-dev libenca-dev libflac-dev libfmt-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgcrypt-dev libgif-dev libgles2-mesa-dev libgl1-mesa-dev libglu1-mesa-dev libgnutls28-dev libgpg-error-dev libgtest-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libogg-dev libpcre3-dev libplist-dev libpng-dev libpulse-dev libshairplay-dev libsmbclient-dev libspdlog-dev libsqlite3-dev libssl-dev libtag1-dev libtiff5-dev libtinyxml-dev libtinyxml2-dev libtool libudev-dev libunistring-dev libva-dev libvdpau-dev libvorbis-dev libxmu-dev libxrandr-dev libxslt1-dev libxt-dev lsb-release meson nasm ninja-build python3-dev python3-pil python3-pip rapidjson-dev swig unzip uuid-dev zip zlib1g-dev +sudo apt install debhelper autoconf automake autopoint gettext autotools-dev cmake curl default-jre doxygen gawk gcc gdc gperf libasound2-dev libass-dev libavahi-client-dev libavahi-common-dev libbluetooth-dev libbluray-dev libbz2-dev libcdio-dev libp8-platform-dev libcrossguid-dev libcurl4-openssl-dev libcwiid-dev libdbus-1-dev libdrm-dev libegl1-mesa-dev libenca-dev libflac-dev libfmt-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgcrypt-dev libgif-dev libgles2-mesa-dev libgl1-mesa-dev libglu1-mesa-dev libgnutls28-dev libgpg-error-dev libgtest-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libogg-dev libpcre2-dev libplist-dev libpng-dev libpulse-dev libshairplay-dev libsmbclient-dev libspdlog-dev libsqlite3-dev libssl-dev libtag1-dev libtiff5-dev libtinyxml-dev libtinyxml2-dev libtool libudev-dev libunistring-dev libva-dev libvdpau-dev libvorbis-dev libxmu-dev libxrandr-dev libxslt1-dev libxt-dev lsb-release meson nasm ninja-build python3-dev python3-pil python3-pip rapidjson-dev swig unzip uuid-dev zip zlib1g-dev ``` > [!WARNING] diff --git a/docs/README.openSUSE.md b/docs/README.openSUSE.md index d1036fee90009..a8b621e1c4a66 100644 --- a/docs/README.openSUSE.md +++ b/docs/README.openSUSE.md @@ -83,7 +83,7 @@ If you get a `package not found` type of message with the below command, remove Install build dependencies: ``` -sudo zypper install alsa-devel autoconf automake bluez-devel boost-devel capi4linux-devel ccache cmake doxygen flac-devel fribidi-devel fstrcmp-devel gcc gcc-c++ gettext-devel giflib-devel glew-devel googletest gperf java-openjdk libass-devel libavahi-devel libbluray-devel libbz2-devel libcap-devel libcap-ng-devel libcdio-devel libcec-devel libcurl-devel libdvdread-devel libgudev-1_0-devel libidn2-devel libjasper-devel libjpeg-devel liblcms2-devel libmad-devel libmicrohttpd-devel libmodplug-devel libmpeg2-devel libmysqlclient-devel libnfs-devel libogg-devel libpcap-devel libplist-devel libpng12-devel libpulse-devel libsamplerate-devel libsmbclient-devel libtag-devel libtiff-devel libtool libudev-devel libuuid-devel libva-devel libvdpau-devel libvorbis-devel libXrandr-devel libXrender-devel libxslt-devel lirc-devel lzo-devel make Mesa-libEGL-devel Mesa-libGLESv2-devel Mesa-libGLESv3-devel nasm patch pcre-devel python3-devel python3-Pillow randrproto-devel renderproto-devel shairplay-devel sqlite3-devel swig tinyxml-devel tinyxml2-devel +sudo zypper install alsa-devel autoconf automake bluez-devel boost-devel capi4linux-devel ccache cmake doxygen flac-devel fribidi-devel fstrcmp-devel gcc gcc-c++ gettext-devel giflib-devel glew-devel googletest gperf java-openjdk libass-devel libavahi-devel libbluray-devel libbz2-devel libcap-devel libcap-ng-devel libcdio-devel libcec-devel libcurl-devel libdvdread-devel libgudev-1_0-devel libidn2-devel libjasper-devel libjpeg-devel liblcms2-devel libmad-devel libmicrohttpd-devel libmodplug-devel libmpeg2-devel libmysqlclient-devel libnfs-devel libogg-devel libpcap-devel libplist-devel libpng12-devel libpulse-devel libsamplerate-devel libsmbclient-devel libtag-devel libtiff-devel libtool libudev-devel libuuid-devel libva-devel libvdpau-devel libvorbis-devel libXrandr-devel libXrender-devel libxslt-devel lirc-devel lzo-devel make Mesa-libEGL-devel Mesa-libGLESv2-devel Mesa-libGLESv3-devel nasm patch pcre2-devel python3-devel python3-Pillow randrproto-devel renderproto-devel shairplay-devel sqlite3-devel swig tinyxml-devel tinyxml2-devel ``` > [!WARNING] diff --git a/tools/buildsteps/freebsd/configure-xbmc b/tools/buildsteps/freebsd/configure-xbmc index 1044b688a60f5..b6c2260902396 100644 --- a/tools/buildsteps/freebsd/configure-xbmc +++ b/tools/buildsteps/freebsd/configure-xbmc @@ -4,4 +4,4 @@ XBMC_PLATFORM_DIR=freebsd mkdir -p $WORKSPACE/build cd $WORKSPACE/build -cmake -DCMAKE_BUILD_TYPE=$Configuration -DENABLE_INTERNAL_PCRE=ON -DAPP_RENDER_SYSTEM=gl .. +cmake -DCMAKE_BUILD_TYPE=$Configuration -DAPP_RENDER_SYSTEM=gl .. diff --git a/xbmc/utils/RegExp.cpp b/xbmc/utils/RegExp.cpp index 40263a28eb8d4..cb620208dcba8 100644 --- a/xbmc/utils/RegExp.cpp +++ b/xbmc/utils/RegExp.cpp @@ -16,27 +16,6 @@ #include #include -using namespace PCRE; - -#ifndef PCRE_UCP -#define PCRE_UCP 0 -#endif // PCRE_UCP - -#ifdef PCRE_CONFIG_JIT -#define PCRE_HAS_JIT_CODE 1 -#endif - -#ifndef PCRE_STUDY_JIT_COMPILE -#define PCRE_STUDY_JIT_COMPILE 0 -#endif -#ifndef PCRE_INFO_JIT -// some unused number -#define PCRE_INFO_JIT 2048 -#endif -#ifndef PCRE_HAS_JIT_CODE -#define pcre_free_study(x) pcre_free((x)) -#endif - int CRegExp::m_Utf8Supported = -1; int CRegExp::m_UcpSupported = -1; int CRegExp::m_JitSupported = -1; @@ -51,25 +30,24 @@ void CRegExp::InitValues(bool caseless /*= false*/, CRegExp::utf8Mode utf8 /*= a { m_utf8Mode = utf8; m_re = NULL; - m_sd = NULL; - m_iOptions = PCRE_DOTALL | PCRE_NEWLINE_ANY; + m_ctxt = nullptr; + m_iOptions = PCRE2_DOTALL; if(caseless) - m_iOptions |= PCRE_CASELESS; + m_iOptions |= PCRE2_CASELESS; if (m_utf8Mode == forceUtf8) { if (IsUtf8Supported()) - m_iOptions |= PCRE_UTF8; + m_iOptions |= PCRE2_UTF; if (AreUnicodePropertiesSupported()) - m_iOptions |= PCRE_UCP; + m_iOptions |= PCRE2_UCP; } m_offset = 0; m_jitCompiled = false; m_bMatched = false; m_iMatchCount = 0; - m_jitStack = NULL; - - memset(m_iOvector, 0, sizeof(m_iOvector)); + m_iOvector = nullptr; + m_jitStack = NULL; } CRegExp::CRegExp(bool caseless, CRegExp::utf8Mode utf8, const char *re, studyMode study /*= NoStudy*/) @@ -225,7 +203,8 @@ bool CRegExp::isCharClassWithUnicode(const std::string& regexp, size_t& pos) CRegExp::CRegExp(const CRegExp& re) { m_re = NULL; - m_sd = NULL; + m_ctxt = nullptr; + m_iOvector = nullptr; m_jitStack = NULL; m_utf8Mode = re.m_utf8Mode; m_iOptions = re.m_iOptions; @@ -240,12 +219,13 @@ CRegExp& CRegExp::operator=(const CRegExp& re) m_pattern = re.m_pattern; if (re.m_re) { - if (pcre_fullinfo(re.m_re, NULL, PCRE_INFO_SIZE, &size) >= 0) + if (pcre2_pattern_info(re.m_re, PCRE2_INFO_SIZE, &size) >= 0) { - if ((m_re = (pcre*)malloc(size))) + if ((m_re = pcre2_code_copy(re.m_re))) { - memcpy(m_re, re.m_re, size); - memcpy(m_iOvector, re.m_iOvector, OVECCOUNT*sizeof(int)); + if (re.m_ctxt) + m_ctxt = pcre2_match_context_copy(re.m_ctxt); + m_iOvector = re.m_iOvector; m_offset = re.m_offset; m_iMatchCount = re.m_iMatchCount; m_bMatched = re.m_bMatched; @@ -273,18 +253,27 @@ bool CRegExp::RegComp(const char *re, studyMode study /*= NoStudy*/) m_jitCompiled = false; m_bMatched = false; m_iMatchCount = 0; - const char *errMsg = NULL; - int errOffset = 0; - int options = m_iOptions; + pcre2_compile_context* ctxt; + int errCode; + char errMsg[120]; + PCRE2_SIZE errOffset; + uint32_t options = m_iOptions; if (m_utf8Mode == autoUtf8 && requireUtf8(re)) - options |= (IsUtf8Supported() ? PCRE_UTF8 : 0) | (AreUnicodePropertiesSupported() ? PCRE_UCP : 0); + options |= + (IsUtf8Supported() ? PCRE2_UTF : 0) | (AreUnicodePropertiesSupported() ? PCRE2_UCP : 0); Cleanup(); - m_re = pcre_compile(re, options, &errMsg, &errOffset, NULL); + ctxt = pcre2_compile_context_create(NULL); + pcre2_set_newline(ctxt, PCRE2_NEWLINE_ANY); + m_re = pcre2_compile(reinterpret_cast(re), PCRE2_ZERO_TERMINATED, options, &errCode, + &errOffset, ctxt); + pcre2_compile_context_free(ctxt); + if (!m_re) { m_pattern.clear(); + pcre2_get_error_message(errCode, reinterpret_cast(errMsg), sizeof(errMsg)); CLog::Log(LOGERROR, "PCRE: {}. Compilation failed at offset {} in expression '{}'", errMsg, errOffset, re); return false; @@ -295,23 +284,12 @@ bool CRegExp::RegComp(const char *re, studyMode study /*= NoStudy*/) if (study) { const bool jitCompile = (study == StudyWithJitComp) && IsJitSupported(); - const int studyOptions = jitCompile ? PCRE_STUDY_JIT_COMPILE : 0; - - m_sd = pcre_study(m_re, studyOptions, &errMsg); - if (errMsg != NULL) - { - CLog::Log(LOGWARNING, "{}: PCRE error \"{}\" while studying expression", __FUNCTION__, - errMsg); - if (m_sd != NULL) - { - pcre_free_study(m_sd); - m_sd = NULL; - } - } - else if (jitCompile) + if (jitCompile) { - int jitPresent = 0; - m_jitCompiled = (pcre_fullinfo(m_re, m_sd, PCRE_INFO_JIT, &jitPresent) == 0 && jitPresent == 1); + pcre2_jit_compile(m_re, PCRE2_JIT_COMPLETE); + size_t jitPresent = 0; + m_jitCompiled = + (pcre2_pattern_info(m_re, PCRE2_INFO_JITSIZE, &jitPresent) == 0 && jitPresent > 0); } } @@ -325,6 +303,9 @@ int CRegExp::RegFind(const char *str, unsigned int startoffset /*= 0*/, int maxN int CRegExp::PrivateRegFind(size_t bufferLen, const char *str, unsigned int startoffset /* = 0*/, int maxNumberOfCharsToTest /*= -1*/) { + pcre2_match_data* md; + PCRE2_SIZE offset; + m_offset = 0; m_bMatched = false; m_iMatchCount = 0; @@ -347,37 +328,46 @@ int CRegExp::PrivateRegFind(size_t bufferLen, const char *str, unsigned int star return -1; } -#ifdef PCRE_HAS_JIT_CODE + if (!m_ctxt) + m_ctxt = pcre2_match_context_create(NULL); + if (m_jitCompiled && !m_jitStack) { - m_jitStack = pcre_jit_stack_alloc(32*1024, 512*1024); + m_jitStack = pcre2_jit_stack_create(32 * 1024, 512 * 1024, NULL); if (m_jitStack == NULL) CLog::Log(LOGWARNING, "{}: can't allocate address space for JIT stack", __FUNCTION__); - pcre_assign_jit_stack(m_sd, NULL, m_jitStack); + pcre2_jit_stack_assign(m_ctxt, NULL, m_jitStack); } -#endif if (maxNumberOfCharsToTest >= 0) bufferLen = std::min(bufferLen, startoffset + maxNumberOfCharsToTest); m_subject.assign(str + startoffset, bufferLen - startoffset); - int rc = pcre_exec(m_re, NULL, m_subject.c_str(), m_subject.length(), 0, 0, m_iOvector, OVECCOUNT); + md = pcre2_match_data_create(OVECCOUNT, nullptr); + int rc = pcre2_match(m_re, reinterpret_cast(m_subject.c_str()), m_subject.length(), 0, + 0, md, m_ctxt); + m_iOvector = pcre2_get_ovector_pointer(md); + offset = pcre2_get_startchar(md); + pcre2_match_data_free(md); if (rc<1) { static const int fragmentLen = 80; // length of excerpt before erroneous char for log switch(rc) { - case PCRE_ERROR_NOMATCH: - return -1; + case PCRE2_ERROR_NOMATCH: + return -1; - case PCRE_ERROR_MATCHLIMIT: - CLog::Log(LOGERROR, "PCRE: Match limit reached"); - return -1; + case PCRE2_ERROR_MATCHLIMIT: + CLog::Log(LOGERROR, "PCRE: Match limit reached"); + return -1; -#ifdef PCRE_ERROR_SHORTUTF8 - case PCRE_ERROR_SHORTUTF8: + case PCRE2_ERROR_UTF8_ERR1: + case PCRE2_ERROR_UTF8_ERR2: + case PCRE2_ERROR_UTF8_ERR3: + case PCRE2_ERROR_UTF8_ERR4: + case PCRE2_ERROR_UTF8_ERR5: { const size_t startPos = (m_subject.length() > fragmentLen) ? CUtf8Utils::RFindValidUtf8Char(m_subject, m_subject.length() - fragmentLen) : 0; if (startPos != std::string::npos) @@ -389,28 +379,44 @@ int CRegExp::PrivateRegFind(size_t bufferLen, const char *str, unsigned int star CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character at the end of string"); return -1; } -#endif - case PCRE_ERROR_BADUTF8: + case PCRE2_ERROR_UTF8_ERR6: + case PCRE2_ERROR_UTF8_ERR7: + case PCRE2_ERROR_UTF8_ERR8: + case PCRE2_ERROR_UTF8_ERR9: + case PCRE2_ERROR_UTF8_ERR10: + case PCRE2_ERROR_UTF8_ERR11: + case PCRE2_ERROR_UTF8_ERR12: + case PCRE2_ERROR_UTF8_ERR13: + case PCRE2_ERROR_UTF8_ERR14: + case PCRE2_ERROR_UTF8_ERR15: + case PCRE2_ERROR_UTF8_ERR16: + case PCRE2_ERROR_UTF8_ERR17: + case PCRE2_ERROR_UTF8_ERR18: + case PCRE2_ERROR_UTF8_ERR19: + case PCRE2_ERROR_UTF8_ERR20: + case PCRE2_ERROR_UTF8_ERR21: { + char errbuf[120]; + + pcre2_get_error_message(rc, reinterpret_cast(errbuf), sizeof(errbuf)); const size_t startPos = (m_iOvector[0] > fragmentLen) ? CUtf8Utils::RFindValidUtf8Char(m_subject, m_iOvector[0] - fragmentLen) : 0; - if (m_iOvector[0] >= 0 && startPos != std::string::npos) + if ((int)m_iOvector[0] >= 0 && startPos != std::string::npos) CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character, error code: {}, position: {}. Text before bad " "char: \"{}\"", - m_iOvector[1], m_iOvector[0], - m_subject.substr(startPos, m_iOvector[0] - startPos + 1)); + errbuf, offset, m_subject.substr(startPos, m_iOvector[0] - startPos + 1)); else - CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character, error code: {}, position: {}", - m_iOvector[1], m_iOvector[0]); + CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character, error code: {}, position: {}", errbuf, + offset); return -1; } - case PCRE_ERROR_BADUTF8_OFFSET: - CLog::Log(LOGERROR, "PCRE: Offset is pointing to the middle of UTF-8 character"); - return -1; + case PCRE2_ERROR_BADUTFOFFSET: + CLog::Log(LOGERROR, "PCRE: Offset is pointing to the middle of UTF-8 character"); + return -1; - default: - CLog::Log(LOGERROR, "PCRE: Unknown error: {}", rc); - return -1; + default: + CLog::Log(LOGERROR, "PCRE: Unknown error: {}", rc); + return -1; } } m_offset = startoffset; @@ -423,7 +429,7 @@ int CRegExp::GetCaptureTotal() const { int c = -1; if (m_re) - pcre_fullinfo(m_re, NULL, PCRE_INFO_CAPTURECOUNT, &c); + pcre2_pattern_info(m_re, PCRE2_INFO_CAPTURECOUNT, &c); return c; } @@ -528,23 +534,21 @@ void CRegExp::Cleanup() { if (m_re) { - pcre_free(m_re); - m_re = NULL; + pcre2_code_free(m_re); + m_re = nullptr; } - if (m_sd) + if (m_ctxt) { - pcre_free_study(m_sd); - m_sd = NULL; + pcre2_match_context_free(m_ctxt); + m_ctxt = nullptr; } -#ifdef PCRE_HAS_JIT_CODE if (m_jitStack) { - pcre_jit_stack_free(m_jitStack); + pcre2_jit_stack_free(m_jitStack); m_jitStack = NULL; } -#endif } inline bool CRegExp::IsValidSubNumber(int iSub) const @@ -557,7 +561,7 @@ bool CRegExp::IsUtf8Supported(void) { if (m_Utf8Supported == -1) { - if (pcre_config(PCRE_CONFIG_UTF8, &m_Utf8Supported) != 0) + if (pcre2_config(PCRE2_CONFIG_UNICODE, &m_Utf8Supported) < 0) m_Utf8Supported = 0; } @@ -566,13 +570,11 @@ bool CRegExp::IsUtf8Supported(void) bool CRegExp::AreUnicodePropertiesSupported(void) { -#if defined(PCRE_CONFIG_UNICODE_PROPERTIES) && PCRE_UCP != 0 if (m_UcpSupported == -1) { - if (pcre_config(PCRE_CONFIG_UNICODE_PROPERTIES, &m_UcpSupported) != 0) + if (pcre2_config(PCRE2_CONFIG_UNICODE, &m_UcpSupported) < 0) m_UcpSupported = 0; } -#endif return m_UcpSupported == 1; } @@ -595,13 +597,13 @@ bool CRegExp::LogCheckUtf8Support(void) if (!utf8FullSupport) { + char ver[24]; + + pcre2_config(PCRE2_CONFIG_VERSION, ver); CLog::Log(LOGINFO, - "Consider installing PCRE lib version 8.10 or later with enabled Unicode properties " + "Consider installing PCRE lib version 10.10 or later with enabled Unicode properties " "and UTF-8 support. Your PCRE lib version: {}", - PCRE::pcre_version()); -#if PCRE_UCP == 0 - CLog::Log(LOGINFO, "You will need to rebuild XBMC after PCRE lib update."); -#endif + ver); } return utf8FullSupport; @@ -611,9 +613,7 @@ bool CRegExp::IsJitSupported(void) { if (m_JitSupported == -1) { -#ifdef PCRE_HAS_JIT_CODE - if (pcre_config(PCRE_CONFIG_JIT, &m_JitSupported) != 0) -#endif + if (pcre2_config(PCRE2_CONFIG_JIT, &m_JitSupported) < 0) m_JitSupported = 0; } diff --git a/xbmc/utils/RegExp.h b/xbmc/utils/RegExp.h index feea89cd0d841..90d3db541e77a 100644 --- a/xbmc/utils/RegExp.h +++ b/xbmc/utils/RegExp.h @@ -13,16 +13,8 @@ #include #include -/* make sure stdlib.h is included before including pcre.h inside the - namespace; this works around stdlib.h definitions also living in - the PCRE namespace */ -#include - -namespace PCRE { -struct real_pcre_jit_stack; // forward declaration for PCRE without JIT -typedef struct real_pcre_jit_stack pcre_jit_stack; -#include -} +#define PCRE2_CODE_UNIT_WIDTH 8 +#include class CRegExp { @@ -138,17 +130,17 @@ class CRegExp void Cleanup(); inline bool IsValidSubNumber(int iSub) const; - PCRE::pcre* m_re; - PCRE::pcre_extra* m_sd; + pcre2_code* m_re; + pcre2_match_context* m_ctxt; static const int OVECCOUNT=(m_MaxNumOfBackrefrences + 1) * 3; unsigned int m_offset; - int m_iOvector[OVECCOUNT]; + PCRE2_SIZE* m_iOvector; utf8Mode m_utf8Mode; int m_iMatchCount; - int m_iOptions; + uint32_t m_iOptions; bool m_jitCompiled; bool m_bMatched; - PCRE::pcre_jit_stack* m_jitStack; + pcre2_jit_stack* m_jitStack; std::string m_subject; std::string m_pattern; static int m_Utf8Supported; diff --git a/xbmc/utils/StringUtils.cpp b/xbmc/utils/StringUtils.cpp index c2c36010c354c..1da187446dc90 100644 --- a/xbmc/utils/StringUtils.cpp +++ b/xbmc/utils/StringUtils.cpp @@ -30,6 +30,7 @@ #include "LangInfo.h" #include "StringUtils.h" #include "XBDateTime.h" +#include "utils/RegExp.h" #include #include @@ -46,11 +47,6 @@ #include #include -// don't move or std functions end up in PCRE namespace -// clang-format off -#include "utils/RegExp.h" -// clang-format on - #define FORMAT_BLOCK_SIZE 512 // # of bytes for initial allocation for printf namespace KODI::UTILS From bdec266e7af0a627174633b2f2200facaef563ca Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sun, 16 Jun 2024 11:49:43 +0200 Subject: [PATCH 058/651] [VideoPlayer] define message queue time size in a single place --- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 19 ++++++++++---- xbmc/cores/VideoPlayer/VideoPlayer.h | 2 ++ xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp | 23 +++++++++------- xbmc/cores/VideoPlayer/VideoPlayerAudio.h | 6 ++++- xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp | 29 +++++++++++---------- xbmc/cores/VideoPlayer/VideoPlayerVideo.h | 15 ++++++----- 6 files changed, 59 insertions(+), 35 deletions(-) diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index 78e9cc3cdc5c4..d2568bd710829 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -73,6 +73,11 @@ using namespace KODI; using namespace std::chrono_literals; +namespace +{ +constexpr double VP_MESSAGE_QUEUE_TIME_SIZE = 8.0; +} + //------------------------------------------------------------------------------ // selection streams //------------------------------------------------------------------------------ @@ -582,8 +587,11 @@ void CVideoPlayer::CreatePlayers() if (m_players_created) return; - m_VideoPlayerVideo = new CVideoPlayerVideo(&m_clock, &m_overlayContainer, m_messenger, m_renderManager, *m_processInfo); - m_VideoPlayerAudio = new CVideoPlayerAudio(&m_clock, m_messenger, *m_processInfo); + m_VideoPlayerVideo = + new CVideoPlayerVideo(&m_clock, &m_overlayContainer, m_messenger, m_renderManager, + *m_processInfo, m_messageQueueTimeSize); + m_VideoPlayerAudio = + new CVideoPlayerAudio(&m_clock, m_messenger, *m_processInfo, m_messageQueueTimeSize); m_VideoPlayerSubtitle = new CVideoPlayerSubtitle(&m_overlayContainer, *m_processInfo); m_VideoPlayerTeletext = new CDVDTeletextData(*m_processInfo); m_VideoPlayerRadioRDS = new CDVDRadioRDSData(*m_processInfo); @@ -636,6 +644,7 @@ CVideoPlayer::CVideoPlayer(IPlayerCallback& callback) m_HasVideo = false; m_HasAudio = false; m_UpdateStreamDetails = false; + m_messageQueueTimeSize = VP_MESSAGE_QUEUE_TIME_SIZE; m_SkipCommercials = true; @@ -1879,7 +1888,7 @@ void CVideoPlayer::HandlePlaySpeed() // Note: Previously used cache.level >= 1 would keep video stalled // event after cache was full // Talk link: https://github.com/xbmc/xbmc/pull/23760 - if (cache.time > 8.0) + if (cache.time > m_messageQueueTimeSize) SetCaching(CACHESTATE_INIT); } else @@ -4677,7 +4686,7 @@ double CVideoPlayer::GetQueueTime() { int a = m_VideoPlayerAudio->GetLevel(); int v = m_processInfo->GetLevelVQ(); - return std::max(a, v) * 8000.0 / 100; + return std::max(a, v) * m_messageQueueTimeSize * 1000.0 / 100.0; } int CVideoPlayer::AddSubtitleFile(const std::string& filename, const std::string& subfilename) @@ -4945,7 +4954,7 @@ void CVideoPlayer::UpdatePlayState(double timeout) } else { - state.cache_level = std::min(1.0, queueTime / 8000.0); + state.cache_level = std::min(1.0, queueTime / (m_messageQueueTimeSize * 1000.0)); state.cache_offset = queueTime / state.timeMax; state.cache_time = queueTime / 1000.0; } diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.h b/xbmc/cores/VideoPlayer/VideoPlayer.h index 2dfd8f4be3153..1300b0fd4ee09 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.h +++ b/xbmc/cores/VideoPlayer/VideoPlayer.h @@ -591,4 +591,6 @@ class CVideoPlayer : public IPlayer, public CThread, public IVideoPlayer, bool m_UpdateStreamDetails; std::atomic m_displayLost; + + double m_messageQueueTimeSize{0.0}; }; diff --git a/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp b/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp index d7795f7311cd3..6195088ab6259 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp @@ -43,12 +43,16 @@ class CDVDMsgAudioCodecChange : public CDVDMsg CDVDStreamInfo m_hints; }; - -CVideoPlayerAudio::CVideoPlayerAudio(CDVDClock* pClock, CDVDMessageQueue& parent, CProcessInfo &processInfo) -: CThread("VideoPlayerAudio"), IDVDStreamPlayerAudio(processInfo) -, m_messageQueue("audio") -, m_messageParent(parent) -, m_audioSink(pClock) +CVideoPlayerAudio::CVideoPlayerAudio(CDVDClock* pClock, + CDVDMessageQueue& parent, + CProcessInfo& processInfo, + double messageQueueTimeSize) + : CThread("VideoPlayerAudio"), + IDVDStreamPlayerAudio(processInfo), + m_messageQueue("audio"), + m_messageParent(parent), + m_audioSink(pClock), + m_messageQueueTimeSize(messageQueueTimeSize) { m_pClock = pClock; m_audioClock = 0; @@ -61,9 +65,10 @@ CVideoPlayerAudio::CVideoPlayerAudio(CDVDClock* pClock, CDVDMessageQueue& parent m_prevskipped = false; m_maxspeedadjust = 0.0; - // 18 MB allows max bitrate of 18 Mbit/s (TrueHD max peak) during 8 seconds - m_messageQueue.SetMaxDataSize(18 * 1024 * 1024); - m_messageQueue.SetMaxTimeSize(8.0); + // allows max bitrate of 18 Mbit/s (TrueHD max peak) during m_messageQueueTimeSize seconds + m_messageQueue.SetMaxDataSize(18 * m_messageQueueTimeSize / 8 * 1024 * 1024); + m_messageQueue.SetMaxTimeSize(m_messageQueueTimeSize); + m_disconAdjustTimeMs = processInfo.GetMaxPassthroughOffSyncDuration(); } diff --git a/xbmc/cores/VideoPlayer/VideoPlayerAudio.h b/xbmc/cores/VideoPlayer/VideoPlayerAudio.h index f550cb8e63620..24c394c52c086 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerAudio.h +++ b/xbmc/cores/VideoPlayer/VideoPlayerAudio.h @@ -30,7 +30,10 @@ class CDVDAudioCodec; class CVideoPlayerAudio : public CThread, public IDVDStreamPlayerAudio { public: - CVideoPlayerAudio(CDVDClock* pClock, CDVDMessageQueue& parent, CProcessInfo &processInfo); + CVideoPlayerAudio(CDVDClock* pClock, + CDVDMessageQueue& parent, + CProcessInfo& processInfo, + double messageQueueTimeSize); ~CVideoPlayerAudio() override; bool OpenStream(CDVDStreamInfo hints) override; @@ -117,5 +120,6 @@ class CVideoPlayerAudio : public CThread, public IDVDStreamPlayerAudio bool m_displayReset = false; unsigned int m_disconAdjustTimeMs = 50; // maximum sync-off before adjusting int m_disconAdjustCounter = 0; + double m_messageQueueTimeSize{0.0}; }; diff --git a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp index c42f8f3dd7b34..651a635b3d3bf 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp @@ -43,17 +43,18 @@ class CDVDMsgVideoCodecChange : public CDVDMsg CDVDStreamInfo m_hints; }; - -CVideoPlayerVideo::CVideoPlayerVideo(CDVDClock* pClock - ,CDVDOverlayContainer* pOverlayContainer - ,CDVDMessageQueue& parent - ,CRenderManager& renderManager - ,CProcessInfo &processInfo) -: CThread("VideoPlayerVideo") -, IDVDStreamPlayerVideo(processInfo) -, m_messageQueue("video") -, m_messageParent(parent) -, m_renderManager(renderManager) +CVideoPlayerVideo::CVideoPlayerVideo(CDVDClock* pClock, + CDVDOverlayContainer* pOverlayContainer, + CDVDMessageQueue& parent, + CRenderManager& renderManager, + CProcessInfo& processInfo, + double messageQueueTimeSize) + : CThread("VideoPlayerVideo"), + IDVDStreamPlayerVideo(processInfo), + m_messageQueue("video"), + m_messageParent(parent), + m_renderManager(renderManager), + m_messageQueueTimeSize(messageQueueTimeSize) { m_pClock = pClock; m_pOverlayContainer = pOverlayContainer; @@ -67,9 +68,9 @@ CVideoPlayerVideo::CVideoPlayerVideo(CDVDClock* pClock m_iDroppedRequest = 0; m_fForcedAspectRatio = 0; - // 128 MB allows max bitrate of 128 Mbit/s (e.g. UHD Blu-Ray) during 8 seconds - m_messageQueue.SetMaxDataSize(128 * 1024 * 1024); - m_messageQueue.SetMaxTimeSize(8.0); + // allows max bitrate of 128 Mbit/s (e.g. UHD Blu-Ray) during m_messageQueueTimeSize seconds + m_messageQueue.SetMaxDataSize(128 * m_messageQueueTimeSize / 8 * 1024 * 1024); + m_messageQueue.SetMaxTimeSize(m_messageQueueTimeSize); m_iDroppedFrames = 0; m_fFrameRate = 25; diff --git a/xbmc/cores/VideoPlayer/VideoPlayerVideo.h b/xbmc/cores/VideoPlayer/VideoPlayerVideo.h index d0c83172f0748..f763334301f9d 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerVideo.h +++ b/xbmc/cores/VideoPlayer/VideoPlayerVideo.h @@ -45,11 +45,12 @@ class CDroppingStats class CVideoPlayerVideo : public CThread, public IDVDStreamPlayerVideo { public: - CVideoPlayerVideo(CDVDClock* pClock - ,CDVDOverlayContainer* pOverlayContainer - ,CDVDMessageQueue& parent - ,CRenderManager& renderManager, - CProcessInfo &processInfo); + CVideoPlayerVideo(CDVDClock* pClock, + CDVDOverlayContainer* pOverlayContainer, + CDVDMessageQueue& parent, + CRenderManager& renderManager, + CProcessInfo& processInfo, + double messageQueueTimeSize); ~CVideoPlayerVideo() override; bool OpenStream(CDVDStreamInfo hint) override; @@ -140,5 +141,7 @@ class CVideoPlayerVideo : public CThread, public IDVDStreamPlayerVideo CRenderManager& m_renderManager; VideoPicture m_picture; - EOutputState m_outputSate; + EOutputState m_outputSate{OUTPUT_NORMAL}; + + double m_messageQueueTimeSize{0.0}; }; From 1119f9a420eb92ce4e48a9e89c04ae9fefa53186 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sun, 16 Jun 2024 11:54:56 +0200 Subject: [PATCH 059/651] [VideoPlayer] add audio/video queue time to debug OSD --- xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp | 6 ++++-- xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp b/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp index 6195088ab6259..91220f92deada 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp @@ -201,9 +201,11 @@ void CVideoPlayerAudio::OnStartup() void CVideoPlayerAudio::UpdatePlayerInfo() { + const int level = m_messageQueue.GetLevel(); std::ostringstream s; - s << "aq:" << std::setw(2) << std::min(99,m_messageQueue.GetLevel()) << "%"; - s << ", Kb/s:" << std::fixed << std::setprecision(2) << m_audioStats.GetBitrate() / 1024.0; + s << "aq:" << std::setw(2) << std::min(99, level); + s << "% " << std::fixed << std::setprecision(3) << m_messageQueueTimeSize * level / 100.0; + s << "s, Kb/s:" << std::fixed << std::setprecision(2) << m_audioStats.GetBitrate() / 1024.0; // print a/v discontinuity adjustments counter when audio is not resampled (passthrough mode) if (m_synctype == SYNC_DISCON) diff --git a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp index 651a635b3d3bf..6588c070013cc 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp @@ -953,10 +953,13 @@ CVideoPlayerVideo::EOutputState CVideoPlayerVideo::OutputPicture(const VideoPict std::string CVideoPlayerVideo::GetPlayerInfo() { + const int level = m_processInfo.GetLevelVQ(); std::ostringstream s; - s << "vq:" << std::setw(2) << std::min(99, m_processInfo.GetLevelVQ()) << "%"; - s << ", Mb/s:" << std::fixed << std::setprecision(2) << (double)GetVideoBitrate() / (1024.0*1024.0); - s << ", fr:" << std::fixed << std::setprecision(3) << m_fFrameRate; + s << "vq:" << std::setw(2) << std::min(99, level); + s << "% " << std::fixed << std::setprecision(3) << m_messageQueueTimeSize * level / 100.0; + s << "s, Mb/s:" << std::fixed << std::setprecision(2) + << static_cast(GetVideoBitrate()) / (1024.0 * 1024.0); + s << ", fr:" << std::fixed << std::setprecision(3) << m_fFrameRate; s << ", drop:" << m_iDroppedFrames; s << ", skip:" << m_renderManager.GetSkippedFrames(); From 459a366434b39fbc72718a993d1bff2631e18ace Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Mon, 17 Jun 2024 10:47:25 +0100 Subject: [PATCH 060/651] [Depends] Bump smctemp to 0.3.2 --- tools/depends/target/smctemp/SMCTEMP-VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/depends/target/smctemp/SMCTEMP-VERSION b/tools/depends/target/smctemp/SMCTEMP-VERSION index 4eb38567cbbdd..9f9321151f776 100644 --- a/tools/depends/target/smctemp/SMCTEMP-VERSION +++ b/tools/depends/target/smctemp/SMCTEMP-VERSION @@ -1,5 +1,5 @@ LIBNAME=smctemp -VERSION=0.2.1 +VERSION=0.3.2 ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz -SHA512=8671836ed3f16122ffc84a1e91b463c8405526b1500fa9f5816a9f9eff1fd598c86958e894d2b8b25ae798da57b536766729b28dcc6b7c69fe2dc3c818f18290 +SHA512=3e2171057939de0c63e69aa01c826f24960b56c830cb9ecc890304464ef02c5d741cefb1fef3271ac5939d9771e5d940983b2900c3f2599d06cc249aaaaa2548 BYPRODUCT=libsmctemp.a From 13ba7adac93e3431ad3edc35c00eb6d8d993638e Mon Sep 17 00:00:00 2001 From: Rechi Date: Tue, 18 Jun 2024 07:27:28 -0700 Subject: [PATCH 061/651] [clang-tidy] modernize-make-shared --- .../DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.cpp | 10 +++++----- .../DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp | 9 ++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.cpp index 5766e1d2b11c0..c420db32ea893 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.cpp @@ -27,6 +27,7 @@ #include "utils/log.h" #include +#include #include #include @@ -208,7 +209,7 @@ bool CDVDAudioCodecAndroidMediaCodec::Open(CDVDStreamInfo &hints, CDVDCodecOptio mimeTypes = codec_info.getSupportedTypes(); if (std::find(mimeTypes.begin(), mimeTypes.end(), m_mime) != mimeTypes.end()) { - m_codec = std::shared_ptr(new CJNIMediaCodec(CJNIMediaCodec::createByCodecName(codecName))); + m_codec = std::make_shared(CJNIMediaCodec::createByCodecName(codecName)); if (xbmc_jnienv()->ExceptionCheck()) { xbmc_jnienv()->ExceptionDescribe(); @@ -288,16 +289,15 @@ bool CDVDAudioCodecAndroidMediaCodec::Open(CDVDStreamInfo &hints, CDVDCodecOptio { CLog::Log(LOGDEBUG, "CDVDAudioCodecAndroidMediaCodec::Open Prefer the Google raw decoder " "over the MediaTek one"); - m_codec = std::shared_ptr( - new CJNIMediaCodec(CJNIMediaCodec::createByCodecName("OMX.google.raw.decoder"))); + m_codec = std::make_shared( + CJNIMediaCodec::createByCodecName("OMX.google.raw.decoder")); } else { CLog::Log( LOGDEBUG, "CDVDAudioCodecAndroidMediaCodec::Open Use the raw decoder proposed by the platform"); - m_codec = std::shared_ptr( - new CJNIMediaCodec(CJNIMediaCodec::createDecoderByType(m_mime))); + m_codec = std::make_shared(CJNIMediaCodec::createDecoderByType(m_mime)); } if (xbmc_jnienv()->ExceptionCheck()) { diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp index 573f4acd18cdc..4844bf07343b5 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp @@ -840,8 +840,7 @@ bool CDVDVideoCodecAndroidMediaCodec::Open(CDVDStreamInfo &hints, CDVDCodecOptio { if (types[j] == m_mime) { - m_codec = std::shared_ptr( - new CJNIMediaCodec(CJNIMediaCodec::createByCodecName(m_codecname))); + m_codec = std::make_shared(CJNIMediaCodec::createByCodecName(m_codecname)); if (xbmc_jnienv()->ExceptionCheck()) { xbmc_jnienv()->ExceptionDescribe(); @@ -921,7 +920,7 @@ bool CDVDVideoCodecAndroidMediaCodec::Open(CDVDStreamInfo &hints, CDVDCodecOptio m_processInfo.SetVideoDeintMethod("hardware"); m_processInfo.SetVideoDAR(m_hints.aspect); - m_videoBufferPool = std::shared_ptr(new CMediaCodecVideoBufferPool(m_codec)); + m_videoBufferPool = std::make_shared(m_codec); UpdateFpsDuration(); @@ -1832,9 +1831,9 @@ void CDVDVideoCodecAndroidMediaCodec::InitSurfaceTexture(void) glBindTexture( GL_TEXTURE_EXTERNAL_OES, 0); m_textureId = texture_id; - m_surfaceTexture = std::shared_ptr(new CJNISurfaceTexture(m_textureId)); + m_surfaceTexture = std::make_shared(m_textureId); // hook the surfaceTexture OnFrameAvailable callback - m_frameAvailable = std::shared_ptr(new CDVDMediaCodecOnFrameAvailable(m_surfaceTexture)); + m_frameAvailable = std::make_shared(m_surfaceTexture); m_jnivideosurface = CJNISurface(*m_surfaceTexture); } else From f01f06e7bf658f0bbed2cc54c9b8f55d16d50fb9 Mon Sep 17 00:00:00 2001 From: Rechi Date: Tue, 18 Jun 2024 07:27:28 -0700 Subject: [PATCH 062/651] [clang-tidy] modernize-use-default-member-init --- xbmc/CueDocument.cpp | 7 ++----- xbmc/cores/DataCacheCore.cpp | 8 ++------ xbmc/cores/DataCacheCore.h | 2 +- xbmc/dialogs/GUIDialogNumeric.cpp | 3 +-- xbmc/dialogs/GUIDialogNumeric.h | 4 ++-- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/xbmc/CueDocument.cpp b/xbmc/CueDocument.cpp index abc5185e63927..2d09d07f905fa 100644 --- a/xbmc/CueDocument.cpp +++ b/xbmc/CueDocument.cpp @@ -75,10 +75,7 @@ class FileReader : public CueReader { public: - explicit FileReader(const std::string &strFile) : m_szBuffer{} - { - m_opened = m_file.Open(strFile); - } + explicit FileReader(const std::string& strFile) { m_opened = m_file.Open(strFile); } bool ReadLine(std::string &line) override { // Read the next line. @@ -106,7 +103,7 @@ class FileReader private: CFile m_file; bool m_opened; - char m_szBuffer[1024]; + char m_szBuffer[1024]{}; }; class BufferReader diff --git a/xbmc/cores/DataCacheCore.cpp b/xbmc/cores/DataCacheCore.cpp index 2a308b471dfd4..8eb961733b8c1 100644 --- a/xbmc/cores/DataCacheCore.cpp +++ b/xbmc/cores/DataCacheCore.cpp @@ -14,12 +14,8 @@ #include #include -CDataCacheCore::CDataCacheCore() : - m_playerVideoInfo {}, - m_playerAudioInfo {}, - m_contentInfo {}, - m_renderInfo {}, - m_stateInfo {} +CDataCacheCore::CDataCacheCore() + : m_playerVideoInfo{}, m_playerAudioInfo{}, m_contentInfo{}, m_stateInfo{} { } diff --git a/xbmc/cores/DataCacheCore.h b/xbmc/cores/DataCacheCore.h index 4fdf96668dd36..c09d7fcf10d08 100644 --- a/xbmc/cores/DataCacheCore.h +++ b/xbmc/cores/DataCacheCore.h @@ -306,7 +306,7 @@ class CDataCacheCore struct SRenderInfo { bool m_isClockSync; - } m_renderInfo; + } m_renderInfo{}; mutable CCriticalSection m_stateSection; bool m_playerStateChanged = false; diff --git a/xbmc/dialogs/GUIDialogNumeric.cpp b/xbmc/dialogs/GUIDialogNumeric.cpp index 802834fbc6b31..04d8c1d72c325 100644 --- a/xbmc/dialogs/GUIDialogNumeric.cpp +++ b/xbmc/dialogs/GUIDialogNumeric.cpp @@ -38,8 +38,7 @@ using namespace KODI::MESSAGING; using KODI::UTILITY::CDigest; -CGUIDialogNumeric::CGUIDialogNumeric(void) - : CGUIDialog(WINDOW_DIALOG_NUMERIC, "DialogNumeric.xml"), m_block{}, m_lastblock{} +CGUIDialogNumeric::CGUIDialogNumeric(void) : CGUIDialog(WINDOW_DIALOG_NUMERIC, "DialogNumeric.xml") { memset(&m_datetime, 0, sizeof(KODI::TIME::SystemTime)); m_loadType = KEEP_IN_MEMORY; diff --git a/xbmc/dialogs/GUIDialogNumeric.h b/xbmc/dialogs/GUIDialogNumeric.h index b8ad82bc6ef37..825a86f7b4711 100644 --- a/xbmc/dialogs/GUIDialogNumeric.h +++ b/xbmc/dialogs/GUIDialogNumeric.h @@ -75,8 +75,8 @@ class CGUIDialogNumeric : INPUT_MODE m_mode = INPUT_PASSWORD; // the current input mode KODI::TIME::SystemTime m_datetime; // for time and date modes uint8_t m_ip[4]; // for ip address mode - uint32_t m_block; // for time, date, and IP methods. - uint32_t m_lastblock; + uint32_t m_block{}; // for time, date, and IP methods. + uint32_t m_lastblock{}; bool m_dirty = false; // true if the current block has been changed. std::string m_number; ///< for number or password input }; From b8b9e852f02a3ee069d9d0a61aea37a527217515 Mon Sep 17 00:00:00 2001 From: Rechi Date: Tue, 18 Jun 2024 07:27:28 -0700 Subject: [PATCH 063/651] [clang-tidy] performance-inefficient-vector-operation --- xbmc/platform/android/peripherals/AndroidJoystickState.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/platform/android/peripherals/AndroidJoystickState.cpp b/xbmc/platform/android/peripherals/AndroidJoystickState.cpp index 5d898698fcc5b..18bbb557e03f8 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickState.cpp +++ b/xbmc/platform/android/peripherals/AndroidJoystickState.cpp @@ -236,6 +236,7 @@ bool CAndroidJoystickState::ProcessEvent(const AInputEvent* event) { // get all potential values std::vector values; + values.reserve(axis.ids.size()); for (const auto& axisId : axis.ids) values.push_back(AMotionEvent_getAxisValue(event, axisId, pointer)); From 5f6e565bbe02bf7d98121f271209b89f2ee40da9 Mon Sep 17 00:00:00 2001 From: Rechi Date: Tue, 18 Jun 2024 07:27:28 -0700 Subject: [PATCH 064/651] [clang-tidy] performance-unnecessary-value-param --- xbmc/utils/PlayerUtils.cpp | 2 +- xbmc/utils/PlayerUtils.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/utils/PlayerUtils.cpp b/xbmc/utils/PlayerUtils.cpp index de3b720300264..1b87966bb7e38 100644 --- a/xbmc/utils/PlayerUtils.cpp +++ b/xbmc/utils/PlayerUtils.cpp @@ -41,7 +41,7 @@ bool CPlayerUtils::IsItemPlayable(const CFileItem& itemIn) return false; } -void CPlayerUtils::AdvanceTempoStep(std::shared_ptr appPlayer, +void CPlayerUtils::AdvanceTempoStep(const std::shared_ptr& appPlayer, TempoStepChange change) { const auto step = 0.1f; diff --git a/xbmc/utils/PlayerUtils.h b/xbmc/utils/PlayerUtils.h index 3dbd8cb5089d9..83ea1ca551c81 100644 --- a/xbmc/utils/PlayerUtils.h +++ b/xbmc/utils/PlayerUtils.h @@ -23,6 +23,6 @@ class CPlayerUtils { public: static bool IsItemPlayable(const CFileItem& item); - static void AdvanceTempoStep(std::shared_ptr appPlayer, + static void AdvanceTempoStep(const std::shared_ptr& appPlayer, TempoStepChange change); }; From 49098f4859277b2f863c7b3001adef38fe88fcc0 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 14 May 2024 21:11:17 +0200 Subject: [PATCH 065/651] [PVR] Persist and restore date and time channels were added to the TV database. --- xbmc/pvr/PVRDatabase.cpp | 32 +++++++++++++++++++++------ xbmc/pvr/PVRDatabase.h | 2 +- xbmc/pvr/channels/PVRChannel.cpp | 21 ++++++++++++++++++ xbmc/pvr/channels/PVRChannel.h | 14 ++++++++++++ xbmc/pvr/channels/PVRChannelGroup.cpp | 4 ++++ 5 files changed, 65 insertions(+), 8 deletions(-) diff --git a/xbmc/pvr/PVRDatabase.cpp b/xbmc/pvr/PVRDatabase.cpp index 32469673e62b6..f6f7e6c5e7bda 100644 --- a/xbmc/pvr/PVRDatabase.cpp +++ b/xbmc/pvr/PVRDatabase.cpp @@ -156,7 +156,8 @@ void CPVRDatabase::CreateTables() "bHasArchive bool, " "iClientProviderUid integer, " "bIsUserSetHidden bool, " - "iLastWatchedGroupId integer" + "iLastWatchedGroupId integer, " + "sDateTimeAdded varchar(20)" ")"); CLog::LogFC(LOGDEBUG, LOGPVR, "Creating table 'channelgroups'"); @@ -368,6 +369,12 @@ void CPVRDatabase::UpdateTables(int iVersion) m_pDS->exec("ALTER TABLE channels ADD iLastWatchedGroupId integer"); m_pDS->exec("UPDATE channels SET iLastWatchedGroupId = -1"); } + + if (iVersion < 45) + { + m_pDS->exec("ALTER TABLE channels ADD sDateTimeAdded varchar(20)"); + m_pDS->exec("UPDATE channels SET sDateTimeAdded = ''"); + } } /********** Client methods **********/ @@ -592,6 +599,9 @@ int CPVRDatabase::Get(bool bRadio, channel->m_iClientProviderUid = m_pDS->fv("iClientProviderUid").get_asInt(); channel->m_bIsUserSetHidden = m_pDS->fv("bIsUserSetHidden").get_asBool(); channel->m_lastWatchedGroupId = m_pDS->fv("iLastWatchedGroupId").get_asInt(); + const std::string dateTimeAdded{m_pDS->fv("sDateTimeAdded").get_asString()}; + if (!dateTimeAdded.empty()) + channel->m_dateTimeAdded = CDateTime::FromDBDateTime(dateTimeAdded); channel->UpdateEncryptionName(); @@ -1024,6 +1034,10 @@ bool CPVRDatabase::Persist(CPVRChannel& channel, bool bCommit) return bReturn; } + std::string dateTimeAdded; + if (channel.DateTimeAdded().IsValid()) + dateTimeAdded = channel.DateTimeAdded().GetAsDBDateTime(); + std::unique_lock lock(m_critSection); // Note: Do not use channel.ChannelID value to check presence of channel in channels table. It might not yet be set correctly. @@ -1037,15 +1051,17 @@ bool CPVRDatabase::Persist(CPVRChannel& channel, bool bCommit) "INSERT INTO channels (" "iUniqueId, bIsRadio, bIsHidden, bIsUserSetIcon, bIsUserSetName, bIsLocked, " "sIconPath, sChannelName, bIsVirtual, bEPGEnabled, sEPGScraper, iLastWatched, iClientId, " - "idEpg, bHasArchive, iClientProviderUid, bIsUserSetHidden, iLastWatchedGroupId) " - "VALUES (%i, %i, %i, %i, %i, %i, '%s', '%s', %i, %i, '%s', %u, %i, %i, %i, %i, %i, %i)", + "idEpg, bHasArchive, iClientProviderUid, bIsUserSetHidden, iLastWatchedGroupId, " + "sDateTimeAdded) " + "VALUES (%i, %i, %i, %i, %i, %i, '%s', '%s', %i, %i, '%s', %u, %i, %i, %i, %i, %i, %i, " + "'%s')", channel.UniqueID(), (channel.IsRadio() ? 1 : 0), (channel.IsHidden() ? 1 : 0), (channel.IsUserSetIcon() ? 1 : 0), (channel.IsUserSetName() ? 1 : 0), (channel.IsLocked() ? 1 : 0), channel.IconPath().c_str(), channel.ChannelName().c_str(), 0, (channel.EPGEnabled() ? 1 : 0), channel.EPGScraper().c_str(), static_cast(channel.LastWatched()), channel.ClientID(), channel.EpgID(), channel.HasArchive(), channel.ClientProviderUid(), channel.IsUserSetHidden() ? 1 : 0, - channel.LastWatchedGroupId()); + channel.LastWatchedGroupId(), dateTimeAdded.c_str()); } else { @@ -1054,8 +1070,10 @@ bool CPVRDatabase::Persist(CPVRChannel& channel, bool bCommit) "REPLACE INTO channels (" "iUniqueId, bIsRadio, bIsHidden, bIsUserSetIcon, bIsUserSetName, bIsLocked, " "sIconPath, sChannelName, bIsVirtual, bEPGEnabled, sEPGScraper, iLastWatched, iClientId, " - "idChannel, idEpg, bHasArchive, iClientProviderUid, bIsUserSetHidden, iLastWatchedGroupId) " - "VALUES (%i, %i, %i, %i, %i, %i, '%s', '%s', %i, %i, '%s', %u, %i, %s, %i, %i, %i, %i, %i)", + "idChannel, idEpg, bHasArchive, iClientProviderUid, bIsUserSetHidden, iLastWatchedGroupId, " + "sDateTimeAdded) " + "VALUES (%i, %i, %i, %i, %i, %i, '%s', '%s', %i, %i, '%s', %u, %i, %s, %i, %i, %i, %i, %i, " + "'%s')", channel.UniqueID(), (channel.IsRadio() ? 1 : 0), (channel.IsHidden() ? 1 : 0), (channel.IsUserSetIcon() ? 1 : 0), (channel.IsUserSetName() ? 1 : 0), (channel.IsLocked() ? 1 : 0), channel.ClientIconPath().c_str(), @@ -1063,7 +1081,7 @@ bool CPVRDatabase::Persist(CPVRChannel& channel, bool bCommit) channel.EPGScraper().c_str(), static_cast(channel.LastWatched()), channel.ClientID(), strValue.c_str(), channel.EpgID(), channel.HasArchive(), channel.ClientProviderUid(), channel.IsUserSetHidden() ? 1 : 0, - channel.LastWatchedGroupId()); + channel.LastWatchedGroupId(), dateTimeAdded.c_str()); } if (QueueInsertQuery(strQuery)) diff --git a/xbmc/pvr/PVRDatabase.h b/xbmc/pvr/PVRDatabase.h index 7a8932e95def9..509360d5b6477 100644 --- a/xbmc/pvr/PVRDatabase.h +++ b/xbmc/pvr/PVRDatabase.h @@ -64,7 +64,7 @@ namespace PVR * @brief Get the minimal database version that is required to operate correctly. * @return The minimal database version. */ - int GetSchemaVersion() const override { return 44; } + int GetSchemaVersion() const override { return 45; } /*! * @brief Get the default sqlite database filename. diff --git a/xbmc/pvr/channels/PVRChannel.cpp b/xbmc/pvr/channels/PVRChannel.cpp index 783d89869c2db..f0c91557a77c8 100644 --- a/xbmc/pvr/channels/PVRChannel.cpp +++ b/xbmc/pvr/channels/PVRChannel.cpp @@ -111,6 +111,7 @@ void CPVRChannel::Serialize(CVariant& value) const value["uniqueid"] = m_iUniqueId; CDateTime lastPlayed(m_iLastWatched); value["lastplayed"] = lastPlayed.IsValid() ? lastPlayed.GetAsDBDate() : ""; + value["dateadded"] = m_dateTimeAdded.IsValid() ? m_dateTimeAdded.GetAsDBDate() : ""; std::shared_ptr epg = GetEPGNow(); if (epg) @@ -406,6 +407,20 @@ bool CPVRChannel::SetLastWatched(time_t lastWatched, int groupId) return false; } +bool CPVRChannel::SetDateTimeAdded(const CDateTime& dateTimeAdded) +{ + std::unique_lock lock(m_critSection); + + if (m_dateTimeAdded != dateTimeAdded) + { + m_dateTimeAdded = dateTimeAdded; + m_bChanged = true; + return true; + } + + return false; +} + /********** Client related channel methods **********/ bool CPVRChannel::SetClientID(int iClientId) @@ -737,6 +752,12 @@ time_t CPVRChannel::LastWatched() const return m_iLastWatched; } +CDateTime CPVRChannel::DateTimeAdded() const +{ + std::unique_lock lock(m_critSection); + return m_dateTimeAdded; +} + bool CPVRChannel::IsChanged() const { std::unique_lock lock(m_critSection); diff --git a/xbmc/pvr/channels/PVRChannel.h b/xbmc/pvr/channels/PVRChannel.h index 26cbc2d193a1e..0e2497f7c75dd 100644 --- a/xbmc/pvr/channels/PVRChannel.h +++ b/xbmc/pvr/channels/PVRChannel.h @@ -8,6 +8,7 @@ #pragma once +#include "XBDateTime.h" #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h" #include "pvr/PVRCachedImage.h" @@ -220,6 +221,18 @@ class CPVRChannel : public ISerializable, public ISortable */ bool SetLastWatched(time_t lastWatched, int groupId); + /*! + * @return the date and time this channel was added to the TV database. + */ + CDateTime DateTimeAdded() const; + + /*! + * @brief Set the date and time the channel was added to the TV database. + * @param dateTimeAdded The date and time. + * @return True if the something changed, false otherwise. + */ + bool SetDateTimeAdded(const CDateTime& dateTimeAdded); + /*! * @brief Check whether this channel has unpersisted data changes. * @return True if this channel has changes to persist, false otherwise @@ -551,6 +564,7 @@ class CPVRChannel : public ISerializable, public ISortable PVR_PROVIDER_INVALID_UID; /*!< the unique id for this provider from the client */ int m_lastWatchedGroupId{ -1}; /*!< the id of the channel group the channel was watched from the last time */ + CDateTime m_dateTimeAdded; /*!< the date and time the channel was added to the TV datebase */ //@} mutable CCriticalSection m_critSection; diff --git a/xbmc/pvr/channels/PVRChannelGroup.cpp b/xbmc/pvr/channels/PVRChannelGroup.cpp index 2bb1608a398e3..a501f1b4a8983 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.cpp +++ b/xbmc/pvr/channels/PVRChannelGroup.cpp @@ -609,6 +609,10 @@ bool CPVRChannelGroup::UpdateFromClient(const std::shared_ptrGroupID() == INVALID_GROUP_ID) groupMember->SetGroupID(GroupID()); + // Seeing this channel for the very first time. Remember when it was added. + if (IsChannelsOwner() && !channel->DateTimeAdded().IsValid()) + channel->SetDateTimeAdded(CDateTime::GetUTCDateTime()); + m_sortedMembers.emplace_back(groupMember); m_members.emplace(channel->StorageId(), groupMember); From 595c90f366a7fec7727fefe71b3fe4f18fac59b2 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 14 May 2024 21:26:35 +0200 Subject: [PATCH 066/651] [PVR] Add possibility to sort Channels and Guide window by date added. --- addons/resource.language.en_gb/resources/strings.po | 1 + xbmc/pvr/channels/PVRChannel.cpp | 2 ++ xbmc/pvr/windows/GUIViewStatePVR.cpp | 6 ++++++ 3 files changed, 9 insertions(+) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 4068a529d90a5..8c6731292eb3b 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -2706,6 +2706,7 @@ msgstr "" #: xbmc/dialogs/GUIDialogMediaFilter.cpp #: xbmc/playlists/SmartPlaylist.cpp +#: xbmc/pvr/windows/GUIViewStatePVR.cpp msgctxt "#570" msgid "Date added" msgstr "" diff --git a/xbmc/pvr/channels/PVRChannel.cpp b/xbmc/pvr/channels/PVRChannel.cpp index f0c91557a77c8..f7fd106a01bb2 100644 --- a/xbmc/pvr/channels/PVRChannel.cpp +++ b/xbmc/pvr/channels/PVRChannel.cpp @@ -676,6 +676,8 @@ void CPVRChannel::ToSortable(SortItem& sortable, Field field) const sortable[FieldLastPlayed] = lastWatched.IsValid() ? lastWatched.GetAsDBDateTime() : StringUtils::Empty; } + else if (field == FieldDateAdded) + sortable[FieldDateAdded] = m_dateTimeAdded.GetAsDBDateTime(); else if (field == FieldProvider) sortable[FieldProvider] = StringUtils::Format("{} {}", m_iClientId, m_iClientProviderUid); } diff --git a/xbmc/pvr/windows/GUIViewStatePVR.cpp b/xbmc/pvr/windows/GUIViewStatePVR.cpp index a5004d8ba95fb..874c98fd8952a 100644 --- a/xbmc/pvr/windows/GUIViewStatePVR.cpp +++ b/xbmc/pvr/windows/GUIViewStatePVR.cpp @@ -34,6 +34,9 @@ CGUIViewStateWindowPVRChannels::CGUIViewStateWindowPVRChannels(const int windowI AddSortMethod( SortByLastPlayed, 568, // "Last played" LABEL_MASKS("%L", "%p", "%L", "%p")); // Filename, LastPlayed | Foldername, LastPlayed + AddSortMethod(SortByDateAdded, 570, // "Date added" + LABEL_MASKS("%L", "%a", "%L", "%a"), // Filename, DateAdded | Foldername, DateAdded + SortAttributeNone, SortOrderDescending); AddSortMethod(SortByClientChannelOrder, 19315, // "Backend number" LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty AddSortMethod(SortByProvider, 19348, // "Provider" @@ -107,6 +110,9 @@ CGUIViewStateWindowPVRGuide::CGUIViewStateWindowPVRGuide(const int windowId, AddSortMethod( SortByLastPlayed, SortAttributeIgnoreLabel, 568, // "Last played" LABEL_MASKS("%L", "%p", "%L", "%p")); // Filename, LastPlayed | Foldername, LastPlayed + AddSortMethod(SortByDateAdded, 570, // "Date added" + LABEL_MASKS("%L", "%a", "%L", "%a"), // Filename, DateAdded | Foldername, DateAdded + SortAttributeNone, SortOrderDescending); AddSortMethod(SortByClientChannelOrder, 19315, // "Backend number" LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty AddSortMethod(SortByProvider, 19348, // "Provider" From 7cd6bac2d3c8ae2176ddc5ed7a9e47d50f93fbe4 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 14 May 2024 22:19:34 +0200 Subject: [PATCH 067/651] [PVR][guiinfo] Add support for ListItem.DateAdded for channels. --- xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index fb09aba999dc7..558f11f7ba0bc 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -870,6 +870,13 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, strValue = CServiceBroker::GetPVRManager().GetClient(channel->ClientID())->GetInstanceName(); return true; + case LISTITEM_DATE_ADDED: + if (channel->DateTimeAdded().IsValid()) + { + strValue = channel->DateTimeAdded().GetAsLocalizedDate(); + return true; + } + break; } } From af26ea29bb31e6cc3969602c7fd965d52c927b6f Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 14 May 2024 22:08:24 +0200 Subject: [PATCH 068/651] [PVR][Estuary] Add 'Recently added channels' Home screen widget. --- .../resources/strings.po | 8 ++++++- addons/skin.estuary/xml/Home.xml | 22 +++++++++++++++++++ .../listproviders/DirectoryProvider.cpp | 5 +++-- xbmc/pvr/filesystem/PVRGUIDirectory.cpp | 5 +++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 8c6731292eb3b..f167a856b73ba 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -3929,7 +3929,13 @@ msgctxt "#854" msgid "Any channel from any client" msgstr "" -#empty strings from id 855 to 996 +#. Label for Estuary 'Recently added channels' 'home screen widget +#: addons/skin.estuary/xml/Home.xml +msgctxt "#855" +msgid "Recently added channels" +msgstr "" + +#empty strings from id 856 to 996 #: xbmc/windows/GUIMediaWindow.cpp msgctxt "#997" diff --git a/addons/skin.estuary/xml/Home.xml b/addons/skin.estuary/xml/Home.xml index 3cd090d7a1719..0f2d3a86a8791 100644 --- a/addons/skin.estuary/xml/Home.xml +++ b/addons/skin.estuary/xml/Home.xml @@ -443,6 +443,17 @@ + + + + + + + + + + + @@ -515,6 +526,17 @@ + + + + + + + + + + + diff --git a/xbmc/guilib/listproviders/DirectoryProvider.cpp b/xbmc/guilib/listproviders/DirectoryProvider.cpp index 14319ed961418..c71855255ae49 100644 --- a/xbmc/guilib/listproviders/DirectoryProvider.cpp +++ b/xbmc/guilib/listproviders/DirectoryProvider.cpp @@ -311,9 +311,10 @@ void CDirectoryProvider::Announce(ANNOUNCEMENT::AnnouncementFlag flag, { if (message == "OnPlay" || message == "OnResume" || message == "OnStop") { - if (m_currentSort.sortBy == SortByNone || // not nice, but many directories that need to be refreshed on start/stop have no special sort order (e.g. in progress movies) + if (m_currentSort.sortBy == + SortByNone || // not nice, but many directories that need to be refreshed on start/stop have no special sort order (e.g. in progress movies) m_currentSort.sortBy == SortByLastPlayed || - m_currentSort.sortBy == SortByPlaycount || + m_currentSort.sortBy == SortByDateAdded || m_currentSort.sortBy == SortByPlaycount || m_currentSort.sortBy == SortByLastUsed) m_updateState = INVALIDATED; } diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp index cdb49b481850a..5bfa415dee8f7 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp @@ -591,6 +591,7 @@ bool CPVRGUIDirectory::GetChannelsDirectory(CFileItemList& results) const else if (path.IsChannelGroup()) { const bool playedOnly{(m_url.HasOption("view") && (m_url.GetOption("view") == "lastplayed"))}; + const bool dateAdded{(m_url.HasOption("view") && (m_url.GetOption("view") == "dateadded"))}; const bool showHiddenChannels{path.IsHiddenChannelGroup()}; const std::vector> groupMembers{ GetChannelGroupMembers(path)}; @@ -602,6 +603,10 @@ bool CPVRGUIDirectory::GetChannelsDirectory(CFileItemList& results) const if (playedOnly && !groupMember->Channel()->LastWatched()) continue; + if (dateAdded && (!groupMember->Channel()->DateTimeAdded().IsValid() || + groupMember->Channel()->LastWatched())) + continue; + results.Add(std::make_shared(groupMember)); } return true; From 207af95ba2e32e6ab0b6df4da65717d4573e5f75 Mon Sep 17 00:00:00 2001 From: Hitcher Date: Fri, 21 Jun 2024 11:57:22 +0100 Subject: [PATCH 069/651] Fix typo in strings.po Fix two labels that contain 'the the' in them. --- addons/resource.language.en_gb/resources/strings.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 4068a529d90a5..d009804d357ac 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -19900,7 +19900,7 @@ msgstr "" #. Description of setting with label #746 "Background Opacity" #: system/settings/settings.xml msgctxt "#36230" -msgid "Set the the subtitle background opacity." +msgid "Set the subtitle background opacity." msgstr "" #: system/settings/settings.xml @@ -20263,7 +20263,7 @@ msgstr "" #. Description of setting with label #752 "Opacity" #: system/settings/settings.xml msgctxt "#36295" -msgid "Set the the subtitle opacity." +msgid "Set the subtitle opacity." msgstr "" #: system/settings/settings.xml From 50ee755831ff0a421e230674b3b5bacde183ab9e Mon Sep 17 00:00:00 2001 From: CrystalP Date: Wed, 29 May 2024 21:19:33 -0400 Subject: [PATCH 070/651] [windows] Identify SDR WCG screens as non HDR-capable Win 11 22621 (22H2) supports WCG screens and also has some interop functions with Win32 to use the new WinRT API for the detection. Unify the Win11 desktop and Xbox paths, kept the older code for older desktop and UWP desktop. The older code can also be used as fallback in case the new method doesn't work. --- xbmc/HDRStatus.h | 1 + xbmc/platform/win10/CMakeLists.txt | 4 +- xbmc/platform/win32/CMakeLists.txt | 4 +- xbmc/platform/win32/WIN32Util.cpp | 75 +++++------ xbmc/platform/win32/WIN32Util.h | 3 + xbmc/platform/win32/WinRtUtil.cpp | 202 ++++++++++++++++++++++++++++ xbmc/platform/win32/WinRtUtil.h | 22 +++ xbmc/rendering/dx/DeviceResources.h | 1 + xbmc/rendering/dx/DirectXHelper.h | 16 +-- 9 files changed, 266 insertions(+), 62 deletions(-) create mode 100644 xbmc/platform/win32/WinRtUtil.cpp create mode 100644 xbmc/platform/win32/WinRtUtil.h diff --git a/xbmc/HDRStatus.h b/xbmc/HDRStatus.h index 8fe5aaec53126..72613e12cd7c0 100644 --- a/xbmc/HDRStatus.h +++ b/xbmc/HDRStatus.h @@ -10,6 +10,7 @@ enum class HDR_STATUS { + HDR_UNKNOWN = -2, HDR_TOGGLE_FAILED = -1, HDR_UNSUPPORTED = 0, HDR_OFF = 1, diff --git a/xbmc/platform/win10/CMakeLists.txt b/xbmc/platform/win10/CMakeLists.txt index 19bc19f0e5ced..ff6260e36ab14 100644 --- a/xbmc/platform/win10/CMakeLists.txt +++ b/xbmc/platform/win10/CMakeLists.txt @@ -11,6 +11,7 @@ set(SOURCES CPUInfoWin10.cpp ../win32/MemUtils.cpp ../win32/pch.cpp ../win32/WIN32Util.cpp + ../win32/WinRtUtil.cpp ../win32/XTimeUtils.cpp) set(HEADERS AsyncHelpers.h @@ -29,6 +30,7 @@ set(HEADERS AsyncHelpers.h ../win32/PlatformDefs.h ../win32/resource.h ../win32/unistd.h - ../win32/WIN32Util.h) + ../win32/WIN32Util.h + ../win32/WinRtUtil.h) core_add_library(platform_win10) diff --git a/xbmc/platform/win32/CMakeLists.txt b/xbmc/platform/win32/CMakeLists.txt index 28faedb9c0ebf..ab54d6c8b2037 100644 --- a/xbmc/platform/win32/CMakeLists.txt +++ b/xbmc/platform/win32/CMakeLists.txt @@ -10,6 +10,7 @@ set(SOURCES CharsetConverter.cpp PlatformWin32.cpp WIN32Util.cpp WindowHelper.cpp + WinRtUtil.cpp XTimeUtils.cpp) set(HEADERS CharsetConverter.h @@ -27,6 +28,7 @@ set(HEADERS CharsetConverter.h resource.h unistd.h WIN32Util.h - WindowHelper.h) + WindowHelper.h + WinRtUtil.h) core_add_library(platform_win32) diff --git a/xbmc/platform/win32/WIN32Util.cpp b/xbmc/platform/win32/WIN32Util.cpp index 29658a42a9b3e..46726af482403 100644 --- a/xbmc/platform/win32/WIN32Util.cpp +++ b/xbmc/platform/win32/WIN32Util.cpp @@ -11,6 +11,7 @@ #include "CompileInfo.h" #include "ServiceBroker.h" #include "Util.h" +#include "WinRtUtil.h" #include "WindowHelper.h" #include "guilib/LocalizeStrings.h" #include "my_ntddscsi.h" @@ -1347,53 +1348,13 @@ HDR_STATUS CWIN32Util::ToggleWindowsHDR(DXGI_MODE_DESC& modeDesc) return status; } -HDR_STATUS CWIN32Util::GetWindowsHDRStatus() +HDR_STATUS CWIN32Util::GetWindowsHDRStatusWin32() { + HDR_STATUS status = HDR_STATUS::HDR_UNKNOWN; + +#ifdef TARGET_WINDOWS_DESKTOP bool advancedColorSupported = false; bool advancedColorEnabled = false; - HDR_STATUS status = HDR_STATUS::HDR_UNSUPPORTED; - -#ifdef TARGET_WINDOWS_STORE - auto displayInformation = DisplayInformation::GetForCurrentView(); - - if (displayInformation) - { - auto advancedColorInfo = displayInformation.GetAdvancedColorInfo(); - - if (advancedColorInfo) - { - if (advancedColorInfo.CurrentAdvancedColorKind() == AdvancedColorKind::HighDynamicRange) - { - advancedColorSupported = true; - advancedColorEnabled = true; - } - } - } - // Try to find out if the display supports HDR even if Windows HDR switch is OFF - if (!advancedColorEnabled) - { - auto displayManager = DisplayManager::Create(DisplayManagerOptions::None); - - if (displayManager) - { - auto targets = displayManager.GetCurrentTargets(); - - for (const auto& target : targets) - { - if (target.IsConnected()) - { - auto displayMonitor = target.TryGetMonitor(); - if (displayMonitor.MaxLuminanceInNits() >= 400.0f) - { - advancedColorSupported = true; - break; - } - } - } - displayManager.Close(); - } - } -#else uint32_t pathCount = 0; uint32_t modeCount = 0; @@ -1451,7 +1412,6 @@ HDR_STATUS CWIN32Util::GetWindowsHDRStatus() } } } -#endif if (!advancedColorSupported) { @@ -1466,10 +1426,35 @@ HDR_STATUS CWIN32Util::GetWindowsHDRStatus() CLog::LogF(LOGDEBUG, "Display is HDR capable and current HDR status is {}", advancedColorEnabled ? "ON" : "OFF"); } +#endif // TARGET_WINDOWS_DESKTOP return status; } +HDR_STATUS CWIN32Util::GetWindowsHDRStatus() +{ + HDR_STATUS status = HDR_STATUS::HDR_UNKNOWN; + +#ifdef TARGET_WINDOWS_STORE + if (CSysInfo::GetWindowsDeviceFamily() == CSysInfo::Xbox && + HDR_STATUS::HDR_UNKNOWN != (status = CWinRtUtil::GetWindowsHDRStatus())) + return status; + + // Not Xbox or detection failure: fallback to traditional UWP method + status = CWinRtUtil::GetWindowsHDRStatusUWP(); + return status == HDR_STATUS::HDR_UNKNOWN ? HDR_STATUS::HDR_UNSUPPORTED : status; +#else + // WinRT detection available for Win 11 22621 and above. + if (CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin11_22H2) && + HDR_STATUS::HDR_UNKNOWN != (status = CWinRtUtil::GetWindowsHDRStatus())) + return status; + + // Not Win11 or detection failure: fallback to traditional Win32 method + status = GetWindowsHDRStatusWin32(); + return status == HDR_STATUS::HDR_UNKNOWN ? HDR_STATUS::HDR_UNSUPPORTED : status; +#endif +} + /*! * \brief Retrieve from the system the max luminance of SDR content in HDR. * diff --git a/xbmc/platform/win32/WIN32Util.h b/xbmc/platform/win32/WIN32Util.h index 054ca262bdd7a..33033fb431829 100644 --- a/xbmc/platform/win32/WIN32Util.h +++ b/xbmc/platform/win32/WIN32Util.h @@ -105,4 +105,7 @@ class CWIN32Util * Undefined results when the strings are not formatted properly. */ static bool IsDriverVersionAtLeast(const std::string& version1, const std::string& version2); + +private: + static HDR_STATUS GetWindowsHDRStatusWin32(); }; diff --git a/xbmc/platform/win32/WinRtUtil.cpp b/xbmc/platform/win32/WinRtUtil.cpp new file mode 100644 index 0000000000000..ce239a722ecab --- /dev/null +++ b/xbmc/platform/win32/WinRtUtil.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2024 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 "WinRtUtil.h" + +#include "rendering/dx/DirectXHelper.h" +#include "utils/SystemInfo.h" +#include "utils/log.h" + +#ifdef TARGET_WINDOWS_DESKTOP +#include + +#else +#include + +using namespace winrt::Windows::Devices::Display::Core; +#endif + +#include + +using namespace winrt::Windows::Graphics::Display; + +#ifdef TARGET_WINDOWS_DESKTOP +extern HWND g_hWnd; +#endif + +static constexpr std::string_view AdvancedColorKindToString(AdvancedColorKind kind) +{ + switch (kind) + { + case AdvancedColorKind::StandardDynamicRange: + return "SDR"; + case AdvancedColorKind::WideColorGamut: + return "WCG"; + case AdvancedColorKind::HighDynamicRange: + return "HDR"; + default: + return "unknown"; + } +} + +static void LogAdvancedColorInfo(AdvancedColorInfo colorInfo) +{ + std::string availableKinds; + static constexpr std::array kinds{AdvancedColorKind::StandardDynamicRange, + AdvancedColorKind::WideColorGamut, + AdvancedColorKind::HighDynamicRange}; + + for (const AdvancedColorKind& kind : kinds) + { + if (colorInfo.IsAdvancedColorKindAvailable(kind)) + availableKinds.append(AdvancedColorKindToString(kind)).append(" "); + } + + CLog::LogF(LOGDEBUG, "Current advanced color kind: {}, supported kinds: {}", + AdvancedColorKindToString(colorInfo.CurrentAdvancedColorKind()), availableKinds); +} + +HDR_STATUS CWinRtUtil::GetWindowsHDRStatus() +{ + DisplayInformation displayInformation{nullptr}; + +#ifdef TARGET_WINDOWS_STORE + displayInformation = DisplayInformation::GetForCurrentView(); + + if (!displayInformation) + { + CLog::LogF(LOGERROR, "unable to retrieve DisplayInformation."); + return HDR_STATUS::HDR_UNKNOWN; + } +#else + auto factory{ + winrt::try_get_activation_factory()}; + + if (!factory) + { + CLog::LogF(LOGERROR, + "unable to activate IDisplayInformationStaticsInterop. Windows version too low?"); + return HDR_STATUS::HDR_UNKNOWN; + } + + HMONITOR hm{}; + if (g_hWnd) + { + RECT rect{}; + + if (FALSE == GetWindowRect(g_hWnd, &rect)) + { + CLog::LogF(LOGERROR, "unable to retrieve window rect, error {}", + DX::GetErrorDescription(GetLastError())); + return HDR_STATUS::HDR_UNKNOWN; + } + + hm = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL); + + if (hm == NULL) + { + CLog::LogF(LOGERROR, "unable to retrieve monitor intersecting with application window"); + return HDR_STATUS::HDR_UNKNOWN; + } + } + else + { + POINT point{}; + + hm = MonitorFromPoint(point, MONITOR_DEFAULTTONULL); + + if (hm == NULL) + { + CLog::LogF(LOGERROR, "unable to retrieve primary monitor"); + return HDR_STATUS::HDR_UNKNOWN; + } + } + + HRESULT hr = factory->GetForMonitor(hm, winrt::guid_of(), + winrt::put_abi(displayInformation)); + if (FAILED(hr)) + { + CLog::LogF(LOGERROR, "unable to retrieve DisplayInformation for window, error {}", + DX::GetErrorDescription(hr)); + return HDR_STATUS::HDR_UNKNOWN; + } +#endif + + AdvancedColorInfo colorInfo{displayInformation.GetAdvancedColorInfo()}; + + if (!colorInfo) + { + CLog::LogF(LOGERROR, "unable to retrieve advanced color info"); + return HDR_STATUS::HDR_UNKNOWN; + } + + LogAdvancedColorInfo(colorInfo); + + if (colorInfo.CurrentAdvancedColorKind() == AdvancedColorKind::HighDynamicRange) + return HDR_STATUS::HDR_ON; + + // IsAdvancedColorKindAvailable works for desktop Windows 11 22H2+ and Xbox + if (colorInfo.IsAdvancedColorKindAvailable(AdvancedColorKind::HighDynamicRange)) + return HDR_STATUS::HDR_OFF; + else + return HDR_STATUS::HDR_UNSUPPORTED; +} + +HDR_STATUS CWinRtUtil::GetWindowsHDRStatusUWP() +{ +#ifdef TARGET_WINDOWS_STORE + // Legacy detection, useful for UWP desktop at this point + DisplayInformation displayInformation = DisplayInformation::GetForCurrentView(); + + if (!displayInformation) + { + CLog::LogF(LOGERROR, "unable to retrieve DisplayInformation."); + return HDR_STATUS::HDR_UNKNOWN; + } + + AdvancedColorInfo colorInfo{displayInformation.GetAdvancedColorInfo()}; + + if (!colorInfo) + { + CLog::LogF(LOGERROR, "unable to retrieve advanced color info"); + return HDR_STATUS::HDR_UNKNOWN; + } + + if (colorInfo.CurrentAdvancedColorKind() == AdvancedColorKind::HighDynamicRange) + return HDR_STATUS::HDR_ON; + + // Detects any hdr-capable screen connected to the system, not necessarily the one that contains + // the Kodi window + bool hdrSupported{false}; + auto displayManager = DisplayManager::Create(DisplayManagerOptions::None); + + if (displayManager) + { + auto targets = displayManager.GetCurrentTargets(); + + for (const auto& target : targets) + { + if (target.IsConnected()) + { + auto displayMonitor = target.TryGetMonitor(); + if (displayMonitor.MaxLuminanceInNits() >= 400.0f) + { + hdrSupported = true; + break; + } + } + } + displayManager.Close(); + } + + if (hdrSupported) + return HDR_STATUS::HDR_OFF; +#endif // TARGET_WINDOWS_STORE + + return HDR_STATUS::HDR_UNSUPPORTED; +} diff --git a/xbmc/platform/win32/WinRtUtil.h b/xbmc/platform/win32/WinRtUtil.h new file mode 100644 index 0000000000000..7d58fe7dc151d --- /dev/null +++ b/xbmc/platform/win32/WinRtUtil.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 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 "HDRStatus.h" + +class CWinRtUtil +{ +private: + CWinRtUtil() = default; + ~CWinRtUtil() = default; + +public: + static HDR_STATUS GetWindowsHDRStatus(); + static HDR_STATUS GetWindowsHDRStatusUWP(); +}; diff --git a/xbmc/rendering/dx/DeviceResources.h b/xbmc/rendering/dx/DeviceResources.h index b0ed0f48829b7..a512e5a0bb4d5 100644 --- a/xbmc/rendering/dx/DeviceResources.h +++ b/xbmc/rendering/dx/DeviceResources.h @@ -17,6 +17,7 @@ #include #include +#include #include #include diff --git a/xbmc/rendering/dx/DirectXHelper.h b/xbmc/rendering/dx/DirectXHelper.h index 779b8751d5713..368bfcbad9e21 100644 --- a/xbmc/rendering/dx/DirectXHelper.h +++ b/xbmc/rendering/dx/DirectXHelper.h @@ -202,18 +202,4 @@ namespace DX const std::string DXGIColorSpaceTypeToString(DXGI_COLOR_SPACE_TYPE type); const std::string D3D11VideoProcessorFormatSupportToString( D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT value); -} - -#ifdef TARGET_WINDOWS_DESKTOP -namespace winrt -{ - namespace Windows - { - namespace Foundation - { - typedef DX::SizeGen Size; - typedef DX::SizeGen SizeInt; - } - } -} -#endif + } // namespace DX From bfe89eebfa8c53316dac8f24f14430f28f965197 Mon Sep 17 00:00:00 2001 From: CrystalP Date: Thu, 20 Jun 2024 21:23:44 -0400 Subject: [PATCH 071/651] [Windows] Update build requirements with Windows 11 SDK 10.0.22621.0 --- cmake/scripts/windows/ArchSetup.cmake | 2 +- cmake/scripts/windowsstore/ArchSetup.cmake | 2 +- docs/README.Windows.md | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cmake/scripts/windows/ArchSetup.cmake b/cmake/scripts/windows/ArchSetup.cmake index 1744ae30b02e5..7c00d73103fe4 100644 --- a/cmake/scripts/windows/ArchSetup.cmake +++ b/cmake/scripts/windows/ArchSetup.cmake @@ -1,5 +1,5 @@ # Minimum SDK version we support -set(VS_MINIMUM_SDK_VERSION 10.0.18362.0) +set(VS_MINIMUM_SDK_VERSION 10.0.22621.0) if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS VS_MINIMUM_SDK_VERSION) message(FATAL_ERROR "Detected Windows SDK version is ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}.\n" diff --git a/cmake/scripts/windowsstore/ArchSetup.cmake b/cmake/scripts/windowsstore/ArchSetup.cmake index ff18e4a6d4ec4..473ddcda06b2d 100644 --- a/cmake/scripts/windowsstore/ArchSetup.cmake +++ b/cmake/scripts/windowsstore/ArchSetup.cmake @@ -1,5 +1,5 @@ # Minimum SDK version we support -set(VS_MINIMUM_SDK_VERSION 10.0.18362.0) +set(VS_MINIMUM_SDK_VERSION 10.0.22621.0) if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS VS_MINIMUM_SDK_VERSION) message(FATAL_ERROR "Detected Windows SDK version is ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}.\n" diff --git a/docs/README.Windows.md b/docs/README.Windows.md index 44ab5ee352c53..60ece12300418 100644 --- a/docs/README.Windows.md +++ b/docs/README.Windows.md @@ -1,7 +1,7 @@ ![Kodi Logo](resources/banner_slim.png) # Windows build guide -This guide has been tested with Windows 10 Pro x64, version 21H2, build 19044.1415. Please read it in full before you proceed to familiarize yourself with the build procedure. +This guide has been tested with Windows 10 Pro x64, version 22H2, build 19045.4529. Please read it in full before you proceed to familiarize yourself with the build procedure. ## Table of Contents 1. **[Document conventions](#1-document-conventions)** @@ -83,6 +83,7 @@ Default options are fine. Start the Visual Studio installer and click **Workloads** select * Under **Desktop & Mobile** section select * `Desktop development with C++` + * Select the optional element Windows 11 SDK (10.0.22621.0) * `Universal Windows Platform development` (if compiling for UWP or UWP-ARM) Click in **Individual components** select From 5d9513c2e38af36dcee1dfa941c0c8ff02edfad4 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Fri, 21 Jun 2024 18:14:14 +0200 Subject: [PATCH 072/651] strings.po: fix duplicated strings id's --- .../resources/strings.po | 58 ++++++++++--------- system/settings/settings.xml | 8 +-- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 4068a529d90a5..3e959180da3b2 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -7365,31 +7365,6 @@ msgctxt "#13435" msgid "Enable HQ scalers for scaling above" msgstr "" -#: system/settings/settings.xml -msgctxt "#39198" -msgid "HQ upscaler intermediate format" -msgstr "" - -#: system/settings/settings.xml -msgctxt "#39199" -msgid "Sets the precision of the intermediate scaling buffer used in the GPU rendering path. 16 bit precision likely demands more performance. If the hardware is unable to support the selected format, Kodi will fall back to the next best format." -msgstr "" - -#: system/settings/settings.xml -msgctxt "#39200" -msgid "8 bit per channel" -msgstr "" - -#: system/settings/settings.xml -msgctxt "#39201" -msgid "10 bit per channel" -msgstr "" - -#: system/settings/settings.xml -msgctxt "#39202" -msgid "16 bit per channel" -msgstr "" - #: system/settings/settings.xml msgctxt "#13436" msgid "Adjust display HDR mode" @@ -7578,7 +7553,36 @@ msgctxt "#13471" msgid "Enable this option to use hardware acceleration for the AVC codec. If disabled the CPU will be used instead." msgstr "" -#empty strings from id 13468 to 13504 +#: system/settings/settings.xml +msgctxt "#13472" +msgid "HQ upscaler intermediate format" +msgstr "" + +#. Description of setting with label #13472 "HQ upscaler intermediate format" +#: system/settings/settings.xml +msgctxt "#13473" +msgid "Sets the precision of the intermediate scaling buffer used in the GPU rendering path. 16 bit precision likely demands more performance. If the hardware is unable to support the selected format, Kodi will fall back to the next best format." +msgstr "" + +#. Value of setting with label #13472 "HQ upscaler intermediate format" +#: system/settings/settings.xml +msgctxt "#13474" +msgid "8 bit per channel" +msgstr "" + +#. Value of setting with label #13472 "HQ upscaler intermediate format" +#: system/settings/settings.xml +msgctxt "#13475" +msgid "10 bit per channel" +msgstr "" + +#. Value of setting with label #13472 "HQ upscaler intermediate format" +#: system/settings/settings.xml +msgctxt "#13476" +msgid "16 bit per channel" +msgstr "" + +#empty strings from id 13477 to 13504 #: system/settings/settings.xml msgctxt "#13505" @@ -23824,6 +23828,8 @@ msgctxt "#39201" msgid "HDR10+" msgstr "" +#empty strings from id 39202 to 39999 + # 40000 to 40800 are reserved for Video Versions feature #. Generic video versions label (plural) diff --git a/system/settings/settings.xml b/system/settings/settings.xml index 0f559176bea01..86841efbed0fd 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -123,15 +123,15 @@ 14047 - + HAS_GL 2 10 - - - + + + From 02313e41c5e5478847d996db6dbbfe71ff61d2eb Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 10 Jun 2024 08:27:10 +0200 Subject: [PATCH 073/651] [PVR] Cleanup: Remove channels/PVRChannelGroupFromUser.cpp --- xbmc/pvr/channels/CMakeLists.txt | 1 - xbmc/pvr/channels/PVRChannelGroupFromUser.cpp | 24 ------------------- xbmc/pvr/channels/PVRChannelGroupFromUser.h | 10 ++++++-- 3 files changed, 8 insertions(+), 27 deletions(-) delete mode 100644 xbmc/pvr/channels/PVRChannelGroupFromUser.cpp diff --git a/xbmc/pvr/channels/CMakeLists.txt b/xbmc/pvr/channels/CMakeLists.txt index f52ee27a02ab5..25689e1081536 100644 --- a/xbmc/pvr/channels/CMakeLists.txt +++ b/xbmc/pvr/channels/CMakeLists.txt @@ -2,7 +2,6 @@ set(SOURCES PVRChannel.cpp PVRChannelGroup.cpp PVRChannelGroupAllChannels.cpp PVRChannelGroupFromClient.cpp - PVRChannelGroupFromUser.cpp PVRChannelGroupMember.cpp PVRChannelGroupSettings.cpp PVRChannelGroups.cpp diff --git a/xbmc/pvr/channels/PVRChannelGroupFromUser.cpp b/xbmc/pvr/channels/PVRChannelGroupFromUser.cpp deleted file mode 100644 index 253f24dec777c..0000000000000 --- a/xbmc/pvr/channels/PVRChannelGroupFromUser.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2012-2023 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 "PVRChannelGroupFromUser.h" - -using namespace PVR; - -CPVRChannelGroupFromUser::CPVRChannelGroupFromUser( - const CPVRChannelsPath& path, const std::shared_ptr& allChannelsGroup) - : CPVRChannelGroup(path, allChannelsGroup) -{ -} - -bool CPVRChannelGroupFromUser::UpdateFromClients( - const std::vector>& clients) -{ - // Nothing to update from any client, because there is none for local groups. - return true; -} diff --git a/xbmc/pvr/channels/PVRChannelGroupFromUser.h b/xbmc/pvr/channels/PVRChannelGroupFromUser.h index b89946b3e72c6..baf1f1cf8176b 100644 --- a/xbmc/pvr/channels/PVRChannelGroupFromUser.h +++ b/xbmc/pvr/channels/PVRChannelGroupFromUser.h @@ -22,7 +22,10 @@ class CPVRChannelGroupFromUser : public CPVRChannelGroup * @param allChannelsGroup The channel group containing all TV or radio channels. */ CPVRChannelGroupFromUser(const CPVRChannelsPath& path, - const std::shared_ptr& allChannelsGroup); + const std::shared_ptr& allChannelsGroup) + : CPVRChannelGroup(path, allChannelsGroup) + { + } /*! * @brief Check whether this group could be deleted by the user. @@ -53,7 +56,10 @@ class CPVRChannelGroupFromUser : public CPVRChannelGroup * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. * @return True on success, false otherwise. */ - bool UpdateFromClients(const std::vector>& clients) override; + bool UpdateFromClients(const std::vector>& clients) override + { + return true; // Nothing to update from any client for locally managed groups. + } private: /*! From dbfe0ea0828883e880ca96a9cd2f3fddbede55a2 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 18 Jun 2024 17:59:45 +0200 Subject: [PATCH 074/651] [PVR] Cleanup: Derive CPVRChannelGroupAllChannels from CPVRChannelGroup. --- .../channels/PVRChannelGroupAllChannels.cpp | 4 ++-- .../pvr/channels/PVRChannelGroupAllChannels.h | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/xbmc/pvr/channels/PVRChannelGroupAllChannels.cpp b/xbmc/pvr/channels/PVRChannelGroupAllChannels.cpp index 700500c8227c4..fceaf3f7dc242 100644 --- a/xbmc/pvr/channels/PVRChannelGroupAllChannels.cpp +++ b/xbmc/pvr/channels/PVRChannelGroupAllChannels.cpp @@ -29,13 +29,13 @@ using namespace PVR; CPVRChannelGroupAllChannels::CPVRChannelGroupAllChannels(bool bRadio) - : CPVRChannelGroupFromUser( + : CPVRChannelGroup( CPVRChannelsPath(bRadio, g_localizeStrings.Get(19287), PVR_GROUP_CLIENT_ID_LOCAL), nullptr) { } CPVRChannelGroupAllChannels::CPVRChannelGroupAllChannels(const CPVRChannelsPath& path) - : CPVRChannelGroupFromUser(path, nullptr) + : CPVRChannelGroup(path, nullptr) { } diff --git a/xbmc/pvr/channels/PVRChannelGroupAllChannels.h b/xbmc/pvr/channels/PVRChannelGroupAllChannels.h index 98d692185af4f..f96eefc9ba799 100644 --- a/xbmc/pvr/channels/PVRChannelGroupAllChannels.h +++ b/xbmc/pvr/channels/PVRChannelGroupAllChannels.h @@ -8,17 +8,12 @@ #pragma once -#include "pvr/channels/PVRChannelGroupFromUser.h" - -#include -#include +#include "pvr/channels/PVRChannelGroup.h" namespace PVR { -class CPVRChannel; -class CPVRChannelNumber; -class CPVRChannelGroupAllChannels : public CPVRChannelGroupFromUser +class CPVRChannelGroupAllChannels : public CPVRChannelGroup { public: CPVRChannelGroupAllChannels() = delete; @@ -64,6 +59,18 @@ class CPVRChannelGroupAllChannels : public CPVRChannelGroupFromUser */ bool SupportsDelete() const override { return false; } + /*! + * @brief Check whether members could be added to this group by the user. + * @return True if members could be added, false otherwise. + */ + bool SupportsMemberAdd() const override { return true; } + + /*! + * @brief Check whether members could be removed from this group by the user. + * @return True if members could be removed, false otherwise. + */ + bool SupportsMemberRemove() const override { return true; } + /*! * @brief Check whether this group is owner of the channel instances it contains. * @return True if owner, false otherwise. From b05a39f2bb763c98595b5ebd375da2f875ba2080 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 18 Jun 2024 19:30:34 +0200 Subject: [PATCH 075/651] [PVR] Channel groups: Introduce group origin USER, SYSTEM and CLIENT, adapt PVR_GROUP_TYPE_* constants accordingly. --- xbmc/pvr/channels/PVRChannelGroup.h | 18 +++++++++++++++--- xbmc/pvr/channels/PVRChannelGroupAllChannels.h | 8 +++++++- xbmc/pvr/channels/PVRChannelGroupFromClient.h | 8 +++++++- xbmc/pvr/channels/PVRChannelGroupFromUser.h | 8 +++++++- xbmc/pvr/channels/PVRChannelGroups.cpp | 12 ++++++------ 5 files changed, 42 insertions(+), 12 deletions(-) diff --git a/xbmc/pvr/channels/PVRChannelGroup.h b/xbmc/pvr/channels/PVRChannelGroup.h index 8fe5b63b60d0b..ab208e0a338d3 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.h +++ b/xbmc/pvr/channels/PVRChannelGroup.h @@ -24,9 +24,9 @@ struct PVR_CHANNEL_GROUP; namespace PVR { -static constexpr int PVR_GROUP_TYPE_REMOTE = 0; -static constexpr int PVR_GROUP_TYPE_ALL_CHANNELS = 1; -static constexpr int PVR_GROUP_TYPE_LOCAL = 2; +static constexpr int PVR_GROUP_TYPE_CLIENT = 0; +static constexpr int PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS = 1; +static constexpr int PVR_GROUP_TYPE_USER = 2; static constexpr int PVR_GROUP_CLIENT_ID_UNKNOWN = -2; static constexpr int PVR_GROUP_CLIENT_ID_LOCAL = -1; @@ -459,6 +459,18 @@ class CPVRChannelGroup : public IChannelGroupSettingsCallback */ void UpdateClientPriorities(); + enum class Origin + { + USER, // created and managed by the user + SYSTEM, // created and managed by Kodi + CLIENT, // created and managed by PVR client add-on + }; + /*! + * @brief Get the group's origin. + * @return The origin. + */ + virtual Origin GetOrigin() const = 0; + /*! * @brief Check whether this group could be deleted by the user. * @return True if the group could be deleted, false otherwise. diff --git a/xbmc/pvr/channels/PVRChannelGroupAllChannels.h b/xbmc/pvr/channels/PVRChannelGroupAllChannels.h index f96eefc9ba799..d91fdd27bfbbc 100644 --- a/xbmc/pvr/channels/PVRChannelGroupAllChannels.h +++ b/xbmc/pvr/channels/PVRChannelGroupAllChannels.h @@ -53,6 +53,12 @@ class CPVRChannelGroupAllChannels : public CPVRChannelGroup */ void CheckGroupName(); + /*! + * @brief Get the group's origin. + * @return The origin. + */ + Origin GetOrigin() const override { return Origin::SYSTEM; } + /*! * @brief Check whether this group could be deleted by the user. * @return True if the group could be deleted, false otherwise. @@ -97,6 +103,6 @@ class CPVRChannelGroupAllChannels : public CPVRChannelGroup /*! * @brief Return the type of this group. */ - int GroupType() const override { return PVR_GROUP_TYPE_ALL_CHANNELS; } + int GroupType() const override { return PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS; } }; } // namespace PVR diff --git a/xbmc/pvr/channels/PVRChannelGroupFromClient.h b/xbmc/pvr/channels/PVRChannelGroupFromClient.h index b0d9df8bd0db1..833c166de15c0 100644 --- a/xbmc/pvr/channels/PVRChannelGroupFromClient.h +++ b/xbmc/pvr/channels/PVRChannelGroupFromClient.h @@ -34,6 +34,12 @@ class CPVRChannelGroupFromClient : public CPVRChannelGroup int clientID, const std::shared_ptr& allChannelsGroup); + /*! + * @brief Get the group's origin. + * @return The origin. + */ + Origin GetOrigin() const override { return Origin::CLIENT; } + /*! * @brief Check whether this group could be deleted by the user. * @return True if the group could be deleted, false otherwise. @@ -69,6 +75,6 @@ class CPVRChannelGroupFromClient : public CPVRChannelGroup /*! * @brief Return the type of this group. */ - int GroupType() const override { return PVR_GROUP_TYPE_REMOTE; } + int GroupType() const override { return PVR_GROUP_TYPE_CLIENT; } }; } // namespace PVR diff --git a/xbmc/pvr/channels/PVRChannelGroupFromUser.h b/xbmc/pvr/channels/PVRChannelGroupFromUser.h index baf1f1cf8176b..26ab71ea61f62 100644 --- a/xbmc/pvr/channels/PVRChannelGroupFromUser.h +++ b/xbmc/pvr/channels/PVRChannelGroupFromUser.h @@ -27,6 +27,12 @@ class CPVRChannelGroupFromUser : public CPVRChannelGroup { } + /*! + * @brief Get the group's origin. + * @return The origin. + */ + Origin GetOrigin() const override { return Origin::USER; } + /*! * @brief Check whether this group could be deleted by the user. * @return True if the group could be deleted, false otherwise. @@ -65,6 +71,6 @@ class CPVRChannelGroupFromUser : public CPVRChannelGroup /*! * @brief Return the type of this group. */ - int GroupType() const override { return PVR_GROUP_TYPE_LOCAL; } + int GroupType() const override { return PVR_GROUP_TYPE_USER; } }; } // namespace PVR diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp index f692e19110aab..37b2d4de13ff8 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.cpp +++ b/xbmc/pvr/channels/PVRChannelGroups.cpp @@ -170,11 +170,11 @@ int CPVRChannelGroups::GetGroupTypePriority( { switch (group->GroupType()) { - case PVR_GROUP_TYPE_ALL_CHANNELS: + case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS: return 0; // highest - case PVR_GROUP_TYPE_LOCAL: + case PVR_GROUP_TYPE_USER: return 1; - case PVR_GROUP_TYPE_REMOTE: + case PVR_GROUP_TYPE_CLIENT: return 2; default: return 3; @@ -379,9 +379,9 @@ bool CPVRChannelGroups::UpdateChannelNumbersFromAllChannelsGroup() std::shared_ptr CPVRChannelGroups::CreateChannelGroup( int iType, const CPVRChannelsPath& path) { - if (iType == PVR_GROUP_TYPE_ALL_CHANNELS) + if (iType == PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS) return std::make_shared(path); - else if (iType == PVR_GROUP_TYPE_LOCAL) + else if (iType == PVR_GROUP_TYPE_USER) return std::make_shared(path, GetGroupAll()); else return std::make_shared(path, GetGroupAll()); @@ -614,7 +614,7 @@ std::shared_ptr CPVRChannelGroups::AddGroup(const std::string& if (!group) { // create a new local group - group = CreateChannelGroup(PVR_GROUP_TYPE_LOCAL, + group = CreateChannelGroup(PVR_GROUP_TYPE_USER, CPVRChannelsPath(m_bRadio, strName, PVR_GROUP_CLIENT_ID_LOCAL)); m_groups.emplace_back(group); From 900d1b61f8458fd2416e3f683426a194ed419739 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 21 Jun 2024 08:44:33 +0200 Subject: [PATCH 076/651] [PVR] Cleanup: Make CPVRChannelGroup::GroupType() public. --- xbmc/pvr/channels/PVRChannelGroup.h | 13 ++++++------- xbmc/pvr/channels/PVRChannelGroupAllChannels.h | 11 +++++------ xbmc/pvr/channels/PVRChannelGroupFromClient.h | 11 +++++------ xbmc/pvr/channels/PVRChannelGroupFromUser.h | 11 +++++------ 4 files changed, 21 insertions(+), 25 deletions(-) diff --git a/xbmc/pvr/channels/PVRChannelGroup.h b/xbmc/pvr/channels/PVRChannelGroup.h index ab208e0a338d3..61b832bcbc6ce 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.h +++ b/xbmc/pvr/channels/PVRChannelGroup.h @@ -51,8 +51,7 @@ using GroupMemberPair = class CPVRChannelGroup : public IChannelGroupSettingsCallback { - friend class CPVRChannelGroups; // for GroupType() - friend class CPVRDatabase; // for GroupType() + friend class CPVRDatabase; public: static const int INVALID_GROUP_ID = -1; @@ -471,6 +470,11 @@ class CPVRChannelGroup : public IChannelGroupSettingsCallback */ virtual Origin GetOrigin() const = 0; + /*! + * @brief Return the type of this group. + */ + virtual int GroupType() const = 0; + /*! * @brief Check whether this group could be deleted by the user. * @return True if the group could be deleted, false otherwise. @@ -550,11 +554,6 @@ class CPVRChannelGroup : public IChannelGroupSettingsCallback static std::weak_ptr m_settingsSingleton; private: - /*! - * @brief Return the type of this group. - */ - virtual int GroupType() const = 0; - /*! * @brief Load the channel group members stored in the database. * @param clients The PVR clients to load data for. Leave empty for all clients. diff --git a/xbmc/pvr/channels/PVRChannelGroupAllChannels.h b/xbmc/pvr/channels/PVRChannelGroupAllChannels.h index d91fdd27bfbbc..83f53f92175f0 100644 --- a/xbmc/pvr/channels/PVRChannelGroupAllChannels.h +++ b/xbmc/pvr/channels/PVRChannelGroupAllChannels.h @@ -59,6 +59,11 @@ class CPVRChannelGroupAllChannels : public CPVRChannelGroup */ Origin GetOrigin() const override { return Origin::SYSTEM; } + /*! + * @brief Return the type of this group. + */ + int GroupType() const override { return PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS; } + /*! * @brief Check whether this group could be deleted by the user. * @return True if the group could be deleted, false otherwise. @@ -98,11 +103,5 @@ class CPVRChannelGroupAllChannels : public CPVRChannelGroup * @return True on success, false otherwise. */ bool UpdateFromClients(const std::vector>& clients) override; - -private: - /*! - * @brief Return the type of this group. - */ - int GroupType() const override { return PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS; } }; } // namespace PVR diff --git a/xbmc/pvr/channels/PVRChannelGroupFromClient.h b/xbmc/pvr/channels/PVRChannelGroupFromClient.h index 833c166de15c0..a25cdd2b38990 100644 --- a/xbmc/pvr/channels/PVRChannelGroupFromClient.h +++ b/xbmc/pvr/channels/PVRChannelGroupFromClient.h @@ -40,6 +40,11 @@ class CPVRChannelGroupFromClient : public CPVRChannelGroup */ Origin GetOrigin() const override { return Origin::CLIENT; } + /*! + * @brief Return the type of this group. + */ + int GroupType() const override { return PVR_GROUP_TYPE_CLIENT; } + /*! * @brief Check whether this group could be deleted by the user. * @return True if the group could be deleted, false otherwise. @@ -70,11 +75,5 @@ class CPVRChannelGroupFromClient : public CPVRChannelGroup * @return True on success, false otherwise. */ bool UpdateFromClients(const std::vector>& clients) override; - -private: - /*! - * @brief Return the type of this group. - */ - int GroupType() const override { return PVR_GROUP_TYPE_CLIENT; } }; } // namespace PVR diff --git a/xbmc/pvr/channels/PVRChannelGroupFromUser.h b/xbmc/pvr/channels/PVRChannelGroupFromUser.h index 26ab71ea61f62..39c0a0c127d52 100644 --- a/xbmc/pvr/channels/PVRChannelGroupFromUser.h +++ b/xbmc/pvr/channels/PVRChannelGroupFromUser.h @@ -33,6 +33,11 @@ class CPVRChannelGroupFromUser : public CPVRChannelGroup */ Origin GetOrigin() const override { return Origin::USER; } + /*! + * @brief Return the type of this group. + */ + int GroupType() const override { return PVR_GROUP_TYPE_USER; } + /*! * @brief Check whether this group could be deleted by the user. * @return True if the group could be deleted, false otherwise. @@ -66,11 +71,5 @@ class CPVRChannelGroupFromUser : public CPVRChannelGroup { return true; // Nothing to update from any client for locally managed groups. } - -private: - /*! - * @brief Return the type of this group. - */ - int GroupType() const override { return PVR_GROUP_TYPE_USER; } }; } // namespace PVR From b1291267fe1061d3fd41df3ab3e90f84be419329 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:36:07 +0200 Subject: [PATCH 077/651] [PVR] Cleanup: Rework channel group creation. Introduce CPVRChannelGroupFactory. --- xbmc/pvr/PVRDatabase.cpp | 6 +- xbmc/pvr/addons/PVRClient.cpp | 5 +- xbmc/pvr/channels/CMakeLists.txt | 2 + xbmc/pvr/channels/PVRChannelGroupFactory.cpp | 75 +++++++++++++++++++ xbmc/pvr/channels/PVRChannelGroupFactory.h | 77 ++++++++++++++++++++ xbmc/pvr/channels/PVRChannelGroups.cpp | 75 +++++++------------ xbmc/pvr/channels/PVRChannelGroups.h | 11 ++- 7 files changed, 191 insertions(+), 60 deletions(-) create mode 100644 xbmc/pvr/channels/PVRChannelGroupFactory.cpp create mode 100644 xbmc/pvr/channels/PVRChannelGroupFactory.h diff --git a/xbmc/pvr/PVRDatabase.cpp b/xbmc/pvr/PVRDatabase.cpp index f6f7e6c5e7bda..c84b658e3c842 100644 --- a/xbmc/pvr/PVRDatabase.cpp +++ b/xbmc/pvr/PVRDatabase.cpp @@ -12,6 +12,7 @@ #include "dbwrappers/dataset.h" #include "pvr/addons/PVRClient.h" #include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroupFactory.h" #include "pvr/channels/PVRChannelGroupMember.h" #include "pvr/channels/PVRChannelGroups.h" #include "pvr/providers/PVRProvider.h" @@ -721,10 +722,11 @@ int CPVRDatabase::GetGroups(CPVRChannelGroups& results, const std::string& query { while (!m_pDS->eof()) { - const std::shared_ptr group = results.CreateChannelGroup( + const std::shared_ptr group = results.GetGroupFactory()->CreateGroup( m_pDS->fv("iGroupType").get_asInt(), CPVRChannelsPath(m_pDS->fv("bIsRadio").get_asBool(), m_pDS->fv("sName").get_asString(), - m_pDS->fv("iClientId").get_asInt())); + m_pDS->fv("iClientId").get_asInt()), + results.GetGroupAll()); group->m_iGroupId = m_pDS->fv("idGroup").get_asInt(); group->m_iLastWatched = static_cast(m_pDS->fv("iLastWatched").get_asInt()); diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 84b97815e3244..bb9ba191f38d8 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -24,8 +24,7 @@ #include "pvr/addons/PVRClients.h" #include "pvr/channels/PVRChannel.h" #include "pvr/channels/PVRChannelGroup.h" -#include "pvr/channels/PVRChannelGroupAllChannels.h" -#include "pvr/channels/PVRChannelGroupFromClient.h" +#include "pvr/channels/PVRChannelGroupFactory.h" #include "pvr/channels/PVRChannelGroupMember.h" #include "pvr/channels/PVRChannelGroups.h" #include "pvr/channels/PVRChannelGroupsContainer.h" @@ -1709,7 +1708,7 @@ void CPVRClient::cb_transfer_channel_group(void* kodiInstance, // transfer this entry to the groups container CPVRChannelGroups* kodiGroups = static_cast(handle->dataAddress); - const auto transferGroup = std::make_shared( + const auto transferGroup = kodiGroups->GetGroupFactory()->CreateClientGroup( *group, client->GetID(), kodiGroups->GetGroupAll()); kodiGroups->UpdateFromClient(transferGroup); }); diff --git a/xbmc/pvr/channels/CMakeLists.txt b/xbmc/pvr/channels/CMakeLists.txt index 25689e1081536..c1eb19f4df0b5 100644 --- a/xbmc/pvr/channels/CMakeLists.txt +++ b/xbmc/pvr/channels/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES PVRChannel.cpp PVRChannelGroup.cpp PVRChannelGroupAllChannels.cpp + PVRChannelGroupFactory.cpp PVRChannelGroupFromClient.cpp PVRChannelGroupMember.cpp PVRChannelGroupSettings.cpp @@ -13,6 +14,7 @@ set(SOURCES PVRChannel.cpp set(HEADERS PVRChannel.h PVRChannelGroup.h PVRChannelGroupAllChannels.h + PVRChannelGroupFactory.h PVRChannelGroupFromClient.h PVRChannelGroupFromUser.h PVRChannelGroupMember.h diff --git a/xbmc/pvr/channels/PVRChannelGroupFactory.cpp b/xbmc/pvr/channels/PVRChannelGroupFactory.cpp new file mode 100644 index 0000000000000..a895b5355449f --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupFactory.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 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 "PVRChannelGroupFactory.h" + +#include "pvr/channels/PVRChannelGroupAllChannels.h" +#include "pvr/channels/PVRChannelGroupFromClient.h" +#include "pvr/channels/PVRChannelGroupFromUser.h" +#include "pvr/channels/PVRChannelsPath.h" +#include "utils/log.h" + +using namespace PVR; + +std::shared_ptr CPVRChannelGroupFactory::CreateAllChannelsGroup(bool isRadio) +{ + return std::make_shared(isRadio); +} + +std::shared_ptr CPVRChannelGroupFactory::CreateClientGroup( + const PVR_CHANNEL_GROUP& groupData, + int clientID, + const std::shared_ptr& allChannels) +{ + return std::make_shared(groupData, clientID, allChannels); +} + +std::shared_ptr CPVRChannelGroupFactory::CreateUserGroup( + bool isRadio, + const std::string& name, + const std::shared_ptr& allChannels) +{ + return std::make_shared( + CPVRChannelsPath{isRadio, name, PVR_GROUP_CLIENT_ID_LOCAL}, allChannels); +} + +std::shared_ptr CPVRChannelGroupFactory::CreateGroup( + int groupType, + const CPVRChannelsPath& groupPath, + const std::shared_ptr& allChannels) +{ + switch (groupType) + { + case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS: + return std::make_shared(groupPath); + case PVR_GROUP_TYPE_USER: + return std::make_shared(groupPath, allChannels); + case PVR_GROUP_TYPE_CLIENT: + return std::make_shared(groupPath, allChannels); + default: + CLog::LogFC(LOGERROR, LOGPVR, "Cannot create channel group '{}'. Unknown type {}.", + groupPath.GetGroupName(), groupType); + return {}; + } +} + +int CPVRChannelGroupFactory::GetGroupTypePriority( + const std::shared_ptr& group) const +{ + switch (group->GroupType()) + { + case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS: + return 0; // highest + case PVR_GROUP_TYPE_USER: + return 1; + case PVR_GROUP_TYPE_CLIENT: + return 2; + default: + return 3; + } +} diff --git a/xbmc/pvr/channels/PVRChannelGroupFactory.h b/xbmc/pvr/channels/PVRChannelGroupFactory.h new file mode 100644 index 0000000000000..569eb9b3324c2 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupFactory.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 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 + +struct PVR_CHANNEL_GROUP; + +namespace PVR +{ + +class CPVRChannelGroup; +class CPVRChannelsPath; + +class CPVRChannelGroupFactory +{ +public: + CPVRChannelGroupFactory() = default; + virtual ~CPVRChannelGroupFactory() = default; + + /*! + * @brief Create an all channels group instance. + * @param isRadio Whether Radio or TV. + * @return The new group. + */ + std::shared_ptr CreateAllChannelsGroup(bool isRadio); + + /*! + * @brief Create an instance for a group provided by a PVR client add-on. + * @param groupData The group data. + * @param clientID The id of the client that provided the group. + * @param allChannels The all channels group. + * @return The new group. + */ + std::shared_ptr CreateClientGroup( + const PVR_CHANNEL_GROUP& groupData, + int clientID, + const std::shared_ptr& allChannels); + + /*! + * @brief Create an instance for a group created by the user. + * @param isRadio Whether Radio or TV. + * @param name The name for the group. + * @param allChannels The all channels group. + * @return The new group. + */ + std::shared_ptr CreateUserGroup( + bool isRadio, + const std::string& name, + const std::shared_ptr& allChannels); + + /*! + * @brief Create a channel group matching the given type. + * @param groupType The type of the group. + * @param groupPath The path of the group. + * @param allChannels The all channels group. + * @return The new group. + */ + std::shared_ptr CreateGroup( + int groupType, + const CPVRChannelsPath& groupPath, + const std::shared_ptr& allChannels); + + /*! + * @brief Get the priority (e.g. for sorting groups) for the type of the given group. Lower number means higer priority. + * @param group The group. + * @return The group's type priority. + */ + int GetGroupTypePriority(const std::shared_ptr& group) const; +}; +} // namespace PVR diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp index 37b2d4de13ff8..6eca862225ff4 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.cpp +++ b/xbmc/pvr/channels/PVRChannelGroups.cpp @@ -16,9 +16,7 @@ #include "pvr/addons/PVRClientUID.h" #include "pvr/addons/PVRClients.h" #include "pvr/channels/PVRChannel.h" -#include "pvr/channels/PVRChannelGroupAllChannels.h" -#include "pvr/channels/PVRChannelGroupFromClient.h" -#include "pvr/channels/PVRChannelGroupFromUser.h" +#include "pvr/channels/PVRChannelGroupFactory.h" #include "pvr/channels/PVRChannelGroupMember.h" #include "pvr/channels/PVRChannelsPath.h" #include "settings/AdvancedSettings.h" @@ -41,7 +39,9 @@ using namespace PVR; CPVRChannelGroups::CPVRChannelGroups(bool bRadio) - : m_bRadio(bRadio), m_settings({CSettings::SETTING_PVRMANAGER_BACKENDCHANNELGROUPSORDER}) + : m_bRadio(bRadio), + m_settings({CSettings::SETTING_PVRMANAGER_BACKENDCHANNELGROUPSORDER}), + m_channelGroupFactory(new CPVRChannelGroupFactory) { m_settings.RegisterCallback(this); } @@ -79,6 +79,7 @@ void CPVRChannelGroups::Unload() m_groups.clear(); m_allChannelsGroup.reset(); + m_channelGroupFactory.reset(new CPVRChannelGroupFactory); m_failedClientsForChannelGroups.clear(); } @@ -165,22 +166,6 @@ bool CPVRChannelGroups::Update(const std::shared_ptr& group, return true; } -int CPVRChannelGroups::GetGroupTypePriority( - const std::shared_ptr& group) const -{ - switch (group->GroupType()) - { - case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS: - return 0; // highest - case PVR_GROUP_TYPE_USER: - return 1; - case PVR_GROUP_TYPE_CLIENT: - return 2; - default: - return 3; - } -} - int CPVRChannelGroups::GetGroupClientPriority( const std::shared_ptr& group) const { @@ -193,22 +178,26 @@ void CPVRChannelGroups::SortGroupsByBackendOrder() { std::unique_lock lock(m_critSection); + const auto& gF = GetGroupFactory(); + // sort by group type, then by client priority, then by position, last by name - std::sort(m_groups.begin(), m_groups.end(), [this](const auto& group1, const auto& group2) { - if (GetGroupTypePriority(group1) == GetGroupTypePriority(group2)) - { - if (GetGroupClientPriority(group1) == GetGroupClientPriority(group2)) - { - if (group1->GetClientPosition() == group2->GetClientPosition()) - { - return group1->GroupName() < group2->GroupName(); - } - return group1->GetClientPosition() < group2->GetClientPosition(); - } - return GetGroupClientPriority(group1) < GetGroupClientPriority(group2); - } - return GetGroupTypePriority(group1) < GetGroupTypePriority(group2); - }); + std::sort(m_groups.begin(), m_groups.end(), + [this, &gF](const auto& group1, const auto& group2) + { + if (gF->GetGroupTypePriority(group1) == gF->GetGroupTypePriority(group2)) + { + if (GetGroupClientPriority(group1) == GetGroupClientPriority(group2)) + { + if (group1->GetClientPosition() == group2->GetClientPosition()) + { + return group1->GroupName() < group2->GroupName(); + } + return group1->GetClientPosition() < group2->GetClientPosition(); + } + return GetGroupClientPriority(group1) < GetGroupClientPriority(group2); + } + return gF->GetGroupTypePriority(group1) < gF->GetGroupTypePriority(group2); + }); } void CPVRChannelGroups::SortGroupsByLocalOrder() @@ -376,17 +365,6 @@ bool CPVRChannelGroups::UpdateChannelNumbersFromAllChannelsGroup() }); } -std::shared_ptr CPVRChannelGroups::CreateChannelGroup( - int iType, const CPVRChannelsPath& path) -{ - if (iType == PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS) - return std::make_shared(path); - else if (iType == PVR_GROUP_TYPE_USER) - return std::make_shared(path, GetGroupAll()); - else - return std::make_shared(path, GetGroupAll()); -} - bool CPVRChannelGroups::LoadFromDatabase(const std::vector>& clients) { const std::shared_ptr database( @@ -400,7 +378,7 @@ bool CPVRChannelGroups::LoadFromDatabase(const std::vector(m_bRadio); + const auto internalGroup = GetGroupFactory()->CreateAllChannelsGroup(IsRadio()); m_groups.emplace_back(internalGroup); m_allChannelsGroup = internalGroup; } @@ -614,8 +592,7 @@ std::shared_ptr CPVRChannelGroups::AddGroup(const std::string& if (!group) { // create a new local group - group = CreateChannelGroup(PVR_GROUP_TYPE_USER, - CPVRChannelsPath(m_bRadio, strName, PVR_GROUP_CLIENT_ID_LOCAL)); + group = GetGroupFactory()->CreateUserGroup(IsRadio(), strName, GetGroupAll()); m_groups.emplace_back(group); SortGroups(); diff --git a/xbmc/pvr/channels/PVRChannelGroups.h b/xbmc/pvr/channels/PVRChannelGroups.h index bb86c28d96573..459759546141a 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.h +++ b/xbmc/pvr/channels/PVRChannelGroups.h @@ -23,6 +23,7 @@ namespace PVR enum class PVREvent; class CPVRChannel; +class CPVRChannelGroupFactory; class CPVRClient; /** A container class for channel groups */ @@ -53,12 +54,10 @@ class CPVRChannelGroups : public ISettingCallback bool LoadFromDatabase(const std::vector>& clients); /*! - * @brief Create a channel group matching the given type. - * @param iType The type for the group. - * @param path The path of the group. - * @return The new group. + * @brief Get the channel group factory + * @return The factory. */ - std::shared_ptr CreateChannelGroup(int iType, const CPVRChannelsPath& path); + std::shared_ptr GetGroupFactory() const { return m_channelGroupFactory; } /*! * @return Amount of groups in this container @@ -243,7 +242,6 @@ class CPVRChannelGroups : public ISettingCallback void OnPVRManagerEvent(const PVR::PVREvent& event); - int GetGroupTypePriority(const std::shared_ptr& group) const; int GetGroupClientPriority(const std::shared_ptr& group) const; bool m_bRadio{false}; @@ -253,5 +251,6 @@ class CPVRChannelGroups : public ISettingCallback bool m_isSubscribed{false}; CPVRSettings m_settings; std::shared_ptr m_allChannelsGroup; + std::shared_ptr m_channelGroupFactory; }; } // namespace PVR From ebf40ca814fadf8037a2b7d81f86154f73574a92 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 18 May 2024 20:23:34 +1000 Subject: [PATCH 078/651] [cmake][modules] FindDetours correctly set IMPORTED_LOCATION_RELEASE property --- cmake/modules/FindDetours.cmake | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindDetours.cmake b/cmake/modules/FindDetours.cmake index 6d87c85ea618e..50fce108becbe 100644 --- a/cmake/modules/FindDetours.cmake +++ b/cmake/modules/FindDetours.cmake @@ -26,11 +26,17 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) if(DETOURS_FOUND) add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${DETOURS_INCLUDE_DIR}" - IMPORTED_LOCATION "${DETOURS_LIBRARY_RELEASE}") + INTERFACE_INCLUDE_DIRECTORIES "${DETOURS_INCLUDE_DIR}") + if(DETOURS_LIBRARY_RELEASE) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${DETOURS_LIBRARY_RELEASE}") + endif() if(DETOURS_LIBRARY_DEBUG) set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES IMPORTED_LOCATION_DEBUG "${DETOURS_LIBRARY_DEBUG}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) endif() else() if(Detours_FIND_REQUIRED) From a3f300869355eea375f19a206daf7f2aae53982d Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 19 May 2024 09:39:39 +1000 Subject: [PATCH 079/651] [cmake][modules] FindCrossGUID fix IMPORTED_CONFIGURATIONS list --- cmake/modules/FindCrossGUID.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/modules/FindCrossGUID.cmake b/cmake/modules/FindCrossGUID.cmake index b5035c3c53081..56a43f58ce0b6 100644 --- a/cmake/modules/FindCrossGUID.cmake +++ b/cmake/modules/FindCrossGUID.cmake @@ -80,8 +80,9 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) endif() if(CROSSGUID_LIBRARY_DEBUG) set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG IMPORTED_LOCATION_DEBUG "${CROSSGUID_LIBRARY_DEBUG}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) endif() set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CROSSGUID_INCLUDE_DIRS}" From 56d7c64e5c4b20e6aa43b8dd020586cff20272c8 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 19 May 2024 09:40:41 +1000 Subject: [PATCH 080/651] [cmake][modules] FindCurl fix IMPORTED_CONFIGURATIONS list --- cmake/modules/FindCurl.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/modules/FindCurl.cmake b/cmake/modules/FindCurl.cmake index 4e289cbe0e5f8..7870b11031739 100644 --- a/cmake/modules/FindCurl.cmake +++ b/cmake/modules/FindCurl.cmake @@ -176,8 +176,9 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) endif() if(CURL_LIBRARY_DEBUG) set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG IMPORTED_LOCATION_DEBUG "${CURL_LIBRARY_DEBUG}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) endif() # Add link libraries for static lib usage found from pkg-config From b08a6e8bb05b9b10161ae6d3200f8d3ed827eebf Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 19 May 2024 09:41:26 +1000 Subject: [PATCH 081/651] [cmake][modules] FindEffects11 fix IMPORTED_CONFIGURATIONS list --- cmake/modules/FindEffects11.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/modules/FindEffects11.cmake b/cmake/modules/FindEffects11.cmake index 6cb8409838576..c6af7593ee9b8 100644 --- a/cmake/modules/FindEffects11.cmake +++ b/cmake/modules/FindEffects11.cmake @@ -87,8 +87,9 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) endif() if(EFFECTS11_LIBRARY_DEBUG) set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG IMPORTED_LOCATION_DEBUG "${EFFECTS11_LIBRARY_DEBUG}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) endif() endif() From 6f330811844b480d413ddb4e3e987ae5418bf404 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 19 May 2024 09:41:46 +1000 Subject: [PATCH 082/651] [cmake][modules] FindFmt fix IMPORTED_CONFIGURATIONS list --- cmake/modules/FindFmt.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/modules/FindFmt.cmake b/cmake/modules/FindFmt.cmake index a96e9117173fa..475238ec829a9 100644 --- a/cmake/modules/FindFmt.cmake +++ b/cmake/modules/FindFmt.cmake @@ -110,8 +110,9 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) endif() if(FMT_LIBRARY_DEBUG) set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG IMPORTED_LOCATION_DEBUG "${FMT_LIBRARY_DEBUG}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) endif() set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${FMT_INCLUDE_DIR}") From 110ce16e9ff108d60c8069ce6c6d0158d3af6bf4 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 12:11:10 +1000 Subject: [PATCH 083/651] [cmake][modules] FindLircClient cleanup and use core_target_link_libraries --- cmake/modules/FindLircClient.cmake | 17 +++++++---------- xbmc/platform/linux/input/CMakeLists.txt | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/cmake/modules/FindLircClient.cmake b/cmake/modules/FindLircClient.cmake index 888cbd628061c..290f230340fd8 100644 --- a/cmake/modules/FindLircClient.cmake +++ b/cmake/modules/FindLircClient.cmake @@ -4,9 +4,9 @@ # # This will define the following target: # -# LIRCCLIENT::LIRCCLIENT - The lirc library +# ${APP_NAME_LC}::LircClient - The lirc library -if(NOT TARGET LIRCCLIENT::LIRCCLIENT) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(PC_LIRC lirc QUIET) @@ -22,13 +22,10 @@ if(NOT TARGET LIRCCLIENT::LIRCCLIENT) REQUIRED_VARS LIRCCLIENT_LIBRARY LIRCCLIENT_INCLUDE_DIR) if(LIRCCLIENT_FOUND) - add_library(LIRCCLIENT::LIRCCLIENT UNKNOWN IMPORTED) - set_target_properties(LIRCCLIENT::LIRCCLIENT PROPERTIES - IMPORTED_LOCATION "${LIRCCLIENT_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${LIRCCLIENT_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS "HAS_LIRC=1") - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP LIRCCLIENT::LIRCCLIENT) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${LIRCCLIENT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIRCCLIENT_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_LIRC) endif() - - mark_as_advanced(LIRCCLIENT_LIBRARY LIRCCLIENT_INCLUDE_DIR) endif() diff --git a/xbmc/platform/linux/input/CMakeLists.txt b/xbmc/platform/linux/input/CMakeLists.txt index 0859158c9c1f3..d3088d44c2420 100644 --- a/xbmc/platform/linux/input/CMakeLists.txt +++ b/xbmc/platform/linux/input/CMakeLists.txt @@ -1,7 +1,7 @@ set(SOURCES "") set(HEADERS "") -if(TARGET LIRCCLIENT::LIRCCLIENT) +if(TARGET ${APP_NAME_LC}::LircClient) list(APPEND SOURCES LIRC.cpp) list(APPEND HEADERS LIRC.h) endif() From e8b3135a3f5a200a28b5a6c7f8ff52c86a168753 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 13:13:14 +1000 Subject: [PATCH 084/651] [cmake][macro] core_optional_dep append to optional_deps list if dep found --- cmake/scripts/common/Macros.cmake | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index 65d6406ec7708..c55bf01a2a03a 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -440,12 +440,21 @@ function(core_optional_dep) set(_required True) endif() - if(${depup}_FOUND) + if(${depup}_FOUND OR TARGET kodi::${dep}) list(APPEND SYSTEM_INCLUDES ${${depup}_INCLUDE_DIRS}) list(APPEND DEPLIBS ${${depup}_LIBRARIES}) list(APPEND DEP_DEFINES ${${depup}_DEFINITIONS}) set(final_message ${final_message} "${depup} enabled: Yes") export_dep() + + # We dont want to add a build tool + if (NOT ${depspec} IN_LIST optional_buildtools AND NOT ${depspec} IN_LIST required_buildtools) + # If dependency is found and is not in the list (eg mariadb/mysql) add to list + if (NOT ${depspec} IN_LIST optional_deps) + set(optional_deps ${optional_deps} ${depspec} PARENT_SCOPE) + endif() + endif() + elseif(_required) message(FATAL_ERROR "${depup} enabled but not found") else() From e3acb8f79b9c6853e454750b833b487ab964f538 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 12:16:32 +1000 Subject: [PATCH 085/651] [cmake][modules] FindMariaDBClient cleanup and use core_target_link_libraries --- CMakeLists.txt | 2 +- cmake/modules/FindMariaDBClient.cmake | 43 +++++++++++---------------- xbmc/dbwrappers/CMakeLists.txt | 2 +- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0ce337b0a4d5..70d7619a909d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -272,7 +272,7 @@ elseif(ENABLE_MYSQLCLIENT AND NOT ENABLE_MYSQLCLIENT STREQUAL AUTO) endif() core_optional_dep(MariaDBClient) -if(NOT TARGET MariaDBClient::MariaDBClient) +if(NOT TARGET ${APP_NAME_LC}::MariaDBClient) core_optional_dep(MySqlClient) endif() diff --git a/cmake/modules/FindMariaDBClient.cmake b/cmake/modules/FindMariaDBClient.cmake index efbe395500a45..98099a411d536 100644 --- a/cmake/modules/FindMariaDBClient.cmake +++ b/cmake/modules/FindMariaDBClient.cmake @@ -5,14 +5,9 @@ # # This will define the following target: # -# MariaDBClient::MariaDBClient - The MariaDBClient library - -if(NOT TARGET MariaDBClient::MariaDBClient) - # Don't find system wide installed version on Windows - if(WIN32) - set(EXTRA_FIND_ARGS NO_SYSTEM_ENVIRONMENT_PATH) - endif() +# ${APP_NAME_LC}::MariaDBClient - The MariaDBClient library +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) if(PKG_CONFIG_FOUND) @@ -20,18 +15,15 @@ if(NOT TARGET MariaDBClient::MariaDBClient) endif() find_path(MARIADBCLIENT_INCLUDE_DIR NAMES mariadb/mysql.h mariadb/server/mysql.h - HINTS ${PC_MARIADBCLIENT_INCLUDEDIR} - NO_CACHE) + HINTS ${PC_MARIADBCLIENT_INCLUDEDIR}) find_library(MARIADBCLIENT_LIBRARY_RELEASE NAMES mariadbclient mariadb libmariadb HINTS ${PC_MARIADBCLIENT_LIBDIR} PATH_SUFFIXES mariadb - ${EXTRA_FIND_ARGS} - NO_CACHE) + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) find_library(MARIADBCLIENT_LIBRARY_DEBUG NAMES mariadbclient mariadb libmariadbd HINTS ${PC_MARIADBCLIENT_LIBDIR} PATH_SUFFIXES mariadb - ${EXTRA_FIND_ARGS} - NO_CACHE) + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) if(PC_MARIADBCLIENT_VERSION) set(MARIADBCLIENT_VERSION_STRING ${PC_MARIADBCLIENT_VERSION}) @@ -50,24 +42,23 @@ if(NOT TARGET MariaDBClient::MariaDBClient) VERSION_VAR MARIADBCLIENT_VERSION_STRING) if(MARIADBCLIENT_FOUND) - add_library(MariaDBClient::MariaDBClient UNKNOWN IMPORTED) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) if(MARIADBCLIENT_LIBRARY_RELEASE) - set_target_properties(MariaDBClient::MariaDBClient PROPERTIES - IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION_RELEASE "${MARIADBCLIENT_LIBRARY_RELEASE}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${MARIADBCLIENT_LIBRARY_RELEASE}") endif() if(MARIADBCLIENT_LIBRARY_DEBUG) - set_target_properties(MariaDBClient::MariaDBClient PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION_DEBUG "${MARIADBCLIENT_LIBRARY_DEBUG}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION_DEBUG "${MARIADBCLIENT_LIBRARY_DEBUG}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) endif() - set_target_properties(MariaDBClient::MariaDBClient PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${MARIADBCLIENT_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_MARIADB=1) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${MARIADBCLIENT_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_MARIADB) if(CORE_SYSTEM_NAME STREQUAL osx) - target_link_libraries(MariaDBClient::MariaDBClient INTERFACE gssapi_krb5) + target_link_libraries(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE gssapi_krb5) endif() - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP MariaDBClient::MariaDBClient) endif() endif() diff --git a/xbmc/dbwrappers/CMakeLists.txt b/xbmc/dbwrappers/CMakeLists.txt index 2c5f0f8f09645..eabd3087d3a5f 100644 --- a/xbmc/dbwrappers/CMakeLists.txt +++ b/xbmc/dbwrappers/CMakeLists.txt @@ -10,7 +10,7 @@ set(HEADERS Database.h qry_dat.h sqlitedataset.h) -if(TARGET MySqlClient::MySqlClient OR TARGET MariaDBClient::MariaDBClient) +if(TARGET MySqlClient::MySqlClient OR TARGET ${APP_NAME_LC}::MariaDBClient) list(APPEND SOURCES mysqldataset.cpp) list(APPEND HEADERS mysqldataset.h) endif() From d2721eecbcd2200f8db4aca6fb00494e1427ae63 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 12:19:40 +1000 Subject: [PATCH 086/651] [cmake][modules] FindMDNS cleanup and use core_target_link_libraries --- cmake/modules/FindMDNS.cmake | 28 ++++++++++++---------------- xbmc/network/mdns/CMakeLists.txt | 2 +- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/cmake/modules/FindMDNS.cmake b/cmake/modules/FindMDNS.cmake index 1ae839b81f15e..b302fc0f61cba 100644 --- a/cmake/modules/FindMDNS.cmake +++ b/cmake/modules/FindMDNS.cmake @@ -5,31 +5,27 @@ # # This will define the following target: # -# MDNS::MDNS - The mDNSlibrary +# ${APP_NAME_LC}::MDNS - The mDNSlibrary -if(NOT TARGET MDNS::MDNS) - find_path(MDNS_INCLUDE_DIR NAMES dmDnsEmbedded.h dns_sd.h - NO_CACHE) - find_library(MDNS_LIBRARY NAMES mDNSEmbedded dnssd - NO_CACHE) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + find_path(MDNS_INCLUDE_DIR NAMES dmDnsEmbedded.h dns_sd.h) + find_library(MDNS_LIBRARY NAMES mDNSEmbedded dnssd) - find_path(MDNS_EMBEDDED_INCLUDE_DIR NAMES mDnsEmbedded.h - NO_CACHE) + find_path(MDNS_EMBEDDED_INCLUDE_DIR NAMES mDnsEmbedded.h) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(MDNS REQUIRED_VARS MDNS_LIBRARY MDNS_INCLUDE_DIR) if(MDNS_FOUND) - add_library(MDNS::MDNS UNKNOWN IMPORTED) - set_target_properties(MDNS::MDNS PROPERTIES - IMPORTED_LOCATION "${MDNS_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${MDNS_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS "HAS_MDNS=1;HAS_ZEROCONF=1") + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${MDNS_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${MDNS_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS "HAS_MDNS;HAS_ZEROCONF") if(MDNS_EMBEDDED_INCLUDE_DIR) - set_property(TARGET MDNS::MDNS APPEND PROPERTY - INTERFACE_COMPILE_DEFINITIONS HAS_MDNS_EMBEDDED=1) + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS HAS_MDNS_EMBEDDED) endif() - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP MDNS::MDNS) endif() endif() diff --git a/xbmc/network/mdns/CMakeLists.txt b/xbmc/network/mdns/CMakeLists.txt index 66db8e8bc8fd7..106022ae2954e 100644 --- a/xbmc/network/mdns/CMakeLists.txt +++ b/xbmc/network/mdns/CMakeLists.txt @@ -1,4 +1,4 @@ -if(TARGET MDNS::MDNS) +if(TARGET ${APP_NAME_LC}::MDNS) set(SOURCES ZeroconfBrowserMDNS.cpp ZeroconfMDNS.cpp) From 16a5c4aa762cbb6e3afba3705a3e67c96b2a1b24 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 12:25:23 +1000 Subject: [PATCH 087/651] [cmake][modules] FindMySqlClient cleanup and use core_target_link_libraries --- cmake/modules/FindMySqlClient.cmake | 45 +++++++---------------------- xbmc/dbwrappers/CMakeLists.txt | 2 +- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/cmake/modules/FindMySqlClient.cmake b/cmake/modules/FindMySqlClient.cmake index aed95776f6534..64a28e4bd2bb2 100644 --- a/cmake/modules/FindMySqlClient.cmake +++ b/cmake/modules/FindMySqlClient.cmake @@ -5,24 +5,14 @@ # # This will define the following target: # -# MySqlClient::MySqlClient - The MySqlClient library +# ${APP_NAME_LC}::MySqlClient - The MySqlClient library -if(NOT TARGET MySqlClient::MySqlClient) - # Don't find system wide installed version on Windows - if(WIN32) - set(EXTRA_FIND_ARGS NO_SYSTEM_ENVIRONMENT_PATH) - endif() +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) - find_path(MYSQLCLIENT_INCLUDE_DIR NAMES mysql/mysql.h mysql/server/mysql.h - NO_CACHE) - find_library(MYSQLCLIENT_LIBRARY_RELEASE NAMES mysqlclient libmysql - PATH_SUFFIXES mysql - ${EXTRA_FIND_ARGS} - NO_CACHE) - find_library(MYSQLCLIENT_LIBRARY_DEBUG NAMES mysqlclient libmysql - PATH_SUFFIXES mysql - ${EXTRA_FIND_ARGS} - NO_CACHE) + find_path(MYSQLCLIENT_INCLUDE_DIR NAMES mysql/mysql.h mysql/server/mysql.h) + find_library(MYSQLCLIENT_LIBRARY NAMES mysqlclient libmysql + PATH_SUFFIXES mysql + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) if(MYSQLCLIENT_INCLUDE_DIR AND EXISTS "${MYSQLCLIENT_INCLUDE_DIR}/mysql/mysql_version.h") file(STRINGS "${MYSQLCLIENT_INCLUDE_DIR}/mysql/mysql_version.h" mysql_version_str REGEX "^#define[\t ]+LIBMYSQL_VERSION[\t ]+\".*\".*") @@ -30,29 +20,16 @@ if(NOT TARGET MySqlClient::MySqlClient) unset(mysql_version_str) endif() - include(SelectLibraryConfigurations) - select_library_configurations(MYSQLCLIENT) - include(FindPackageHandleStandardArgs) find_package_handle_standard_args(MySqlClient REQUIRED_VARS MYSQLCLIENT_LIBRARY MYSQLCLIENT_INCLUDE_DIR VERSION_VAR MYSQLCLIENT_VERSION_STRING) if(MYSQLCLIENT_FOUND) - add_library(MySqlClient::MySqlClient UNKNOWN IMPORTED) - if(MYSQLCLIENT_LIBRARY_RELEASE) - set_target_properties(MySqlClient::MySqlClient PROPERTIES - IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION_RELEASE "${MYSQLCLIENT_LIBRARY_RELEASE}") - endif() - if(MYSQLCLIENT_LIBRARY_DEBUG) - set_target_properties(MySqlClient::MySqlClient PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION_DEBUG "${MYSQLCLIENT_LIBRARY_DEBUG}") - endif() - set_target_properties(MySqlClient::MySqlClient PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${MYSQLCLIENT_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_MYSQL=1) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP MySqlClient::MySqlClient) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${MYSQLCLIENT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${MYSQLCLIENT_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_MYSQL) endif() endif() diff --git a/xbmc/dbwrappers/CMakeLists.txt b/xbmc/dbwrappers/CMakeLists.txt index eabd3087d3a5f..20ddd914c5e5f 100644 --- a/xbmc/dbwrappers/CMakeLists.txt +++ b/xbmc/dbwrappers/CMakeLists.txt @@ -10,7 +10,7 @@ set(HEADERS Database.h qry_dat.h sqlitedataset.h) -if(TARGET MySqlClient::MySqlClient OR TARGET ${APP_NAME_LC}::MariaDBClient) +if(TARGET ${APP_NAME_LC}::MySqlClient OR TARGET ${APP_NAME_LC}::MariaDBClient) list(APPEND SOURCES mysqldataset.cpp) list(APPEND HEADERS mysqldataset.h) endif() From 93fd9d98dbfeb34c1e71140291f8fac58f53cb32 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 12:44:54 +1000 Subject: [PATCH 088/651] [cmake][modules] FindNFS cleanup and use core_target_link_libraries --- cmake/modules/FindNFS.cmake | 107 +++++++++++++++------------- xbmc/filesystem/CMakeLists.txt | 2 +- xbmc/filesystem/test/CMakeLists.txt | 2 +- 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/cmake/modules/FindNFS.cmake b/cmake/modules/FindNFS.cmake index 2250093749fa5..76a1090652c8a 100644 --- a/cmake/modules/FindNFS.cmake +++ b/cmake/modules/FindNFS.cmake @@ -5,9 +5,9 @@ # # This will define the following target: # -# NFS::NFS - The libnfs library +# ${APP_NAME_LC}::NFS - The libnfs library -if(NOT TARGET libnfs::nfs) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) macro(buildlibnfs) set(CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF @@ -48,52 +48,51 @@ if(NOT TARGET libnfs::nfs) # Build lib buildlibnfs() else() - if(NOT TARGET libnfs::nfs) - - if(NFS_FIND_VERSION) - if(NFS_FIND_VERSION_EXACT) - set(NFS_FIND_SPEC "=${NFS_FIND_VERSION_COMPLETE}") + if(TARGET libnfs::nfs) + # This is for the case where a distro provides a non standard (Debug/Release) config type + # convert this back to either DEBUG/RELEASE or just RELEASE + # we only do this because we use find_package_handle_standard_args for config time output + # and it isnt capable of handling TARGETS, so we have to extract the info + get_target_property(_LIBNFS_CONFIGURATIONS libnfs::nfs IMPORTED_CONFIGURATIONS) + foreach(_libnfs_config IN LISTS _LIBNFS_CONFIGURATIONS) + # Just set to RELEASE var so select_library_configurations can continue to work its magic + string(TOUPPER ${_libnfs_config} _libnfs_config_UPPER) + if((NOT ${_libnfs_config_UPPER} STREQUAL "RELEASE") AND + (NOT ${_libnfs_config_UPPER} STREQUAL "DEBUG")) + get_target_property(LIBNFS_LIBRARY_RELEASE libnfs::nfs IMPORTED_LOCATION_${_libnfs_config_UPPER}) else() - set(NFS_FIND_SPEC ">=${NFS_FIND_VERSION_COMPLETE}") + get_target_property(LIBNFS_LIBRARY_${_libnfs_config_UPPER} libnfs::nfs IMPORTED_LOCATION_${_libnfs_config_UPPER}) endif() - endif() + endforeach() + # libnfs cmake config doesnt include INTERFACE_INCLUDE_DIRECTORIES + find_path(LIBNFS_INCLUDE_DIR NAMES nfsc/libnfs.h + HINTS ${DEPENDS_PATH}/include + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) + else() find_package(PkgConfig) # Try pkgconfig based search as last resort - if(PKG_CONFIG_FOUND) + if(PKG_CONFIG_FOUND AND NOT (WIN32 OR WINDOWS_STORE)) + if(NFS_FIND_VERSION) + if(NFS_FIND_VERSION_EXACT) + set(NFS_FIND_SPEC "=${NFS_FIND_VERSION_COMPLETE}") + else() + set(NFS_FIND_SPEC ">=${NFS_FIND_VERSION_COMPLETE}") + endif() + endif() + pkg_check_modules(PC_LIBNFS libnfs${NFS_FIND_SPEC} QUIET) endif() find_library(LIBNFS_LIBRARY_RELEASE NAMES nfs libnfs HINTS ${DEPENDS_PATH}/lib ${PC_LIBNFS_LIBDIR} - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} - NO_CACHE) + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) + find_path(LIBNFS_INCLUDE_DIR nfsc/libnfs.h HINTS ${PC_LIBNFS_INCLUDEDIR} + ${DEPENDS_PATH}/include + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) set(LIBNFS_VERSION ${PC_LIBNFS_VERSION}) endif() - - find_path(LIBNFS_INCLUDE_DIR nfsc/libnfs.h HINTS ${PC_LIBNFS_INCLUDEDIR} - ${DEPENDS_PATH}/include - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} - NO_CACHE) - endif() - - if(TARGET libnfs::nfs) - # This is for the case where a distro provides a non standard (Debug/Release) config type - # convert this back to either DEBUG/RELEASE or just RELEASE - # we only do this because we use find_package_handle_standard_args for config time output - # and it isnt capable of handling TARGETS, so we have to extract the info - get_target_property(_LIBNFS_CONFIGURATIONS libnfs::nfs IMPORTED_CONFIGURATIONS) - foreach(_libnfs_config IN LISTS _LIBNFS_CONFIGURATIONS) - # Just set to RELEASE var so select_library_configurations can continue to work its magic - string(TOUPPER ${_libnfs_config} _libnfs_config_UPPER) - if((NOT ${_libnfs_config_UPPER} STREQUAL "RELEASE") AND - (NOT ${_libnfs_config_UPPER} STREQUAL "DEBUG")) - get_target_property(LIBNFS_LIBRARY_RELEASE libnfs::nfs IMPORTED_LOCATION_${_libnfs_config_UPPER}) - else() - get_target_property(LIBNFS_LIBRARY_${_libnfs_config_UPPER} libnfs::nfs IMPORTED_LOCATION_${_libnfs_config_UPPER}) - endif() - endforeach() endif() include(SelectLibraryConfigurations) @@ -142,25 +141,37 @@ if(NOT TARGET libnfs::nfs) unset(CMAKE_REQUIRED_LIBRARIES) endif() - if(NOT TARGET libnfs::nfs) - add_library(libnfs::nfs UNKNOWN IMPORTED) - set_target_properties(libnfs::nfs PROPERTIES - IMPORTED_LOCATION "${LIBNFS_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${LIBNFS_INCLUDE_DIR}") - endif() + list(APPEND _nfs_definitions HAS_FILESYSTEM_NFS) - if(TARGET libnfs) - add_dependencies(libnfs::nfs libnfs) + # cmake target and not building internal + if(TARGET libnfs::nfs AND NOT TARGET libnfs) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS libnfs::nfs) + else() + + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${LIBNFS_LIBRARY}") endif() - list(APPEND _nfs_definitions HAS_FILESYSTEM_NFS) + # Test if target is an alias. We cant set properties on alias targets, and must find + # the actual target. + get_property(aliased_target TARGET "${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}" PROPERTY ALIASED_TARGET) + if("${aliased_target}" STREQUAL "") + set(_nfs_target "${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}") + else() + set(_nfs_target "${aliased_target}") + endif() # We need to append in case the cmake config already has definitions - set_property(TARGET libnfs::nfs APPEND PROPERTY - INTERFACE_COMPILE_DEFINITIONS ${_nfs_definitions}) + set_property(TARGET ${_nfs_target} APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS ${_nfs_definitions}) # Need to manually set this, as libnfs cmake config does not provide INTERFACE_INCLUDE_DIRECTORIES - set_target_properties(libnfs::nfs PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${LIBNFS_INCLUDE_DIR}) + set_target_properties(${_nfs_target} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${LIBNFS_INCLUDE_DIR}) + + if(TARGET libnfs) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} libnfs) + endif() # Add internal build target when a Multi Config Generator is used # We cant add a dependency based off a generator expression for targeted build types, @@ -177,7 +188,5 @@ if(NOT TARGET libnfs::nfs) endif() add_dependencies(build_internal_depends libnfs) endif() - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP libnfs::nfs) endif() endif() diff --git a/xbmc/filesystem/CMakeLists.txt b/xbmc/filesystem/CMakeLists.txt index 8d2336a347156..9d653ae0f9cbe 100644 --- a/xbmc/filesystem/CMakeLists.txt +++ b/xbmc/filesystem/CMakeLists.txt @@ -158,7 +158,7 @@ if(ENABLE_OPTICAL) DVDDirectory.h) endif() -if(TARGET libnfs::nfs) +if(TARGET ${APP_NAME_LC}::NFS) list(APPEND SOURCES NFSDirectory.cpp NFSFile.cpp) list(APPEND HEADERS NFSDirectory.h diff --git a/xbmc/filesystem/test/CMakeLists.txt b/xbmc/filesystem/test/CMakeLists.txt index 0020050598e8b..5d2f0344c7a6a 100644 --- a/xbmc/filesystem/test/CMakeLists.txt +++ b/xbmc/filesystem/test/CMakeLists.txt @@ -8,7 +8,7 @@ if(MICROHTTPD_FOUND) list(APPEND SOURCES TestHTTPDirectory.cpp) endif() -if(TARGET libnfs::nfs) +if(TARGET ${APP_NAME_LC}::NFS) list(APPEND SOURCES TestNfsFile.cpp) endif() From 33b4f017cf8e18e6704e2ccd85e59fdd51f09058 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 12:55:51 +1000 Subject: [PATCH 089/651] [cmake][modules] FindOpenGl cleanup and use core_target_link_libraries --- cmake/modules/FindOpenGl.cmake | 21 ++++++++----------- xbmc/cores/RetroPlayer/buffers/CMakeLists.txt | 4 ++-- .../rendering/VideoRenderers/CMakeLists.txt | 4 ++-- .../VideoPlayer/VideoRenderers/CMakeLists.txt | 4 ++-- .../VideoRenderers/HwDecRender/CMakeLists.txt | 4 ++-- .../VideoShaders/CMakeLists.txt | 4 ++-- xbmc/guilib/CMakeLists.txt | 4 ++-- xbmc/pictures/CMakeLists.txt | 2 +- xbmc/rendering/CMakeLists.txt | 2 +- xbmc/rendering/gl/CMakeLists.txt | 2 +- xbmc/utils/CMakeLists.txt | 2 +- xbmc/video/dialogs/CMakeLists.txt | 2 +- xbmc/windowing/X11/CMakeLists.txt | 2 +- xbmc/windowing/gbm/CMakeLists.txt | 2 +- xbmc/windowing/osx/OpenGL/CMakeLists.txt | 2 +- xbmc/windowing/wayland/CMakeLists.txt | 2 +- 16 files changed, 30 insertions(+), 33 deletions(-) diff --git a/cmake/modules/FindOpenGl.cmake b/cmake/modules/FindOpenGl.cmake index 1af7469e1802b..57cae1597c171 100644 --- a/cmake/modules/FindOpenGl.cmake +++ b/cmake/modules/FindOpenGl.cmake @@ -5,9 +5,9 @@ # # This will define the following target: # -# OpenGL::GL - The OpenGL library +# ${APP_NAME_LC}::OpenGl - The OpenGL library -if(NOT TARGET OpenGL::GL) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(PC_OPENGL gl QUIET) @@ -15,22 +15,19 @@ if(NOT TARGET OpenGL::GL) find_library(OPENGL_gl_LIBRARY NAMES GL OpenGL HINTS ${PC_OPENGL_gl_LIBDIR} ${CMAKE_OSX_SYSROOT}/System/Library - PATH_SUFFIXES Frameworks - NO_CACHE) + PATH_SUFFIXES Frameworks) find_path(OPENGL_INCLUDE_DIR NAMES GL/gl.h gl.h - HINTS ${PC_OPENGL_gl_INCLUDEDIR} ${OPENGL_gl_LIBRARY}/Headers - NO_CACHE) + HINTS ${PC_OPENGL_gl_INCLUDEDIR} ${OPENGL_gl_LIBRARY}/Headers) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(OpenGl REQUIRED_VARS OPENGL_gl_LIBRARY OPENGL_INCLUDE_DIR) if(OPENGL_FOUND) - add_library(OpenGL::GL UNKNOWN IMPORTED) - set_target_properties(OpenGL::GL PROPERTIES - IMPORTED_LOCATION "${OPENGL_gl_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${OPENGL_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_GL=1) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP OpenGL::GL) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${OPENGL_gl_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${OPENGL_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_GL) endif() endif() diff --git a/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt b/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt index ef66479c46b74..871af8a8977ab 100644 --- a/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt +++ b/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt @@ -10,14 +10,14 @@ set(HEADERS BaseRenderBuffer.h RenderBufferManager.h ) -if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) list(APPEND SOURCES RenderBufferOpenGLES.cpp RenderBufferPoolOpenGLES.cpp) list(APPEND HEADERS RenderBufferOpenGLES.h RenderBufferPoolOpenGLES.h) endif() -if(TARGET OpenGL::GL) +if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES RenderBufferOpenGL.cpp RenderBufferPoolOpenGL.cpp) list(APPEND HEADERS RenderBufferOpenGL.h diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt index 5680b8ec9e190..d371b9fab3a6a 100644 --- a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt +++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt @@ -8,12 +8,12 @@ if(CORE_SYSTEM_NAME STREQUAL windows) list(APPEND HEADERS RPWinRenderer.h) endif() -if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) list(APPEND SOURCES RPRendererOpenGLES.cpp) list(APPEND HEADERS RPRendererOpenGLES.h) endif() -if(TARGET OpenGL::GL) +if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES RPRendererOpenGL.cpp) list(APPEND HEADERS RPRendererOpenGL.h) endif() diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt index bc367887afe3c..279693766fac5 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt @@ -29,12 +29,12 @@ if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) RenderCapture.h) endif() -if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) list(APPEND SOURCES FrameBufferObject.cpp) list(APPEND HEADERS FrameBufferObject.h) endif() -if(TARGET OpenGL::GL) +if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES LinuxRendererGL.cpp OverlayRendererGL.cpp RenderCaptureGL.cpp) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt index bd4d6333eea08..750b7a0bd97d6 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt @@ -6,7 +6,7 @@ if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) endif() if(VAAPI_FOUND) - if(TARGET OpenGL::GL) + if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES RendererVAAPIGL.cpp) list(APPEND HEADERS RendererVAAPIGL.h) endif() @@ -28,7 +28,7 @@ if(TARGET VDPAU::VDPAU) endif() if(CORE_SYSTEM_NAME STREQUAL osx) - if(TARGET OpenGL::GL) + if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES RendererVTBGL.cpp) list(APPEND HEADERS RendererVTBGL.h) endif() diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt index 5bb02199e76b7..2b1940927aa90 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt @@ -14,14 +14,14 @@ if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) endif() -if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) list(APPEND SOURCES ConversionMatrix.cpp ToneMappers.cpp) list(APPEND HEADERS ConversionMatrix.h ToneMappers.h) endif() -if(TARGET OpenGL::GL) +if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES GLSLOutput.cpp VideoFilterShaderGL.cpp YUV2RGBShaderGL.cpp) diff --git a/xbmc/guilib/CMakeLists.txt b/xbmc/guilib/CMakeLists.txt index d179ee1dcc3e8..78ed07b6cf1dd 100644 --- a/xbmc/guilib/CMakeLists.txt +++ b/xbmc/guilib/CMakeLists.txt @@ -159,11 +159,11 @@ set(HEADERS DDSImage.h XBTF.h XBTFReader.h) -if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) list(APPEND SOURCES Shader.cpp) list(APPEND HEADERS Shader.h) - if(TARGET OpenGL::GL) + if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES GUIFontTTFGL.cpp GUITextureGL.cpp TextureGL.cpp) diff --git a/xbmc/pictures/CMakeLists.txt b/xbmc/pictures/CMakeLists.txt index ee73acad6ccf4..678ba70960a73 100644 --- a/xbmc/pictures/CMakeLists.txt +++ b/xbmc/pictures/CMakeLists.txt @@ -29,7 +29,7 @@ set(HEADERS interfaces/ISlideShowDelegate.h SlideShowDelegator.h SlideShowPicture.h) -if(TARGET OpenGL::GL) +if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES SlideShowPictureGL.cpp) list(APPEND HEADERS SlideShowPictureGL.h) endif() diff --git a/xbmc/rendering/CMakeLists.txt b/xbmc/rendering/CMakeLists.txt index 9daaf0c726254..b128fe51a700a 100644 --- a/xbmc/rendering/CMakeLists.txt +++ b/xbmc/rendering/CMakeLists.txt @@ -3,7 +3,7 @@ set(SOURCES RenderSystem.cpp) set(HEADERS RenderSystem.h RenderSystemTypes.h) -if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) list(APPEND SOURCES MatrixGL.cpp) list(APPEND HEADERS MatrixGL.h) diff --git a/xbmc/rendering/gl/CMakeLists.txt b/xbmc/rendering/gl/CMakeLists.txt index 71c1ee8d322a6..a6a5b905756e1 100644 --- a/xbmc/rendering/gl/CMakeLists.txt +++ b/xbmc/rendering/gl/CMakeLists.txt @@ -1,4 +1,4 @@ -if(TARGET OpenGL::GL) +if(TARGET ${APP_NAME_LC}::OpenGl) set(SOURCES RenderSystemGL.cpp ScreenshotSurfaceGL.cpp GLShader.cpp) diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index d486bcd976000..a4de874e0ce53 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -199,7 +199,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL Clang) set_source_files_properties(Mime.cpp PROPERTIES COMPILE_FLAGS -O0) endif() -if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) list(APPEND SOURCES GLUtils.cpp) list(APPEND HEADERS GLUtils.h) endif() diff --git a/xbmc/video/dialogs/CMakeLists.txt b/xbmc/video/dialogs/CMakeLists.txt index 15f04c90742ed..c37b369d70fbe 100644 --- a/xbmc/video/dialogs/CMakeLists.txt +++ b/xbmc/video/dialogs/CMakeLists.txt @@ -24,7 +24,7 @@ set(HEADERS GUIDialogAudioSettings.h GUIDialogVideoOSD.h GUIDialogVideoSettings.h) -if(TARGET OpenGL::GL OR CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) +if(TARGET ${APP_NAME_LC}::OpenGl OR CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) list(APPEND SOURCES GUIDialogCMSSettings.cpp) list(APPEND HEADERS GUIDialogCMSSettings.h) endif() diff --git a/xbmc/windowing/X11/CMakeLists.txt b/xbmc/windowing/X11/CMakeLists.txt index 192d8225ace15..33cc4d7720ba6 100644 --- a/xbmc/windowing/X11/CMakeLists.txt +++ b/xbmc/windowing/X11/CMakeLists.txt @@ -23,7 +23,7 @@ if(GLX_FOUND) VideoSyncGLX.h) endif() -if(TARGET OpenGL::GL) +if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES WinSystemX11GLContext.cpp) list(APPEND HEADERS WinSystemX11GLContext.h) list(APPEND SOURCES VideoSyncOML.cpp) diff --git a/xbmc/windowing/gbm/CMakeLists.txt b/xbmc/windowing/gbm/CMakeLists.txt index 91085f8b7f6a5..84cea44524771 100644 --- a/xbmc/windowing/gbm/CMakeLists.txt +++ b/xbmc/windowing/gbm/CMakeLists.txt @@ -14,7 +14,7 @@ set(HEADERS OptionalsReg.h WinSystemGbmEGLContext.h GBMDPMSSupport.h) -if(TARGET OpenGL::GL) +if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES WinSystemGbmGLContext.cpp) list(APPEND HEADERS WinSystemGbmGLContext.h) endif() diff --git a/xbmc/windowing/osx/OpenGL/CMakeLists.txt b/xbmc/windowing/osx/OpenGL/CMakeLists.txt index 61dc24867f114..42600bf6b2c19 100644 --- a/xbmc/windowing/osx/OpenGL/CMakeLists.txt +++ b/xbmc/windowing/osx/OpenGL/CMakeLists.txt @@ -1,4 +1,4 @@ -if(TARGET OpenGL::GL) +if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES OSXGLView.mm WindowControllerMacOS.mm WinSystemOSXGL.mm) diff --git a/xbmc/windowing/wayland/CMakeLists.txt b/xbmc/windowing/wayland/CMakeLists.txt index b1ff8a236f7cb..0cfbdee80af43 100644 --- a/xbmc/windowing/wayland/CMakeLists.txt +++ b/xbmc/windowing/wayland/CMakeLists.txt @@ -54,7 +54,7 @@ if(TARGET ${APP_NAME_LC}::EGL) list(APPEND HEADERS WinSystemWaylandEGLContext.h) endif() -if(TARGET OpenGL::GL) +if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES WinSystemWaylandEGLContextGL.cpp) list(APPEND HEADERS WinSystemWaylandEGLContextGL.h) endif() From 150e8d0c53c47b40d7f51e5e18a38e4e957f4c80 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 12:59:26 +1000 Subject: [PATCH 090/651] [cmake][modules] FindOpenGLES cleanup and use core_target_link_libraries --- cmake/modules/FindOpenGLES.cmake | 39 ++++++++----------- xbmc/cores/RetroPlayer/buffers/CMakeLists.txt | 2 +- .../rendering/VideoRenderers/CMakeLists.txt | 2 +- .../VideoPlayer/VideoRenderers/CMakeLists.txt | 4 +- .../VideoRenderers/HwDecRender/CMakeLists.txt | 6 +-- .../VideoShaders/CMakeLists.txt | 4 +- xbmc/guilib/CMakeLists.txt | 4 +- xbmc/pictures/CMakeLists.txt | 2 +- xbmc/rendering/CMakeLists.txt | 2 +- xbmc/rendering/gles/CMakeLists.txt | 2 +- xbmc/utils/CMakeLists.txt | 2 +- xbmc/windowing/X11/CMakeLists.txt | 2 +- xbmc/windowing/android/CMakeLists.txt | 2 +- xbmc/windowing/gbm/CMakeLists.txt | 2 +- xbmc/windowing/wayland/CMakeLists.txt | 2 +- 15 files changed, 36 insertions(+), 41 deletions(-) diff --git a/cmake/modules/FindOpenGLES.cmake b/cmake/modules/FindOpenGLES.cmake index 92172bd6282c4..c2c23cc97582f 100644 --- a/cmake/modules/FindOpenGLES.cmake +++ b/cmake/modules/FindOpenGLES.cmake @@ -5,9 +5,9 @@ # # This will define the following target: # -# OpenGL::GLES - The OpenGLES IMPORTED library +# ${APP_NAME_LC}::OpenGLES - The OpenGLES IMPORTED library -if(NOT TARGET OpenGL::GLES) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(PC_OPENGLES glesv2 QUIET) @@ -15,14 +15,11 @@ if(NOT TARGET OpenGL::GLES) find_library(OPENGLES_gl_LIBRARY NAMES GLESv2 OpenGLES HINTS ${PC_OPENGLES_LIBDIR} ${CMAKE_OSX_SYSROOT}/System/Library - PATH_SUFFIXES Frameworks - NO_CACHE) + PATH_SUFFIXES Frameworks) find_path(OPENGLES_INCLUDE_DIR NAMES GLES2/gl2.h ES2/gl.h - HINTS ${PC_OPENGLES_INCLUDEDIR} ${OPENGLES_gl_LIBRARY}/Headers - NO_CACHE) + HINTS ${PC_OPENGLES_INCLUDEDIR} ${OPENGLES_gl_LIBRARY}/Headers) find_path(OPENGLES3_INCLUDE_DIR NAMES GLES3/gl3.h ES3/gl.h - HINTS ${PC_OPENGLES_INCLUDEDIR} ${OPENGLES_gl_LIBRARY}/Headers - NO_CACHE) + HINTS ${PC_OPENGLES_INCLUDEDIR} ${OPENGLES_gl_LIBRARY}/Headers) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(OpenGLES @@ -30,26 +27,24 @@ if(NOT TARGET OpenGL::GLES) if(OPENGLES_FOUND) if(${OPENGLES_gl_LIBRARY} MATCHES ".+\.so$") - add_library(OpenGL::GLES SHARED IMPORTED) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} SHARED IMPORTED) else() - add_library(OpenGL::GLES UNKNOWN IMPORTED) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) endif() - set_target_properties(OpenGL::GLES PROPERTIES - IMPORTED_LOCATION "${OPENGLES_gl_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${OPENGLES_INCLUDE_DIR}" - IMPORTED_NO_SONAME TRUE) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${OPENGLES_gl_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${OPENGLES_INCLUDE_DIR}" + IMPORTED_NO_SONAME TRUE) if(OPENGLES3_INCLUDE_DIR) - set_property(TARGET OpenGL::GLES APPEND PROPERTY - INTERFACE_INCLUDE_DIRECTORIES "${OPENGLES3_INCLUDE_DIR}") - set_target_properties(OpenGL::GLES PROPERTIES - INTERFACE_COMPILE_DEFINITIONS HAS_GLES=3) + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${OPENGLES3_INCLUDE_DIR}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_COMPILE_DEFINITIONS HAS_GLES=3) else() - set_target_properties(OpenGL::GLES PROPERTIES - INTERFACE_COMPILE_DEFINITIONS HAS_GLES=2) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_COMPILE_DEFINITIONS HAS_GLES=2) endif() - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP OpenGL::GLES) endif() endif() diff --git a/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt b/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt index 871af8a8977ab..4c4b3c025315b 100644 --- a/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt +++ b/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt @@ -10,7 +10,7 @@ set(HEADERS BaseRenderBuffer.h RenderBufferManager.h ) -if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES RenderBufferOpenGLES.cpp RenderBufferPoolOpenGLES.cpp) list(APPEND HEADERS RenderBufferOpenGLES.h diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt index d371b9fab3a6a..23d3307351a68 100644 --- a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt +++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt @@ -8,7 +8,7 @@ if(CORE_SYSTEM_NAME STREQUAL windows) list(APPEND HEADERS RPWinRenderer.h) endif() -if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES RPRendererOpenGLES.cpp) list(APPEND HEADERS RPRendererOpenGLES.h) endif() diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt index 279693766fac5..5467be87ef086 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt @@ -29,7 +29,7 @@ if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) RenderCapture.h) endif() -if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES FrameBufferObject.cpp) list(APPEND HEADERS FrameBufferObject.h) endif() @@ -43,7 +43,7 @@ if(TARGET ${APP_NAME_LC}::OpenGl) RenderCaptureGL.h) endif() -if(TARGET OpenGL::GLES AND ("android" IN_LIST CORE_PLATFORM_NAME_LC OR +if(TARGET ${APP_NAME_LC}::OpenGLES AND ("android" IN_LIST CORE_PLATFORM_NAME_LC OR "ios" IN_LIST CORE_PLATFORM_NAME_LC OR "tvos" IN_LIST CORE_PLATFORM_NAME_LC OR "gbm" IN_LIST CORE_PLATFORM_NAME_LC OR diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt index 750b7a0bd97d6..ac93f18d314a3 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt @@ -10,7 +10,7 @@ if(VAAPI_FOUND) list(APPEND SOURCES RendererVAAPIGL.cpp) list(APPEND HEADERS RendererVAAPIGL.h) endif() - if(TARGET OpenGL::GLES) + if(TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES RendererVAAPIGLES.cpp) list(APPEND HEADERS RendererVAAPIGLES.h) endif() @@ -35,7 +35,7 @@ if(CORE_SYSTEM_NAME STREQUAL osx) endif() if(CORE_SYSTEM_NAME STREQUAL darwin_embedded) - if(TARGET OpenGL::GLES) + if(TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES RendererVTBGLES.cpp) list(APPEND HEADERS RendererVTBGLES.h) endif() @@ -56,7 +56,7 @@ if("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_L VideoLayerBridgeDRMPRIME.h) endif() - if(TARGET OpenGL::GLES) + if(TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES RendererDRMPRIMEGLES.cpp DRMPRIMEEGL.cpp) list(APPEND HEADERS RendererDRMPRIMEGLES.h diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt index 2b1940927aa90..4cf1269214d14 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt @@ -14,7 +14,7 @@ if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) endif() -if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES ConversionMatrix.cpp ToneMappers.cpp) list(APPEND HEADERS ConversionMatrix.h @@ -30,7 +30,7 @@ if(TARGET ${APP_NAME_LC}::OpenGl) YUV2RGBShaderGL.h) endif() -if(TARGET OpenGL::GLES AND ("android" IN_LIST CORE_PLATFORM_NAME_LC OR +if(TARGET ${APP_NAME_LC}::OpenGLES AND ("android" IN_LIST CORE_PLATFORM_NAME_LC OR "ios" IN_LIST CORE_PLATFORM_NAME_LC OR "tvos" IN_LIST CORE_PLATFORM_NAME_LC OR "gbm" IN_LIST CORE_PLATFORM_NAME_LC OR diff --git a/xbmc/guilib/CMakeLists.txt b/xbmc/guilib/CMakeLists.txt index 78ed07b6cf1dd..ab0b9776460dd 100644 --- a/xbmc/guilib/CMakeLists.txt +++ b/xbmc/guilib/CMakeLists.txt @@ -159,7 +159,7 @@ set(HEADERS DDSImage.h XBTF.h XBTFReader.h) -if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES Shader.cpp) list(APPEND HEADERS Shader.h) @@ -172,7 +172,7 @@ if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) TextureGL.h) endif() - if(TARGET OpenGL::GLES) + if(TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES GUIFontTTFGLES.cpp GUITextureGLES.cpp TextureGLES.cpp) diff --git a/xbmc/pictures/CMakeLists.txt b/xbmc/pictures/CMakeLists.txt index 678ba70960a73..9253be8f6d6f8 100644 --- a/xbmc/pictures/CMakeLists.txt +++ b/xbmc/pictures/CMakeLists.txt @@ -34,7 +34,7 @@ if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND HEADERS SlideShowPictureGL.h) endif() -if(TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES SlideShowPictureGLES.cpp) list(APPEND HEADERS SlideShowPictureGLES.h) endif() diff --git a/xbmc/rendering/CMakeLists.txt b/xbmc/rendering/CMakeLists.txt index b128fe51a700a..94ab0720399d1 100644 --- a/xbmc/rendering/CMakeLists.txt +++ b/xbmc/rendering/CMakeLists.txt @@ -3,7 +3,7 @@ set(SOURCES RenderSystem.cpp) set(HEADERS RenderSystem.h RenderSystemTypes.h) -if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES MatrixGL.cpp) list(APPEND HEADERS MatrixGL.h) diff --git a/xbmc/rendering/gles/CMakeLists.txt b/xbmc/rendering/gles/CMakeLists.txt index 9276974875c1f..94bef8e8ad410 100644 --- a/xbmc/rendering/gles/CMakeLists.txt +++ b/xbmc/rendering/gles/CMakeLists.txt @@ -1,4 +1,4 @@ -if(TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGLES) set(SOURCES RenderSystemGLES.cpp ScreenshotSurfaceGLES.cpp GLESShader.cpp) diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index a4de874e0ce53..e0f70aa08b2e8 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -199,7 +199,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL Clang) set_source_files_properties(Mime.cpp PROPERTIES COMPILE_FLAGS -O0) endif() -if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGl OR TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES GLUtils.cpp) list(APPEND HEADERS GLUtils.h) endif() diff --git a/xbmc/windowing/X11/CMakeLists.txt b/xbmc/windowing/X11/CMakeLists.txt index 33cc4d7720ba6..b96ff8271aa54 100644 --- a/xbmc/windowing/X11/CMakeLists.txt +++ b/xbmc/windowing/X11/CMakeLists.txt @@ -29,7 +29,7 @@ if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES VideoSyncOML.cpp) list(APPEND HEADERS VideoSyncOML.h) endif() -if(TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES WinSystemX11GLESContext.cpp) list(APPEND HEADERS WinSystemX11GLESContext.h) endif() diff --git a/xbmc/windowing/android/CMakeLists.txt b/xbmc/windowing/android/CMakeLists.txt index c9462c0419dd7..279d834bcd1c2 100644 --- a/xbmc/windowing/android/CMakeLists.txt +++ b/xbmc/windowing/android/CMakeLists.txt @@ -10,7 +10,7 @@ set(HEADERS OSScreenSaverAndroid.h AndroidUtils.h VideoSyncAndroid.h) -if(TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES WinSystemAndroidGLESContext.cpp) list(APPEND HEADERS WinSystemAndroidGLESContext.h) endif() diff --git a/xbmc/windowing/gbm/CMakeLists.txt b/xbmc/windowing/gbm/CMakeLists.txt index 84cea44524771..4bf4d12262fa1 100644 --- a/xbmc/windowing/gbm/CMakeLists.txt +++ b/xbmc/windowing/gbm/CMakeLists.txt @@ -18,7 +18,7 @@ if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES WinSystemGbmGLContext.cpp) list(APPEND HEADERS WinSystemGbmGLContext.h) endif() -if(TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES WinSystemGbmGLESContext.cpp) list(APPEND HEADERS WinSystemGbmGLESContext.h) endif() diff --git a/xbmc/windowing/wayland/CMakeLists.txt b/xbmc/windowing/wayland/CMakeLists.txt index 0cfbdee80af43..5e14018453f78 100644 --- a/xbmc/windowing/wayland/CMakeLists.txt +++ b/xbmc/windowing/wayland/CMakeLists.txt @@ -58,7 +58,7 @@ if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES WinSystemWaylandEGLContextGL.cpp) list(APPEND HEADERS WinSystemWaylandEGLContextGL.h) endif() -if(TARGET OpenGL::GLES) +if(TARGET ${APP_NAME_LC}::OpenGLES) list(APPEND SOURCES WinSystemWaylandEGLContextGLES.cpp) list(APPEND HEADERS WinSystemWaylandEGLContextGLES.h) endif() From cc98095621cfba860f891a3b24c7e946a1d66155 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 13:06:24 +1000 Subject: [PATCH 091/651] [cmake][modules] FindP8Platform cleanup and use core_target_link_libraries --- cmake/modules/FindCEC.cmake | 4 ++-- cmake/modules/FindP8Platform.cmake | 28 +++++++++++++--------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/cmake/modules/FindCEC.cmake b/cmake/modules/FindCEC.cmake index b15c8e4b1483f..aa2ab9ead7ca6 100644 --- a/cmake/modules/FindCEC.cmake +++ b/cmake/modules/FindCEC.cmake @@ -42,7 +42,7 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) COMMAND ${INSTALL_NAME_TOOL} -id ${CEC_LIBRARY} ${CEC_LIBRARY}) endif() - add_dependencies(cec P8Platform::P8Platform) + add_dependencies(cec ${APP_NAME_LC}::P8Platform) endmacro() # We only need to check p8-platform if we have any intention to build internal @@ -51,7 +51,7 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) get_libversion_data("p8-platform" "target") find_package(P8Platform ${LIB_P8-PLATFORM_VER} MODULE QUIET REQUIRED) # Check if we want to force a build due to a dependency rebuild - get_property(LIB_FORCE_REBUILD TARGET P8Platform::P8Platform PROPERTY LIB_BUILD) + get_property(LIB_FORCE_REBUILD TARGET ${APP_NAME_LC}::P8Platform PROPERTY LIB_BUILD) endif() set(MODULE_LC cec) diff --git a/cmake/modules/FindP8Platform.cmake b/cmake/modules/FindP8Platform.cmake index b0875357c4053..c2d89703d42ea 100644 --- a/cmake/modules/FindP8Platform.cmake +++ b/cmake/modules/FindP8Platform.cmake @@ -4,11 +4,11 @@ # # This will define the following target: # -# P8Platform::P8Platform - The P8-Platform library +# ${APP_NAME_LC}::P8Platform - The P8-Platform library # If find_package REQUIRED, check again to make sure any potential versions # supplied in the call match what we can find/build -if(NOT P8Platform::P8Platform OR P8Platform_FIND_REQUIRED) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} OR P8Platform_FIND_REQUIRED) include(cmake/scripts/common/ModuleHelpers.cmake) macro(buildlibp8platform) @@ -60,12 +60,10 @@ if(NOT P8Platform::P8Platform OR P8Platform_FIND_REQUIRED) set(P8Platform_FIND_VERSION "2.1") find_library(P8-PLATFORM_LIBRARY NAMES p8-platform HINTS ${DEPENDS_PATH}/lib ${PC_P8PLATFORM_LIBDIR} - ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} - NO_CACHE) + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) find_path(P8-PLATFORM_INCLUDE_DIR NAMES p8-platform/os.h HINTS ${DEPENDS_PATH}/include ${PC_P8PLATFORM_INCLUDEDIR} - ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} - NO_CACHE) + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) endif() include(FindPackageHandleStandardArgs) @@ -74,21 +72,21 @@ if(NOT P8Platform::P8Platform OR P8Platform_FIND_REQUIRED) VERSION_VAR P8-PLATFORM_VERSION) if(P8PLATFORM_FOUND) - add_library(P8Platform::P8Platform UNKNOWN IMPORTED) - set_target_properties(P8Platform::P8Platform PROPERTIES - IMPORTED_LOCATION "${P8-PLATFORM_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${P8-PLATFORM_INCLUDE_DIR}") + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${P8-PLATFORM_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${P8-PLATFORM_INCLUDE_DIR}") if(CMAKE_SYSTEM_NAME STREQUAL Darwin) - set_target_properties(P8Platform::P8Platform PROPERTIES - INTERFACE_LINK_LIBRARIES "-framework CoreVideo") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_LINK_LIBRARIES "-framework CoreVideo") endif() if(TARGET build-p8-platform) - add_dependencies(P8Platform::P8Platform build-p8-platform) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} build-p8-platform) # If the build target exists here, set LIB_BUILD property to allow calling modules # know that this will be rebuilt, and they will need to rebuild as well - set_target_properties(P8Platform::P8Platform PROPERTIES LIB_BUILD ON) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES LIB_BUILD ON) endif() # Add internal build target when a Multi Config Generator is used @@ -107,7 +105,7 @@ if(NOT P8Platform::P8Platform OR P8Platform_FIND_REQUIRED) add_dependencies(build_internal_depends build-p8-platform) endif() else() - if(P8PLATFORM_FIND_REQUIRED) + if(P8Platform_FIND_REQUIRED) message(FATAL_ERROR "P8-PLATFORM not found.") endif() endif() From 74427b2686f1e734372bd1f64be5934bd9748bc0 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 13:31:32 +1000 Subject: [PATCH 092/651] [cmake][modules] FindPlayerAPIs cleanup and use core_target_link_libraries --- cmake/modules/FindPlayerAPIs.cmake | 40 +++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/cmake/modules/FindPlayerAPIs.cmake b/cmake/modules/FindPlayerAPIs.cmake index 00b497ffcf2e9..cc2e11ccc0637 100644 --- a/cmake/modules/FindPlayerAPIs.cmake +++ b/cmake/modules/FindPlayerAPIs.cmake @@ -5,29 +5,26 @@ # # This will define the following target: # -# PLAYERAPIS::PLAYERAPIS - The playerAPIs library - -if(NOT TARGET PLAYERAPIS::PLAYERAPIS) - - if(PlayerAPIs_FIND_VERSION) - if(PlayerAPIs_FIND_VERSION_EXACT) - set(PlayerAPIs_FIND_SPEC "=${PlayerAPIs_FIND_VERSION_COMPLETE}") - else() - set(PlayerAPIs_FIND_SPEC ">=${PlayerAPIs_FIND_VERSION_COMPLETE}") - endif() - endif() +# ${APP_NAME_LC}::PlayerAPIs - The playerAPIs library +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) if(PKG_CONFIG_FOUND) + if(PlayerAPIs_FIND_VERSION) + if(PlayerAPIs_FIND_VERSION_EXACT) + set(PlayerAPIs_FIND_SPEC "=${PlayerAPIs_FIND_VERSION_COMPLETE}") + else() + set(PlayerAPIs_FIND_SPEC ">=${PlayerAPIs_FIND_VERSION_COMPLETE}") + endif() + endif() + pkg_check_modules(PC_PLAYERAPIS libplayerAPIs${PlayerAPIs_FIND_SPEC} QUIET) endif() find_path(PLAYERAPIS_INCLUDE_DIR NAMES starfish-media-pipeline/StarfishMediaAPIs.h - HINTS ${PC_PLAYERAPIS_INCLUDEDIR} - NO_CACHE) + HINTS ${PC_PLAYERAPIS_INCLUDEDIR}) find_library(PLAYERAPIS_LIBRARY NAMES playerAPIs - HINTS ${PC_PLAYERAPIS_LIBDIR} - NO_CACHE) + HINTS ${PC_PLAYERAPIS_LIBDIR}) set(PLAYERAPIS_VERSION ${PC_PLAYERAPIS_VERSION}) @@ -37,10 +34,13 @@ if(NOT TARGET PLAYERAPIS::PLAYERAPIS) VERSION_VAR PLAYERAPIS_VERSION) if(PLAYERAPIS_FOUND) - add_library(PLAYERAPIS::PLAYERAPIS UNKNOWN IMPORTED) - set_target_properties(PLAYERAPIS::PLAYERAPIS PROPERTIES - IMPORTED_LOCATION "${PLAYERAPIS_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${PLAYERAPIS_INCLUDE_DIR}") - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP PLAYERAPIS::PLAYERAPIS) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${PLAYERAPIS_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${PLAYERAPIS_INCLUDE_DIR}") + else() + if(PlayerAPIs_FIND_REQUIRED) + message(FATAL_ERROR "PlayerAPIs library not found.") + endif() endif() endif() From 2511b00b330e93cf18f66a5ab86e365de59fdf31 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 13:33:09 +1000 Subject: [PATCH 093/651] [cmake][modules] FindPlayerFactory cleanup and use core_target_link_libraries --- cmake/modules/FindPlayerFactory.cmake | 40 +++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/cmake/modules/FindPlayerFactory.cmake b/cmake/modules/FindPlayerFactory.cmake index 40944e5730c7b..3ddf13a70bbe7 100644 --- a/cmake/modules/FindPlayerFactory.cmake +++ b/cmake/modules/FindPlayerFactory.cmake @@ -5,29 +5,26 @@ # # This will define the following target: # -# PLAYERFACTORY::PLAYERFACTORY - The PlayerFactory library - -if(NOT TARGET PLAYERFACTORY::PLAYERFACTORY) - - if(PlayerFactory_FIND_VERSION) - if(PlayerFactory_FIND_VERSION_EXACT) - set(PlayerFactory_FIND_SPEC "=${PlayerFactory_FIND_VERSION_COMPLETE}") - else() - set(PlayerFactory_FIND_SPEC ">=${PlayerFactory_FIND_VERSION_COMPLETE}") - endif() - endif() +# ${APP_NAME_LC}::PlayerFactory - The PlayerFactory library +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) if(PKG_CONFIG_FOUND) + if(PlayerFactory_FIND_VERSION) + if(PlayerFactory_FIND_VERSION_EXACT) + set(PlayerFactory_FIND_SPEC "=${PlayerFactory_FIND_VERSION_COMPLETE}") + else() + set(PlayerFactory_FIND_SPEC ">=${PlayerFactory_FIND_VERSION_COMPLETE}") + endif() + endif() + pkg_check_modules(PC_PLAYERFACTORY libpf-1.0${PlayerFactory_FIND_SPEC} QUIET) endif() find_path(PLAYERFACTORY_INCLUDE_DIR NAMES player-factory/common.hpp - HINTS ${PC_PLAYERFACTORY_INCLUDEDIR} - NO_CACHE) + HINTS ${PC_PLAYERFACTORY_INCLUDEDIR}) find_library(PLAYERFACTORY_LIBRARY NAMES pf-1.0 - HINTS ${PC_PLAYERFACTORY_LIBDIR} - NO_CACHE) + HINTS ${PC_PLAYERFACTORY_LIBDIR}) set(PLAYERFACTORY_VERSION ${PC_PLAYERFACTORY_VERSION}) @@ -37,10 +34,13 @@ if(NOT TARGET PLAYERFACTORY::PLAYERFACTORY) VERSION_VAR PLAYERFACTORY_VERSION) if(PLAYERFACTORY_FOUND) - add_library(PLAYERFACTORY::PLAYERFACTORY UNKNOWN IMPORTED) - set_target_properties(PLAYERFACTORY::PLAYERFACTORY PROPERTIES - IMPORTED_LOCATION "${PLAYERFACTORY_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${PLAYERFACTORY_INCLUDE_DIR}") - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP PLAYERFACTORY::PLAYERFACTORY) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${PLAYERFACTORY_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${PLAYERFACTORY_INCLUDE_DIR}") + else() + if(PlayerFactory_FIND_REQUIRED) + message(FATAL_ERROR "PlayerFactory library not found.") + endif() endif() endif() From 7236765e06325461589584e5b7e074681224a76e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 13:36:10 +1000 Subject: [PATCH 094/651] [cmake][modules] FindPlist cleanup and use core_target_link_libraries --- cmake/modules/FindPlist.cmake | 28 +++++++++++++--------------- xbmc/network/CMakeLists.txt | 2 +- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/cmake/modules/FindPlist.cmake b/cmake/modules/FindPlist.cmake index 194ee305e7d53..4ba046de1ce5c 100644 --- a/cmake/modules/FindPlist.cmake +++ b/cmake/modules/FindPlist.cmake @@ -5,35 +5,33 @@ # # This will define the following target: # -# Plist::Plist - The Plist library +# ${APP_NAME_LC}::Plist - The Plist library -if(NOT TARGET Plist::Plist) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) - if(PKG_CONFIG_FOUND) + if(PKG_CONFIG_FOUND AND NOT (WIN32 OR WINDOWS_STORE)) pkg_search_module(PC_PLIST libplist-2.0 libplist QUIET) endif() find_path(PLIST_INCLUDE_DIR plist/plist.h - HINTS ${PC_PLIST_INCLUDEDIR} - NO_CACHE) + HINTS ${DEPENDS_PATH}/include ${PC_PLIST_INCLUDEDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) + find_library(PLIST_LIBRARY NAMES plist-2.0 plist libplist-2.0 libplist + HINTS ${DEPENDS_PATH}/lib ${PC_PLIST_LIBDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) set(PLIST_VERSION ${PC_PLIST_VERSION}) - find_library(PLIST_LIBRARY NAMES plist-2.0 plist libplist-2.0 libplist - HINTS ${PC_PLIST_LIBDIR} - NO_CACHE) - include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Plist REQUIRED_VARS PLIST_LIBRARY PLIST_INCLUDE_DIR VERSION_VAR PLIST_VERSION) if(PLIST_FOUND) - add_library(Plist::Plist UNKNOWN IMPORTED) - set_target_properties(Plist::Plist PROPERTIES - IMPORTED_LOCATION "${PLIST_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${PLIST_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_AIRPLAY=1) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP Plist::Plist) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${PLIST_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${PLIST_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_AIRPLAY) endif() endif() diff --git a/xbmc/network/CMakeLists.txt b/xbmc/network/CMakeLists.txt index 20606057613b2..7d7a635a5d21e 100644 --- a/xbmc/network/CMakeLists.txt +++ b/xbmc/network/CMakeLists.txt @@ -33,7 +33,7 @@ if(ENABLE_OPTICAL) list(APPEND HEADERS cddb.h) endif() -if(TARGET Plist::Plist) +if(TARGET ${APP_NAME_LC}::Plist) list(APPEND SOURCES AirPlayServer.cpp) list(APPEND HEADERS AirPlayServer.h) endif() From 489a7fcdd24f8002a44debd4ca7402ce6dcad384 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 13:41:57 +1000 Subject: [PATCH 095/651] [cmake][modules] FindPulseAudio cleanup and use core_target_link_libraries --- CMakeLists.txt | 2 +- cmake/modules/FindPulseAudio.cmake | 64 ++++++++++++--------------- cmake/scripts/linux/Install.cmake | 2 +- xbmc/cores/AudioEngine/CMakeLists.txt | 2 +- 4 files changed, 32 insertions(+), 38 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 70d7619a909d8..a1eb44e1c35da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -308,7 +308,7 @@ endif() set(outputFilterRegex "addons/xbmc.json") find_addon_xml_in_files(${outputFilterRegex}) -if(TARGET ${APP_NAME_LC}::Alsa AND TARGET PulseAudio::PulseAudio) +if(TARGET ${APP_NAME_LC}::Alsa AND TARGET ${APP_NAME_LC}::PulseAudio) list(APPEND AUDIO_BACKENDS_LIST "alsa+pulseaudio") endif() diff --git a/cmake/modules/FindPulseAudio.cmake b/cmake/modules/FindPulseAudio.cmake index 4d480ba6e56ae..907a7f952ea85 100644 --- a/cmake/modules/FindPulseAudio.cmake +++ b/cmake/modules/FindPulseAudio.cmake @@ -3,44 +3,40 @@ # -------------- # Finds the PulseAudio library # -# This will define the following target: +# This will define the following targets: # -# PulseAudio::PulseAudio - The PulseAudio library -# PulseAudio::PulseAudioSimple - The PulseAudio simple library -# PulseAudio::PulseAudioMainloop - The PulseAudio mainloop library +# ${APP_NAME_LC}::PulseAudio - The PulseAudio library +# ${APP_NAME_LC}::PulseAudioSimple - The PulseAudio simple library +# ${APP_NAME_LC}::PulseAudioMainloop - The PulseAudio mainloop library -if(NOT TARGET PulseAudio::PulseAudio) - - if(PulseAudio_FIND_VERSION) - if(PulseAudio_FIND_VERSION_EXACT) - set(PulseAudio_FIND_SPEC "=${PulseAudio_FIND_VERSION_COMPLETE}") - else() - set(PulseAudio_FIND_SPEC ">=${PulseAudio_FIND_VERSION_COMPLETE}") - endif() - endif() +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) if(PKG_CONFIG_FOUND) + if(PulseAudio_FIND_VERSION) + if(PulseAudio_FIND_VERSION_EXACT) + set(PulseAudio_FIND_SPEC "=${PulseAudio_FIND_VERSION_COMPLETE}") + else() + set(PulseAudio_FIND_SPEC ">=${PulseAudio_FIND_VERSION_COMPLETE}") + endif() + endif() + pkg_check_modules(PC_PULSEAUDIO libpulse${PulseAudio_FIND_SPEC} QUIET) pkg_check_modules(PC_PULSEAUDIO_MAINLOOP libpulse-mainloop-glib${PulseAudio_FIND_SPEC} QUIET) pkg_check_modules(PC_PULSEAUDIO_SIMPLE libpulse-simple${PulseAudio_FIND_SPEC} QUIET) endif() find_path(PULSEAUDIO_INCLUDE_DIR NAMES pulse/pulseaudio.h pulse/simple.h - HINTS ${PC_PULSEAUDIO_INCLUDEDIR} ${PC_PULSEAUDIO_INCLUDE_DIRS} - NO_CACHE) + HINTS ${PC_PULSEAUDIO_INCLUDEDIR} ${PC_PULSEAUDIO_INCLUDE_DIRS}) find_library(PULSEAUDIO_LIBRARY NAMES pulse libpulse - HINTS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS} - NO_CACHE) + HINTS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS}) find_library(PULSEAUDIO_SIMPLE_LIBRARY NAMES pulse-simple libpulse-simple - HINTS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS} - NO_CACHE) + HINTS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS}) find_library(PULSEAUDIO_MAINLOOP_LIBRARY NAMES pulse-mainloop pulse-mainloop-glib libpulse-mainloop-glib - HINTS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS} - NO_CACHE) + HINTS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS}) if(PC_PULSEAUDIO_VERSION) set(PULSEAUDIO_VERSION_STRING ${PC_PULSEAUDIO_VERSION}) @@ -59,21 +55,19 @@ if(NOT TARGET PulseAudio::PulseAudio) list(APPEND AUDIO_BACKENDS_LIST "pulseaudio") set(AUDIO_BACKENDS_LIST ${AUDIO_BACKENDS_LIST} PARENT_SCOPE) - add_library(PulseAudio::PulseAudioSimple UNKNOWN IMPORTED) - set_target_properties(PulseAudio::PulseAudioSimple PROPERTIES - IMPORTED_LOCATION "${PULSEAUDIO_SIMPLE_LIBRARY}") - - add_library(PulseAudio::PulseAudioMainloop UNKNOWN IMPORTED) - set_target_properties(PulseAudio::PulseAudioMainloop PROPERTIES - IMPORTED_LOCATION "${PULSEAUDIO_MAINLOOP_LIBRARY}") + add_library(${APP_NAME_LC}::PulseAudioSimple UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::PulseAudioSimple PROPERTIES + IMPORTED_LOCATION "${PULSEAUDIO_SIMPLE_LIBRARY}") - add_library(PulseAudio::PulseAudio UNKNOWN IMPORTED) - set_target_properties(PulseAudio::PulseAudio PROPERTIES - IMPORTED_LOCATION "${PULSEAUDIO_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${PULSEAUDIO_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_PULSEAUDIO=1 - INTERFACE_LINK_LIBRARIES "PulseAudio::PulseAudioMainloop;PulseAudio::PulseAudioSimple") + add_library(${APP_NAME_LC}::PulseAudioMainloop UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::PulseAudioMainloop PROPERTIES + IMPORTED_LOCATION "${PULSEAUDIO_MAINLOOP_LIBRARY}") - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP PulseAudio::PulseAudio) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${PULSEAUDIO_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${PULSEAUDIO_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_PULSEAUDIO + INTERFACE_LINK_LIBRARIES "${APP_NAME_LC}::PulseAudioMainloop;${APP_NAME_LC}::PulseAudioSimple") endif() endif() diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index 47dc8faeef74b..efe7674b74a5a 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -13,7 +13,7 @@ set(APP_INCLUDE_DIR ${includedir}/${APP_NAME_LC}) # Set XBMC_STANDALONE_SH_PULSE so we can insert PulseAudio block into kodi-standalone if(EXISTS ${CMAKE_SOURCE_DIR}/tools/Linux/kodi-standalone.sh.pulse) - if(ENABLE_PULSEAUDIO AND TARGET PulseAudio::PulseAudio) + if(ENABLE_PULSEAUDIO AND TARGET ${APP_NAME_LC}::PulseAudio) file(READ "${CMAKE_SOURCE_DIR}/tools/Linux/kodi-standalone.sh.pulse" pulse_content) set(XBMC_STANDALONE_SH_PULSE ${pulse_content}) endif() diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt index 5536d7b17ca05..cb3132c1afa48 100644 --- a/xbmc/cores/AudioEngine/CMakeLists.txt +++ b/xbmc/cores/AudioEngine/CMakeLists.txt @@ -65,7 +65,7 @@ if(TARGET ${APP_NAME_LC}::Alsa) endif() endif() -if(TARGET PulseAudio::PulseAudio) +if(TARGET ${APP_NAME_LC}::PulseAudio) list(APPEND SOURCES Sinks/AESinkPULSE.cpp) list(APPEND HEADERS Sinks/AESinkPULSE.h) endif() From b2d661ad2d1a62e2f775650c5006392af6320521 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 13:46:05 +1000 Subject: [PATCH 096/651] [cmake][modules] FindRapidJSON cleanup and use core_target_link_libraries --- cmake/modules/FindRapidJSON.cmake | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/cmake/modules/FindRapidJSON.cmake b/cmake/modules/FindRapidJSON.cmake index df34eff2b27d1..10a21390d1953 100644 --- a/cmake/modules/FindRapidJSON.cmake +++ b/cmake/modules/FindRapidJSON.cmake @@ -5,10 +5,10 @@ # # This will define the following target: # -# RapidJSON::RapidJSON - The RapidJSON library +# ${APP_NAME_LC}::RapidJSON - The RapidJSON library # -if(NOT TARGET RapidJSON::RapidJSON) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) include(cmake/scripts/common/ModuleHelpers.cmake) macro(buildrapidjson) @@ -71,8 +71,7 @@ if(NOT TARGET RapidJSON::RapidJSON) find_path(RAPIDJSON_INCLUDE_DIRS NAMES rapidjson/rapidjson.h HINTS ${DEPENDS_PATH}/include ${PC_RapidJSON_INCLUDEDIR} - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} - NO_CACHE) + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) endif() endif() @@ -82,11 +81,11 @@ if(NOT TARGET RapidJSON::RapidJSON) VERSION_VAR RapidJSON_VERSION) if(RAPIDJSON_FOUND) - add_library(RapidJSON::RapidJSON INTERFACE IMPORTED) - set_target_properties(RapidJSON::RapidJSON PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${RAPIDJSON_INCLUDE_DIRS}") + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${RAPIDJSON_INCLUDE_DIRS}") if(TARGET rapidjson) - add_dependencies(RapidJSON::RapidJSON rapidjson) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} rapidjson) endif() # Add internal build target when a Multi Config Generator is used @@ -104,7 +103,9 @@ if(NOT TARGET RapidJSON::RapidJSON) endif() add_dependencies(build_internal_depends rapidjson) endif() - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP RapidJSON::RapidJSON) + else() + if(RapidJSON_FIND_REQUIRED) + message(FATAL_ERROR "RapidJSON library not found. You may want to try -DENABLE_INTERNAL_RapidJSON=ON") + endif() endif() endif() From e2c7f8ff59212f798a16eea00e66a3579bdcac12 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 16:18:42 +1000 Subject: [PATCH 097/651] [cmake][macro] core_required_dep append to required_deps list if dep found --- cmake/scripts/common/Macros.cmake | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index c55bf01a2a03a..77c148a9116d4 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -407,6 +407,15 @@ function(core_require_dep) list(APPEND DEPLIBS ${${depup}_LIBRARIES}) list(APPEND DEP_DEFINES ${${depup}_DEFINITIONS}) export_dep() + + # We dont want to add a build tool + if (NOT ${depspec} IN_LIST optional_buildtools AND NOT ${depspec} IN_LIST required_buildtools) + # If dependency is found and is not in the list (eg shairplay) add to list + if (NOT ${depspec} IN_LIST required_deps) + set(required_deps ${required_deps} ${depspec} PARENT_SCOPE) + endif() + endif() + endforeach() endfunction() From c75630c6393027b927d0b4892469612f9e8f9159 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 13:51:48 +1000 Subject: [PATCH 098/651] [cmake][modules] FindShairplay cleanup and use core_target_link_libraries --- CMakeLists.txt | 2 +- cmake/modules/FindShairplay.cmake | 31 ++++++++++++++--------------- cmake/scripts/android/Install.cmake | 4 ++-- xbmc/network/CMakeLists.txt | 2 +- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a1eb44e1c35da..a50d75a130bbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -290,7 +290,7 @@ endif() if(ENABLE_AIRTUNES) find_package(Shairplay) - if(TARGET Shairplay::Shairplay) + if(TARGET ${APP_NAME_LC}::Shairplay) core_require_dep(Shairplay) endif() endif() diff --git a/cmake/modules/FindShairplay.cmake b/cmake/modules/FindShairplay.cmake index df4afa72d5b14..fd916ca648524 100644 --- a/cmake/modules/FindShairplay.cmake +++ b/cmake/modules/FindShairplay.cmake @@ -7,15 +7,16 @@ # # Shairplay::Shairplay - The Shairplay library -if(NOT TARGET Shairplay::Shairplay) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) - find_path(SHAIRPLAY_INCLUDE_DIR shairplay/raop.h NO_CACHE) + find_path(SHAIRPLAY_INCLUDE_DIR shairplay/raop.h) + find_library(SHAIRPLAY_LIBRARY NAMES shairplay libshairplay) include(FindPackageHandleStandardArgs) - find_library(SHAIRPLAY_LIBRARY NAMES shairplay libshairplay - NO_CACHE) + find_package_handle_standard_args(Shairplay + REQUIRED_VARS SHAIRPLAY_LIBRARY SHAIRPLAY_INCLUDE_DIR) - if(SHAIRPLAY_INCLUDE_DIR AND SHAIRPLAY_LIBRARY) + if(SHAIRPLAY_FOUND) include(CheckCSourceCompiles) set(CMAKE_REQUIRED_INCLUDES ${SHAIRPLAY_INCLUDE_DIR}) set(CMAKE_REQUIRED_LIBRARIES ${SHAIRPLAY_LIBRARIES}) @@ -28,17 +29,15 @@ if(NOT TARGET Shairplay::Shairplay) return 0; } " HAVE_SHAIRPLAY_CALLBACK_CLS) - endif() + unset(CMAKE_REQUIRED_INCLUDES) + unset(CMAKE_REQUIRED_LIBRARIES) - find_package_handle_standard_args(Shairplay - REQUIRED_VARS SHAIRPLAY_LIBRARY SHAIRPLAY_INCLUDE_DIR HAVE_SHAIRPLAY_CALLBACK_CLS) - - if(SHAIRPLAY_FOUND) - add_library(Shairplay::Shairplay UNKNOWN IMPORTED) - set_target_properties(Shairplay::Shairplay PROPERTIES - IMPORTED_LOCATION "${SHAIRPLAY_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${SHAIRPLAY_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_AIRTUNES=1) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP Shairplay::Shairplay) + if(HAVE_SHAIRPLAY_CALLBACK_CLS) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${SHAIRPLAY_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SHAIRPLAY_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_AIRTUNES) + endif() endif() endif() diff --git a/cmake/scripts/android/Install.cmake b/cmake/scripts/android/Install.cmake index b9121410070e9..8e2b90e7b4fa5 100644 --- a/cmake/scripts/android/Install.cmake +++ b/cmake/scripts/android/Install.cmake @@ -141,8 +141,8 @@ foreach(library IN LISTS LIBRARY_FILES) add_bundle_file(${library} ${libdir}/${APP_NAME_LC} ${CMAKE_BINARY_DIR}) endforeach() -if(TARGET Shairplay::Shairplay) - add_bundle_file(Shairplay::Shairplay ${libdir} "") +if(TARGET ${APP_NAME_LC}::Shairplay) + add_bundle_file(${APP_NAME_LC}::Shairplay ${libdir} "") endif() # Main targets from Makefile.in diff --git a/xbmc/network/CMakeLists.txt b/xbmc/network/CMakeLists.txt index 7d7a635a5d21e..b5266914bb38e 100644 --- a/xbmc/network/CMakeLists.txt +++ b/xbmc/network/CMakeLists.txt @@ -38,7 +38,7 @@ if(TARGET ${APP_NAME_LC}::Plist) list(APPEND HEADERS AirPlayServer.h) endif() -if(TARGET Shairplay::Shairplay) +if(TARGET ${APP_NAME_LC}::Shairplay) list(APPEND SOURCES AirTunesServer.cpp) list(APPEND HEADERS AirTunesServer.h) endif() From 123781bc04fbd701e99af462688756348d5395d8 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 14:15:17 +1000 Subject: [PATCH 099/651] [cmake][modules] FindSmbClient cleanup and use core_target_link_libraries --- cmake/modules/FindSmbClient.cmake | 91 +++++++++++-------- cmake/scripts/windows/ArchSetup.cmake | 2 +- xbmc/network/CMakeLists.txt | 2 +- xbmc/platform/posix/filesystem/CMakeLists.txt | 2 +- 4 files changed, 56 insertions(+), 41 deletions(-) diff --git a/cmake/modules/FindSmbClient.cmake b/cmake/modules/FindSmbClient.cmake index 1cb265036aabb..159dae52f77c0 100644 --- a/cmake/modules/FindSmbClient.cmake +++ b/cmake/modules/FindSmbClient.cmake @@ -3,50 +3,65 @@ # ------------- # Finds the SMB Client library # -# This will define the following variables:: +# This following imported target will be defined:: # -# SMBCLIENT_FOUND - system has SmbClient -# SMBCLIENT_INCLUDE_DIRS - the SmbClient include directory -# SMBCLIENT_LIBRARIES - the SmbClient libraries -# SMBCLIENT_DEFINITIONS - the SmbClient definitions -# -# and the following imported targets:: -# -# SmbClient::SmbClient - The SmbClient library +# ${APP_NAME_LC}::SmbClient - The SmbClient library -find_package(PkgConfig) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_SMBCLIENT smbclient QUIET) -endif() + if(WIN32 OR WINDOWS_STORE) + # UWP doesnt have native smb support. It receives it from an addon. + if(NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_COMPILE_DEFINITIONS HAS_FILESYSTEM_SMB) + endif() + else() -find_path(SMBCLIENT_INCLUDE_DIR NAMES libsmbclient.h - HINTS ${PC_SMBCLIENT_INCLUDEDIR}) -find_library(SMBCLIENT_LIBRARY NAMES smbclient - HINTS ${PC_SMBCLIENT_LIBDIR}) + find_package(PkgConfig) -set(SMBCLIENT_VERSION ${PC_SMBCLIENT_VERSION}) + if(PKG_CONFIG_FOUND) + pkg_check_modules(SMBCLIENT smbclient QUIET) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(SmbClient - REQUIRED_VARS SMBCLIENT_LIBRARY SMBCLIENT_INCLUDE_DIR - VERSION_VAR SMBCLIENT_VERSION) + # First item is the full path of the library file found + # pkg_check_modules does not populate a variable of the found library explicitly + list(GET SMBCLIENT_LINK_LIBRARIES 0 SMBCLIENT_LIBRARY) -if(SMBCLIENT_FOUND) - set(SMBCLIENT_LIBRARIES ${SMBCLIENT_LIBRARY}) - if(${SMBCLIENT_LIBRARY} MATCHES ".+\.a$" AND PC_SMBCLIENT_STATIC_LIBRARIES) - list(APPEND SMBCLIENT_LIBRARIES ${PC_SMBCLIENT_STATIC_LIBRARIES}) - endif() - set(SMBCLIENT_INCLUDE_DIRS ${SMBCLIENT_INCLUDE_DIR}) - set(SMBCLIENT_DEFINITIONS -DHAS_FILESYSTEM_SMB=1) - - if(NOT TARGET SmbClient::SmbClient) - add_library(SmbClient::SmbClient UNKNOWN IMPORTED) - set_target_properties(SmbClient::SmbClient PROPERTIES - IMPORTED_LOCATION "${SMBCLIENT_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${SMBCLIENT_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_FILESYSTEM_SMB=1) + # Add link libraries for static lib usage + if(${SMBCLIENT_LIBRARY} MATCHES ".+\.a$" AND SMBCLIENT_LINK_LIBRARIES) + # Remove duplicates + list(REMOVE_DUPLICATES SMBCLIENT_LINK_LIBRARIES) + + # Remove own library + list(FILTER SMBCLIENT_LINK_LIBRARIES EXCLUDE REGEX ".*smbclient.*\.a$") + set(PC_SMBCLIENT_LINK_LIBRARIES ${SMBCLIENT_LINK_LIBRARIES}) + endif() + + # pkgconfig sets SMBCLIENT_INCLUDEDIR, map this to our "standard" variable name + set(SMBCLIENT_INCLUDE_DIR ${SMBCLIENT_INCLUDEDIR}) + set(SMBCLIENT_VERSION ${PC_SMBCLIENT_VERSION}) + else() + find_path(SMBCLIENT_INCLUDE_DIR NAMES libsmbclient.h) + find_library(SMBCLIENT_LIBRARY NAMES smbclient) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(SmbClient + REQUIRED_VARS SMBCLIENT_LIBRARY SMBCLIENT_INCLUDE_DIR + VERSION_VAR SMBCLIENT_VERSION) + + if(SMBCLIENT_FOUND) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${SMBCLIENT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SMBCLIENT_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_FILESYSTEM_SMB) + + # Add link libraries for static lib usage found from pkg-config + if(PC_SMBCLIENT_LINK_LIBRARIES) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_LINK_LIBRARIES "${PC_SMBCLIENT_LINK_LIBRARIES}") + endif() + endif() endif() endif() - -mark_as_advanced(LIBSMBCLIENT_INCLUDE_DIR LIBSMBCLIENT_LIBRARY) diff --git a/cmake/scripts/windows/ArchSetup.cmake b/cmake/scripts/windows/ArchSetup.cmake index b19789d5bbbb9..656c36d4612b9 100644 --- a/cmake/scripts/windows/ArchSetup.cmake +++ b/cmake/scripts/windows/ArchSetup.cmake @@ -65,7 +65,7 @@ set(SYSTEM_DEFINES -DWIN32_LEAN_AND_MEAN -DNOMINMAX -DHAS_DX -D__STDC_CONSTANT_M $<$:-DD3D_DEBUG_INFO>) # Additional SYSTEM_DEFINES -list(APPEND SYSTEM_DEFINES -DHAS_WIN32_NETWORK -DHAS_FILESYSTEM_SMB) +list(APPEND SYSTEM_DEFINES -DHAS_WIN32_NETWORK) # The /MP option enables /FS by default. if(CMAKE_GENERATOR MATCHES "Visual Studio") diff --git a/xbmc/network/CMakeLists.txt b/xbmc/network/CMakeLists.txt index b5266914bb38e..a4dac89ecbdd2 100644 --- a/xbmc/network/CMakeLists.txt +++ b/xbmc/network/CMakeLists.txt @@ -43,7 +43,7 @@ if(TARGET ${APP_NAME_LC}::Shairplay) list(APPEND HEADERS AirTunesServer.h) endif() -if(SMBCLIENT_FOUND) +if(TARGET ${APP_NAME_LC}::SmbClient) list(APPEND HEADERS IWSDiscovery.h) endif() diff --git a/xbmc/platform/posix/filesystem/CMakeLists.txt b/xbmc/platform/posix/filesystem/CMakeLists.txt index badb1a8da8d81..2ee8695e80a9f 100644 --- a/xbmc/platform/posix/filesystem/CMakeLists.txt +++ b/xbmc/platform/posix/filesystem/CMakeLists.txt @@ -4,7 +4,7 @@ set(SOURCES PosixDirectory.cpp set(HEADERS PosixDirectory.h PosixFile.h) -if(SMBCLIENT_FOUND) +if(TARGET ${APP_NAME_LC}::SmbClient) list(APPEND SOURCES SMBDirectory.cpp SMBFile.cpp SMBWSDiscovery.cpp From 1d0c419879f02737eb46620c00abf19281cb6cd9 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 14:24:31 +1000 Subject: [PATCH 100/651] [cmake][modules] FindSmctemp cleanup and use core_target_link_libraries --- cmake/modules/FindSmctemp.cmake | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/cmake/modules/FindSmctemp.cmake b/cmake/modules/FindSmctemp.cmake index a96aa3375e2f9..3cfa5d6fa5672 100644 --- a/cmake/modules/FindSmctemp.cmake +++ b/cmake/modules/FindSmctemp.cmake @@ -5,14 +5,14 @@ # # This will define the following imported targets:: # -# SMCTEMP::SMCTEMP - The smctemp library +# ${APP_NAME_LC}::Smctemp - The smctemp library -if(NOT TARGET SMCTEMP::SMCTEMP) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_path(SMCTEMP_INCLUDE_DIR NAMES smctemp.h - PATHS ${PC_SMCTEMP_INCLUDEDIR} NO_CACHE) + HINTS ${DEPENDS_PATH}/include) find_library(SMCTEMP_LIBRARY NAMES smctemp - PATHS ${PC_SMCTEMP_LIBDIR} NO_CACHE) + HINTS ${DEPENDS_PATH}/lib) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Smctemp @@ -20,11 +20,13 @@ if(NOT TARGET SMCTEMP::SMCTEMP) VERSION_VAR SMCTEMP_VERSION) if(SMCTEMP_FOUND) - add_library(SMCTEMP::SMCTEMP UNKNOWN IMPORTED) - set_target_properties(SMCTEMP::SMCTEMP PROPERTIES - IMPORTED_LOCATION "${SMCTEMP_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${SMCTEMP_INCLUDE_DIR}") - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP SMCTEMP::SMCTEMP) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${SMCTEMP_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SMCTEMP_INCLUDE_DIR}") + else() + if(Smctemp_FIND_REQUIRED) + message(FATAL_ERROR "Smctemp library not found.") + endif() endif() endif() From bc3b4a22ae253e682d908ab81d25a69d75520ba2 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 14:27:36 +1000 Subject: [PATCH 101/651] [cmake][modules] FindSndio cleanup and use core_target_link_libraries --- cmake/modules/FindSndio.cmake | 20 +++++++++----------- xbmc/cores/AudioEngine/CMakeLists.txt | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/cmake/modules/FindSndio.cmake b/cmake/modules/FindSndio.cmake index c8c7161bff490..bc4f6d370a0e1 100644 --- a/cmake/modules/FindSndio.cmake +++ b/cmake/modules/FindSndio.cmake @@ -5,11 +5,11 @@ # # This will define the following target: # -# Sndio::Sndio - the sndio library +# ${APP_NAME_LC}::Sndio - the sndio library # -if(NOT TARGET Sndio::Sndio) - find_path(SNDIO_INCLUDE_DIR sndio.h NO_CACHE) - find_library(SNDIO_LIBRARY sndio NO_CACHE) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + find_path(SNDIO_INCLUDE_DIR sndio.h) + find_library(SNDIO_LIBRARY sndio) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Sndio @@ -19,12 +19,10 @@ if(NOT TARGET Sndio::Sndio) list(APPEND AUDIO_BACKENDS_LIST "sndio") set(AUDIO_BACKENDS_LIST ${AUDIO_BACKENDS_LIST} PARENT_SCOPE) - - add_library(Sndio::Sndio UNKNOWN IMPORTED) - set_target_properties(Sndio::Sndio PROPERTIES - IMPORTED_LOCATION "${SNDIO_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${SNDIO_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_SNDIO=1) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP Sndio::Sndio) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${SNDIO_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SNDIO_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_SNDIO) endif() endif() diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt index cb3132c1afa48..593e1849d41a7 100644 --- a/xbmc/cores/AudioEngine/CMakeLists.txt +++ b/xbmc/cores/AudioEngine/CMakeLists.txt @@ -93,7 +93,7 @@ if(PIPEWIRE_FOUND) Sinks/pipewire/PipewireThreadLoop.h) endif() -if(TARGET Sndio::Sndio) +if(TARGET ${APP_NAME_LC}::Sndio) list(APPEND SOURCES Sinks/AESinkSNDIO.cpp) list(APPEND HEADERS Sinks/AESinkSNDIO.h) endif() From 0a5c05173a5297d722b1fd4e58dd8d28812eec72 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 14:40:13 +1000 Subject: [PATCH 102/651] [cmake][modules] FindSpdlog cleanup and use core_target_link_libraries --- cmake/modules/FindSpdlog.cmake | 115 ++++++++++++++-------------- xbmc/interfaces/swig/CMakeLists.txt | 6 +- 2 files changed, 61 insertions(+), 60 deletions(-) diff --git a/cmake/modules/FindSpdlog.cmake b/cmake/modules/FindSpdlog.cmake index d1c76451d687e..4432acc434775 100644 --- a/cmake/modules/FindSpdlog.cmake +++ b/cmake/modules/FindSpdlog.cmake @@ -4,7 +4,7 @@ # # This will define the following target: # -# spdlog::spdlog - The Spdlog library +# ${APP_NAME_LC}::Spdlog - The Spdlog library macro(buildSpdlog) if(APPLE) @@ -45,7 +45,7 @@ macro(buildSpdlog) add_dependencies(${MODULE_LC} ${APP_NAME_LC}::Fmt) endmacro() -if(NOT TARGET spdlog::spdlog) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) include(cmake/scripts/common/ModuleHelpers.cmake) # Check for dependencies - Must be done before SETUP_BUILD_VARS @@ -74,59 +74,55 @@ if(NOT TARGET spdlog::spdlog) buildSpdlog() else() - if(NOT TARGET spdlog::spdlog) + if(TARGET spdlog::spdlog) + # This is for the case where a distro provides a non standard (Debug/Release) config type + # eg Debian's config file is spdlogConfigTargets-none.cmake + # convert this back to either DEBUG/RELEASE or just RELEASE + # we only do this because we use find_package_handle_standard_args for config time output + # and it isnt capable of handling TARGETS, so we have to extract the info + get_target_property(_SPDLOG_CONFIGURATIONS spdlog::spdlog IMPORTED_CONFIGURATIONS) + foreach(_spdlog_config IN LISTS _SPDLOG_CONFIGURATIONS) + # Some non standard config (eg None on Debian) + # Just set to RELEASE var so select_library_configurations can continue to work its magic + string(TOUPPER ${_spdlog_config} _spdlog_config_UPPER) + if((NOT ${_spdlog_config_UPPER} STREQUAL "RELEASE") AND + (NOT ${_spdlog_config_UPPER} STREQUAL "DEBUG")) + get_target_property(SPDLOG_LIBRARY_RELEASE spdlog::spdlog IMPORTED_LOCATION_${_spdlog_config_UPPER}) + else() + get_target_property(SPDLOG_LIBRARY_${_spdlog_config_UPPER} spdlog::spdlog IMPORTED_LOCATION_${_spdlog_config_UPPER}) + endif() + endforeach() + + get_target_property(SPDLOG_INCLUDE_DIR spdlog::spdlog INTERFACE_INCLUDE_DIRECTORIES) + else() find_package(PkgConfig) # Fallback to pkg-config and individual lib/include file search if(PKG_CONFIG_FOUND) pkg_check_modules(PC_SPDLOG spdlog QUIET) + + # Only add -D definitions. Skip -I include as we do a find_path for the header anyway + foreach(_spdlog_cflag IN LISTS PC_SPDLOG_CFLAGS) + if(${_spdlog_cflag} MATCHES "^-D(.*)") + list(APPEND _spdlog_definitions ${CMAKE_MATCH_1}) + endif() + endforeach() + set(SPDLOG_VERSION ${PC_SPDLOG_VERSION}) endif() find_path(SPDLOG_INCLUDE_DIR NAMES spdlog/spdlog.h HINTS ${DEPENDS_PATH}/include ${PC_SPDLOG_INCLUDEDIR} - ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} - NO_CACHE) + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) find_library(SPDLOG_LIBRARY_RELEASE NAMES spdlog HINTS ${DEPENDS_PATH}/lib ${PC_SPDLOG_LIBDIR} - ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} - NO_CACHE) + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) find_library(SPDLOG_LIBRARY_DEBUG NAMES spdlogd HINTS ${DEPENDS_PATH}/lib ${PC_SPDLOG_LIBDIR} - ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} - NO_CACHE) - - # Only add -D definitions. Skip -I include as we do a find_path for the header anyway - foreach(_spdlog_cflag IN LISTS PC_SPDLOG_CFLAGS) - if(${_spdlog_cflag} MATCHES "^-D(.*)") - list(APPEND _spdlog_definitions ${CMAKE_MATCH_1}) - endif() - endforeach() + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) endif() endif() - if(TARGET spdlog::spdlog) - # This is for the case where a distro provides a non standard (Debug/Release) config type - # eg Debian's config file is spdlogConfigTargets-none.cmake - # convert this back to either DEBUG/RELEASE or just RELEASE - # we only do this because we use find_package_handle_standard_args for config time output - # and it isnt capable of handling TARGETS, so we have to extract the info - get_target_property(_SPDLOG_CONFIGURATIONS spdlog::spdlog IMPORTED_CONFIGURATIONS) - foreach(_spdlog_config IN LISTS _SPDLOG_CONFIGURATIONS) - # Some non standard config (eg None on Debian) - # Just set to RELEASE var so select_library_configurations can continue to work its magic - string(TOUPPER ${_spdlog_config} _spdlog_config_UPPER) - if((NOT ${_spdlog_config_UPPER} STREQUAL "RELEASE") AND - (NOT ${_spdlog_config_UPPER} STREQUAL "DEBUG")) - get_target_property(SPDLOG_LIBRARY_RELEASE spdlog::spdlog IMPORTED_LOCATION_${_spdlog_config_UPPER}) - else() - get_target_property(SPDLOG_LIBRARY_${_spdlog_config_UPPER} spdlog::spdlog IMPORTED_LOCATION_${_spdlog_config_UPPER}) - endif() - endforeach() - - get_target_property(SPDLOG_INCLUDE_DIR spdlog::spdlog INTERFACE_INCLUDE_DIRECTORIES) - endif() - include(SelectLibraryConfigurations) select_library_configurations(SPDLOG) unset(SPDLOG_LIBRARIES) @@ -137,34 +133,35 @@ if(NOT TARGET spdlog::spdlog) VERSION_VAR SPDLOG_VERSION) if(Spdlog_FOUND) - if(NOT TARGET spdlog::spdlog) - # Ideally we probably shouldnt be overriding these. We should trust the cmake config file - list(APPEND _spdlog_definitions SPDLOG_DEBUG_ON - SPDLOG_NO_ATOMIC_LEVELS) + # cmake target and not building internal + if(TARGET spdlog::spdlog AND NOT TARGET spdlog) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS spdlog::spdlog) - add_library(spdlog::spdlog UNKNOWN IMPORTED) + else() + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) if(SPDLOG_LIBRARY_RELEASE) - set_target_properties(spdlog::spdlog PROPERTIES - IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION_RELEASE "${SPDLOG_LIBRARY_RELEASE}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${SPDLOG_LIBRARY_RELEASE}") endif() if(SPDLOG_LIBRARY_DEBUG) - set_target_properties(spdlog::spdlog PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION_DEBUG "${SPDLOG_LIBRARY_DEBUG}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION_DEBUG "${SPDLOG_LIBRARY_DEBUG}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) endif() - set_target_properties(spdlog::spdlog PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${SPDLOG_INCLUDE_DIR}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${SPDLOG_INCLUDE_DIR}") - # We need to append in case the cmake config already has definitions - set_property(TARGET spdlog::spdlog APPEND PROPERTY - INTERFACE_COMPILE_DEFINITIONS "${_spdlog_definitions}") + if(_spdlog_definitions) + # We need to append in case the cmake config already has definitions + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "${_spdlog_definitions}") + endif() endif() - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP spdlog::spdlog) - if(TARGET spdlog) - add_dependencies(spdlog::spdlog spdlog) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} spdlog) endif() # Add internal build target when a Multi Config Generator is used @@ -182,5 +179,9 @@ if(NOT TARGET spdlog::spdlog) endif() add_dependencies(build_internal_depends spdlog) endif() + else() + if(Spdlog_FIND_REQUIRED) + message(FATAL_ERROR "Spdlog libraries were not found. You may want to try -DENABLE_INTERNAL_SPDLOG=ON") + endif() endif() endif() diff --git a/xbmc/interfaces/swig/CMakeLists.txt b/xbmc/interfaces/swig/CMakeLists.txt index 0d0cadcabd524..ae23c77261af4 100644 --- a/xbmc/interfaces/swig/CMakeLists.txt +++ b/xbmc/interfaces/swig/CMakeLists.txt @@ -117,9 +117,9 @@ add_library(python_binding STATIC ${SOURCES}) set_target_properties(python_binding PROPERTIES POSITION_INDEPENDENT_CODE TRUE FOLDER "Build Utilities") set(core_DEPENDS python_binding ${core_DEPENDS} CACHE STRING "" FORCE) -add_dependencies(python_binding ${GLOBAL_TARGET_DEPS}) -# This propagates target options from dependencies (eg spdlog definitions) -target_link_libraries(python_binding PRIVATE ${GLOBAL_TARGET_DEPS}) + +# This target is not run through our regular macro's. Explicitly link required target +target_link_libraries(python_binding PRIVATE ${APP_NAME_LC}::Spdlog) if(CORE_SYSTEM_NAME STREQUAL windowsstore) set_target_properties(python_binding PROPERTIES STATIC_LIBRARY_FLAGS "/ignore:4264") From f134ce5459da0c8aab81db831e33115632f0a659 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 14:45:34 +1000 Subject: [PATCH 103/651] [cmake][modules] FindSqlite3 cleanup and use core_target_link_libraries --- cmake/modules/FindSqlite3.cmake | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/cmake/modules/FindSqlite3.cmake b/cmake/modules/FindSqlite3.cmake index 6fb2bfe2d74d5..c7c81fd96897c 100644 --- a/cmake/modules/FindSqlite3.cmake +++ b/cmake/modules/FindSqlite3.cmake @@ -5,23 +5,23 @@ # # This will define the following target: # -# SQLite3::SQLite3 - The SQLite3 library +# ${APP_NAME_LC}::Sqlite3 - The SQLite3 library # -if(NOT TARGET SQLite3::SQLite3) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) - if(PKG_CONFIG_FOUND) + if(PKG_CONFIG_FOUND AND NOT (WIN32 OR WINDOWS_STORE)) pkg_check_modules(PC_SQLITE3 sqlite3 QUIET) + + set(SQLITE3_VERSION ${PC_SQLITE3_VERSION}) endif() find_path(SQLITE3_INCLUDE_DIR NAMES sqlite3.h - HINTS ${PC_SQLITE3_INCLUDEDIR} - NO_CACHE) + HINTS ${DEPENDS_PATH}/include ${PC_SQLITE3_INCLUDEDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) find_library(SQLITE3_LIBRARY NAMES sqlite3 - HINTS ${PC_SQLITE3_LIBDIR} - NO_CACHE) - - set(SQLITE3_VERSION ${PC_SQLITE3_VERSION}) + HINTS ${DEPENDS_PATH}/lib ${PC_SQLITE3_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Sqlite3 @@ -29,11 +29,13 @@ if(NOT TARGET SQLite3::SQLite3) VERSION_VAR SQLITE3_VERSION) if(Sqlite3_FOUND) - add_library(SQLite3::SQLite3 UNKNOWN IMPORTED) - set_target_properties(SQLite3::SQLite3 PROPERTIES - IMPORTED_LOCATION "${SQLITE3_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${SQLITE3_INCLUDE_DIR}") - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP SQLite3::SQLite3) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${SQLITE3_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SQLITE3_INCLUDE_DIR}") + else() + if(Sqlite3_FIND_REQUIRED) + message(FATAL_ERROR "SQLite3 library not found.") + endif() endif() endif() From 307176ab65d3e8fd481d794bddb9ca5736e6d390 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 14:53:30 +1000 Subject: [PATCH 104/651] [cmake][modules] FindTagLib cleanup and use core_target_link_libraries --- cmake/modules/FindTagLib.cmake | 67 ++++++++++++++-------------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/cmake/modules/FindTagLib.cmake b/cmake/modules/FindTagLib.cmake index 1883d3fd9d14b..04b78d3d921d4 100644 --- a/cmake/modules/FindTagLib.cmake +++ b/cmake/modules/FindTagLib.cmake @@ -5,7 +5,7 @@ # # This will define the following target: # -# TagLib::TagLib - The TagLib library +# ${APP_NAME_LC}::TagLib - The TagLib library # macro(buildTagLib) @@ -46,9 +46,10 @@ macro(buildTagLib) BUILD_DEP_TARGET() add_dependencies(${MODULE_LC} ZLIB::ZLIB) + set(TAGLIB_LINK_LIBRARIES "ZLIB::ZLIB") endmacro() -if(NOT TARGET TagLib::TagLib) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) include(cmake/scripts/common/ModuleHelpers.cmake) @@ -71,6 +72,7 @@ if(NOT TARGET TagLib::TagLib) # Build Taglib buildTagLib() else() + find_package(PkgConfig) if(PKG_CONFIG_FOUND AND NOT (WIN32 OR WINDOWS_STORE)) if(TagLib_FIND_VERSION) if(TagLib_FIND_VERSION_EXACT) @@ -79,29 +81,18 @@ if(NOT TARGET TagLib::TagLib) set(TagLib_FIND_SPEC ">=${TagLib_FIND_VERSION_COMPLETE}") endif() endif() - pkg_check_modules(PC_TAGLIB taglib${TagLib_FIND_SPEC} QUIET) - - set(TAGLIB_LINK_LIBS ${PC_TAGLIB_LIBRARIES}) + pkg_check_modules(TAGLIB taglib${TagLib_FIND_SPEC} QUIET) endif() find_path(TAGLIB_INCLUDE_DIR NAMES taglib/tag.h - HINTS ${DEPENDS_PATH}/include ${PC_TAGLIB_INCLUDEDIR} - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} - NO_CACHE) + HINTS ${DEPENDS_PATH}/include ${TAGLIB_INCLUDEDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) find_library(TAGLIB_LIBRARY_RELEASE NAMES tag - HINTS ${DEPENDS_PATH}/lib ${PC_TAGLIB_LIBDIR} - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} - NO_CACHE) + HINTS ${DEPENDS_PATH}/lib ${TAGLIB_LIBDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) find_library(TAGLIB_LIBRARY_DEBUG NAMES tagd - HINTS ${DEPENDS_PATH}/lib ${PC_TAGLIB_LIBDIR} - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} - NO_CACHE) - - if(TAGLIBCONFIG_VER) - set(TAGLIB_VERSION ${TAGLIBCONFIG_VER}) - else() - set(TAGLIB_VERSION ${PC_TAGLIB_VERSION}) - endif() + HINTS ${DEPENDS_PATH}/lib ${TAGLIB_LIBDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) endif() include(SelectLibraryConfigurations) @@ -114,29 +105,29 @@ if(NOT TARGET TagLib::TagLib) VERSION_VAR TAGLIB_VERSION) if(TagLib_FOUND) - add_library(TagLib::TagLib UNKNOWN IMPORTED) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) if(TAGLIB_LIBRARY_RELEASE) - set_target_properties(TagLib::TagLib PROPERTIES - IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION_RELEASE "${TAGLIB_LIBRARY_RELEASE}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${TAGLIB_LIBRARY_RELEASE}") endif() if(TAGLIB_LIBRARY_DEBUG) - set_target_properties(TagLib::TagLib PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION_DEBUG "${TAGLIB_LIBRARY_DEBUG}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION_DEBUG "${TAGLIB_LIBRARY_DEBUG}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) endif() - set_target_properties(TagLib::TagLib PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${TAGLIB_INCLUDE_DIR}") - - # if pkg-config returns link libs at to TARGET. For internal build, we use ZLIB::Zlib - # dependency explicitly - if(TAGLIB_LINK_LIBS) - set_target_properties(TagLib::TagLib PROPERTIES - INTERFACE_LINK_LIBRARIES "${TAGLIB_LINK_LIBS}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${TAGLIB_INCLUDE_DIR}") + + # if pkg-config returns link libs add to TARGET. + if(TAGLIB_LINK_LIBRARIES) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_LINK_LIBRARIES "${TAGLIB_LINK_LIBRARIES}") endif() if(TARGET taglib) - add_dependencies(TagLib::TagLib taglib) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} taglib) endif() # Add internal build target when a Multi Config Generator is used @@ -154,11 +145,9 @@ if(NOT TARGET TagLib::TagLib) endif() add_dependencies(build_internal_depends taglib) endif() - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP TagLib::TagLib) else() if(TagLib_FIND_REQUIRED) - message(FATAL_ERROR "TagLib not found.") + message(FATAL_ERROR "TagLib not found. You may want to try -DENABLE_INTERNAL_TAGLIB=ON") endif() endif() endif() From 0ec9b32e31b06a17d988c1fda94c2a1160a2f29f Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 14:58:09 +1000 Subject: [PATCH 105/651] [cmake][modules] FindTinyXML cleanup and use core_target_link_libraries --- cmake/modules/FindTinyXML.cmake | 88 +++++++++++++++------------------ 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/cmake/modules/FindTinyXML.cmake b/cmake/modules/FindTinyXML.cmake index c0daa9978e82b..9b17c52629e0b 100644 --- a/cmake/modules/FindTinyXML.cmake +++ b/cmake/modules/FindTinyXML.cmake @@ -3,68 +3,58 @@ # ----------- # Finds the TinyXML library # -# This will define the following variables:: +# The following imported targets are created:: # -# TINYXML_FOUND - system has TinyXML -# TINYXML_INCLUDE_DIRS - the TinyXML include directory -# TINYXML_LIBRARIES - the TinyXML libraries -# TINYXML_DEFINITIONS - the TinyXML definitions -# -# and the following imported targets:: -# -# TinyXML::TinyXML - The TinyXML library +# ${APP_NAME_LC}::TinyXML - The TinyXML library -find_package(PkgConfig) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + find_package(PkgConfig) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_TINYXML tinyxml QUIET) -endif() + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_TINYXML tinyxml QUIET) + endif() -find_path(TINYXML_INCLUDE_DIR tinyxml.h - PATH_SUFFIXES tinyxml - HINTS ${PC_TINYXML_INCLUDEDIR}) -find_library(TINYXML_LIBRARY_RELEASE NAMES tinyxml tinyxmlSTL + find_path(TINYXML_INCLUDE_DIR tinyxml.h + PATH_SUFFIXES tinyxml + HINTS ${PC_TINYXML_INCLUDEDIR}) + find_library(TINYXML_LIBRARY_RELEASE NAMES tinyxml tinyxmlSTL + PATH_SUFFIXES tinyxml + HINTS ${PC_TINYXML_LIBDIR}) + find_library(TINYXML_LIBRARY_DEBUG NAMES tinyxmld tinyxmlSTLd PATH_SUFFIXES tinyxml HINTS ${PC_TINYXML_LIBDIR}) -find_library(TINYXML_LIBRARY_DEBUG NAMES tinyxmld tinyxmlSTLd - PATH_SUFFIXES tinyxml - HINTS ${PC_TINYXML_LIBDIR}) -set(TINYXML_VERSION ${PC_TINYXML_VERSION}) + set(TINYXML_VERSION ${PC_TINYXML_VERSION}) -include(SelectLibraryConfigurations) -select_library_configurations(TINYXML) + include(SelectLibraryConfigurations) + select_library_configurations(TINYXML) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(TinyXML - REQUIRED_VARS TINYXML_LIBRARY TINYXML_INCLUDE_DIR - VERSION_VAR TINYXML_VERSION) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(TinyXML + REQUIRED_VARS TINYXML_LIBRARY TINYXML_INCLUDE_DIR + VERSION_VAR TINYXML_VERSION) -if(TINYXML_FOUND) - set(TINYXML_LIBRARIES ${TINYXML_LIBRARY}) - set(TINYXML_INCLUDE_DIRS ${TINYXML_INCLUDE_DIR}) - if(WIN32) - set(TINYXML_DEFINITIONS -DTIXML_USE_STL=1) - endif() - - if(NOT TARGET TinyXML::TinyXML) - add_library(TinyXML::TinyXML UNKNOWN IMPORTED) + if(TINYXML_FOUND) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) if(TINYXML_LIBRARY_RELEASE) - set_target_properties(TinyXML::TinyXML PROPERTIES - IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION "${TINYXML_LIBRARY_RELEASE}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${TINYXML_LIBRARY_RELEASE}") endif() if(TINYXML_LIBRARY_DEBUG) - set_target_properties(TinyXML::TinyXML PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION "${TINYXML_LIBRARY_DEBUG}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION_DEBUG "${TINYXML_LIBRARY_DEBUG}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) endif() - set_target_properties(TinyXML::TinyXML PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${TINYXML_INCLUDE_DIR}") - if(WIN32) - set_target_properties(TinyXML::TinyXML PROPERTIES - INTERFACE_COMPILE_DEFINITIONS TIXML_USE_STL=1) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${TINYXML_INCLUDE_DIR}") + if(WIN32 OR WINDOWS_STORE) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_COMPILE_DEFINITIONS TIXML_USE_STL) + endif() + else() + if(TinyXML_FIND_REQUIRED) + message(FATAL_ERROR "TinyXML library not found.") endif() endif() endif() - -mark_as_advanced(TINYXML_INCLUDE_DIR TINYXML_LIBRARY) From 4b812a626cb24bbcadd5ea7b21071426e755a20f Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 15:06:42 +1000 Subject: [PATCH 106/651] [cmake][modules] FindTinyXML2 cleanup and use core_target_link_libraries --- cmake/modules/FindTinyXML2.cmake | 99 ++++++++++++++++---------------- 1 file changed, 48 insertions(+), 51 deletions(-) diff --git a/cmake/modules/FindTinyXML2.cmake b/cmake/modules/FindTinyXML2.cmake index a75c370f7e175..d65d4b597713f 100644 --- a/cmake/modules/FindTinyXML2.cmake +++ b/cmake/modules/FindTinyXML2.cmake @@ -5,7 +5,7 @@ # # This will define the following target: # -# tinyxml2::tinyxml2 - The TinyXML2 library +# ${APP_NAME_LC}::TinyXML2 - The TinyXML2 library macro(buildTinyXML2) set(TINYXML2_VERSION ${${MODULE}_VER}) @@ -41,72 +41,64 @@ macro(buildTinyXML2) BUILD_DEP_TARGET() endmacro() -if(NOT TARGET tinyxml2::tinyxml2) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) include(cmake/scripts/common/ModuleHelpers.cmake) set(MODULE_LC tinyxml2) SETUP_BUILD_VARS() - find_package(TINYXML2 CONFIG QUIET + find_package(tinyxml2 CONFIG QUIET HINTS ${DEPENDS_PATH}/lib/cmake ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) # Check for existing TINYXML2. If version >= TINYXML2-VERSION file version, dont build # A corner case, but if a linux/freebsd user WANTS to build internal tinyxml2, build anyway - if((TINYXML2_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_TINYXML2) OR + if((tinyxml2_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_TINYXML2) OR ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_TINYXML2)) buildTinyXML2() else() - # This is the fallback case where linux distro's dont ship cmake config files - # use the old find_library way. Only do this if we didnt find a cmake config - # in the event of the version < depends version - if(NOT TARGET tinyxml2::tinyxml2) + if(TARGET tinyxml2::tinyxml2) + # This is for the case where a distro provides a non standard (Debug/Release) config type + # eg Debian's config file is tinyxml2ConfigTargets-none.cmake + # convert this back to either DEBUG/RELEASE or just RELEASE + # we only do this because we use find_package_handle_standard_args for config time output + # and it isnt capable of handling TARGETS, so we have to extract the info + get_target_property(_TINYXML2_CONFIGURATIONS tinyxml2::tinyxml2 IMPORTED_CONFIGURATIONS) + foreach(_tinyxml2_config IN LISTS _TINYXML2_CONFIGURATIONS) + # Some non standard config (eg None on Debian) + # Just set to RELEASE var so select_library_configurations can continue to work its magic + string(TOUPPER ${_tinyxml2_config} _tinyxml2_config_UPPER) + if((NOT ${_tinyxml2_config_UPPER} STREQUAL "RELEASE") AND + (NOT ${_tinyxml2_config_UPPER} STREQUAL "DEBUG")) + get_target_property(TINYXML2_LIBRARY_RELEASE tinyxml2::tinyxml2 IMPORTED_LOCATION_${_tinyxml2_config_UPPER}) + else() + get_target_property(TINYXML2_LIBRARY_${_tinyxml2_config_UPPER} tinyxml2::tinyxml2 IMPORTED_LOCATION_${_tinyxml2_config_UPPER}) + endif() + endforeach() + + # Need this, as we may only get the existing TARGET from system and not build or use pkg-config + get_target_property(TINYXML2_INCLUDE_DIR tinyxml2::tinyxml2 INTERFACE_INCLUDE_DIRECTORIES) + else() if(PKG_CONFIG_FOUND) pkg_check_modules(PC_TINYXML2 tinyxml2 QUIET) endif() find_path(TINYXML2_INCLUDE_DIR NAMES tinyxml2.h HINTS ${DEPENDS_PATH}/include ${PC_TINYXML2_INCLUDEDIR} - ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} - NO_CACHE) + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) find_library(TINYXML2_LIBRARY_RELEASE NAMES tinyxml2 HINTS ${DEPENDS_PATH}/lib ${PC_TINYXML2_LIBDIR} - ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} - NO_CACHE) + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) find_library(TINYXML2_LIBRARY_DEBUG NAMES tinyxml2d HINTS ${DEPENDS_PATH}/lib ${PC_TINYXML2_LIBDIR} - ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} - NO_CACHE) + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) set(TINYXML2_VERSION ${PC_TINYXML2_VERSION}) endif() endif() - if(TARGET tinyxml2::tinyxml2) - # This is for the case where a distro provides a non standard (Debug/Release) config type - # eg Debian's config file is tinyxml2ConfigTargets-none.cmake - # convert this back to either DEBUG/RELEASE or just RELEASE - # we only do this because we use find_package_handle_standard_args for config time output - # and it isnt capable of handling TARGETS, so we have to extract the info - get_target_property(_TINYXML2_CONFIGURATIONS tinyxml2::tinyxml2 IMPORTED_CONFIGURATIONS) - foreach(_tinyxml2_config IN LISTS _TINYXML2_CONFIGURATIONS) - # Some non standard config (eg None on Debian) - # Just set to RELEASE var so select_library_configurations can continue to work its magic - string(TOUPPER ${_tinyxml2_config} _tinyxml2_config_UPPER) - if((NOT ${_tinyxml2_config_UPPER} STREQUAL "RELEASE") AND - (NOT ${_tinyxml2_config_UPPER} STREQUAL "DEBUG")) - get_target_property(TINYXML2_LIBRARY_RELEASE tinyxml2::tinyxml2 IMPORTED_LOCATION_${_tinyxml2_config_UPPER}) - else() - get_target_property(TINYXML2_LIBRARY_${_tinyxml2_config_UPPER} tinyxml2::tinyxml2 IMPORTED_LOCATION_${_tinyxml2_config_UPPER}) - endif() - endforeach() - - # Need this, as we may only get the existing TARGET from system and not build or use pkg-config - get_target_property(TINYXML2_INCLUDE_DIR tinyxml2::tinyxml2 INTERFACE_INCLUDE_DIRECTORIES) - endif() - include(SelectLibraryConfigurations) select_library_configurations(TINYXML2) unset(TINYXML2_LIBRARIES) @@ -117,24 +109,28 @@ if(NOT TARGET tinyxml2::tinyxml2) VERSION_VAR TINYXML2_VERSION) if(TinyXML2_FOUND) - if(NOT TARGET tinyxml2::tinyxml2) - add_library(tinyxml2::tinyxml2 UNKNOWN IMPORTED) + # cmake target and not building internal + if(TARGET tinyxml2::tinyxml2 AND NOT TARGET tinyxml2) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS tinyxml2::tinyxml2) + else() + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) if(TINYXML2_LIBRARY_RELEASE) - set_target_properties(tinyxml2::tinyxml2 PROPERTIES - IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION_RELEASE "${TINYXML2_LIBRARY_RELEASE}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${TINYXML2_LIBRARY_RELEASE}") endif() if(TINYXML2_LIBRARY_DEBUG) - set_target_properties(tinyxml2::tinyxml2 PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION_DEBUG "${TINYXML2_LIBRARY_DEBUG}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION_DEBUG "${TINYXML2_LIBRARY_DEBUG}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) endif() - set_target_properties(tinyxml2::tinyxml2 PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${TINYXML2_INCLUDE_DIR}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${TINYXML2_INCLUDE_DIR}") endif() if(TARGET tinyxml2) - add_dependencies(tinyxml2::tinyxml2 tinyxml2) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} tinyxml2) endif() # Add internal build target when a Multi Config Generator is used @@ -152,8 +148,9 @@ if(NOT TARGET tinyxml2::tinyxml2) endif() add_dependencies(build_internal_depends tinyxml2) endif() - + else() + if(TinyXML2_FIND_REQUIRED) + message(FATAL_ERROR "TinyXML2 libraries were not found. You may want to try -DENABLE_INTERNAL_TINYXML2=ON") + endif() endif() - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP tinyxml2::tinyxml2) endif() From bc8c308e7640c40a0b7da9cac4f5fdadcc2a4f46 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 15:08:56 +1000 Subject: [PATCH 107/651] [cmake][modules] FindUDEV cleanup and use core_target_link_libraries --- CMakeLists.txt | 2 +- cmake/modules/FindUDEV.cmake | 21 ++++++++----------- xbmc/cores/AudioEngine/CMakeLists.txt | 2 +- .../platform/linux/peripherals/CMakeLists.txt | 2 +- xbmc/platform/linux/storage/CMakeLists.txt | 2 +- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a50d75a130bbd..9cfc77719e39f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -276,7 +276,7 @@ if(NOT TARGET ${APP_NAME_LC}::MariaDBClient) core_optional_dep(MySqlClient) endif() -if(NOT TARGET UDEV::UDEV) +if(NOT TARGET ${APP_NAME_LC}::UDEV) core_optional_dep(LibUSB) endif() diff --git a/cmake/modules/FindUDEV.cmake b/cmake/modules/FindUDEV.cmake index 8187e0206d38f..5ad22f17c80d8 100644 --- a/cmake/modules/FindUDEV.cmake +++ b/cmake/modules/FindUDEV.cmake @@ -5,20 +5,18 @@ # # This will define the following target: # -# UDEV::UDEV - The UDEV library +# ${APP_NAME_LC}::UDEV - The UDEV library -if(NOT TARGET UDEV::UDEV) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(PC_UDEV libudev QUIET) endif() find_path(UDEV_INCLUDE_DIR NAMES libudev.h - HINTS ${PC_UDEV_INCLUDEDIR} - NO_CACHE) + HINTS ${PC_UDEV_INCLUDEDIR}) find_library(UDEV_LIBRARY NAMES udev - HINTS ${PC_UDEV_LIBDIR} - NO_CACHE) + HINTS ${PC_UDEV_LIBDIR}) set(UDEV_VERSION ${PC_UDEV_VERSION}) @@ -28,11 +26,10 @@ if(NOT TARGET UDEV::UDEV) VERSION_VAR UDEV_VERSION) if(UDEV_FOUND) - add_library(UDEV::UDEV UNKNOWN IMPORTED) - set_target_properties(UDEV::UDEV PROPERTIES - IMPORTED_LOCATION "${UDEV_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${UDEV_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAVE_LIBUDEV=1) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP UDEV::UDEV) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${UDEV_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${UDEV_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAVE_LIBUDEV) endif() endif() diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt index 593e1849d41a7..9185e1e81d7d0 100644 --- a/xbmc/cores/AudioEngine/CMakeLists.txt +++ b/xbmc/cores/AudioEngine/CMakeLists.txt @@ -59,7 +59,7 @@ if(TARGET ${APP_NAME_LC}::Alsa) list(APPEND HEADERS Sinks/alsa/ALSAHControlMonitor.h) endif() - if(TARGET UDEV::UDEV) + if(TARGET ${APP_NAME_LC}::UDEV) list(APPEND SOURCES Sinks/alsa/ALSADeviceMonitor.cpp) list(APPEND HEADERS Sinks/alsa/ALSADeviceMonitor.h) endif() diff --git a/xbmc/platform/linux/peripherals/CMakeLists.txt b/xbmc/platform/linux/peripherals/CMakeLists.txt index 554f753e2fb53..0706421ad0862 100644 --- a/xbmc/platform/linux/peripherals/CMakeLists.txt +++ b/xbmc/platform/linux/peripherals/CMakeLists.txt @@ -1,4 +1,4 @@ -if(TARGET UDEV::UDEV) +if(TARGET ${APP_NAME_LC}::UDEV) list(APPEND SOURCES PeripheralBusUSBLibUdev.cpp) list(APPEND HEADERS PeripheralBusUSBLibUdev.h) elseif(TARGET ${APP_NAME_LC}::LibUSB) diff --git a/xbmc/platform/linux/storage/CMakeLists.txt b/xbmc/platform/linux/storage/CMakeLists.txt index 9fa51a23a456f..ed21cbb32e08c 100644 --- a/xbmc/platform/linux/storage/CMakeLists.txt +++ b/xbmc/platform/linux/storage/CMakeLists.txt @@ -9,7 +9,7 @@ if(TARGET ${APP_NAME_LC}::DBus) UDisks2Provider.h) endif() -if(TARGET UDEV::UDEV) +if(TARGET ${APP_NAME_LC}::UDEV) list(APPEND SOURCES UDevProvider.cpp) list(APPEND HEADERS UDevProvider.h) endif() From b2610822ad759bcaebd24e13722adb3e95268cbd Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 15:35:28 +1000 Subject: [PATCH 108/651] [cmake][modules] FindUdfread cleanup and use core_target_link_libraries --- cmake/modules/FindUdfread.cmake | 108 ++++++++++++++++---------------- xbmc/filesystem/CMakeLists.txt | 2 +- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/cmake/modules/FindUdfread.cmake b/cmake/modules/FindUdfread.cmake index 5aadd9167708b..ff88b31b5cf2d 100644 --- a/cmake/modules/FindUdfread.cmake +++ b/cmake/modules/FindUdfread.cmake @@ -5,13 +5,30 @@ # # This will define the following target: # -# udfread::udfread - The libudfread library +# ${APP_NAME_LC}::Udfread - The libudfread library + +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + + macro(buildudfread) + set(UDFREAD_VERSION ${${MODULE}_VER}) + set(BUILD_NAME udfread_build) + + set(CONFIGURE_COMMAND autoreconf -vif && + ./configure + --enable-static + --disable-shared + --prefix=${DEPENDS_PATH}) + set(BUILD_IN_SOURCE 1) + + BUILD_DEP_TARGET() + endmacro() -if(NOT TARGET udfread::udfread) if(WIN32 OR WINDOWS_STORE) include(FindPackageMessage) - find_package(libudfread CONFIG) + find_package(libudfread CONFIG QUIET + HINTS ${DEPENDS_PATH}/lib/cmake + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) if(libudfread_FOUND) # Specifically tailored to kodi windows cmake config - Debug and RelWithDebInfo available @@ -21,14 +38,11 @@ if(NOT TARGET udfread::udfread) include(SelectLibraryConfigurations) select_library_configurations(UDFREAD) - - find_package_handle_standard_args(Udfread - REQUIRED_VARS UDFREAD_LIBRARY UDFREAD_INCLUDE_DIR - VERSION_VAR UDFREAD_VERSION) + unset(UDFREAD_LIBRARIES) endif() else() find_package(PkgConfig) - pkg_check_modules(udfread libudfread IMPORTED_TARGET GLOBAL QUIET) + pkg_check_modules(libudfread libudfread IMPORTED_TARGET GLOBAL QUIET) include(cmake/scripts/common/ModuleHelpers.cmake) @@ -37,56 +51,42 @@ if(NOT TARGET udfread::udfread) # Check for existing UDFREAD. If version >= UDFREAD-VERSION file version, dont build # A corner case, but if a linux/freebsd user WANTS to build internal udfread, build anyway - if(NOT udfread_FOUND OR - (udfread_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_UDFREAD) OR + if((libudfread_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_UDFREAD) OR ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_UDFREAD)) - - set(UDFREAD_VERSION ${${MODULE}_VER}) - set(BUILD_NAME udfread_build) - - set(CONFIGURE_COMMAND autoreconf -vif && - ./configure - --enable-static - --disable-shared - --prefix=${DEPENDS_PATH}) - set(BUILD_IN_SOURCE 1) - - BUILD_DEP_TARGET() - elseif(udfread_FOUND) - get_target_property(UDFREAD_LIBRARY PkgConfig::udfread INTERFACE_LINK_LIBRARIES) - get_target_property(UDFREAD_INCLUDE_DIR PkgConfig::udfread INTERFACE_INCLUDE_DIRECTORIES) - set(UDFREAD_VERSION ${udfread_VERSION}) - endif() - - if(udfread_FOUND OR TARGET udfread_build) - set(UDFREAD_FOUND ${udfread_FOUND}) - include(FindPackageMessage) - find_package_message(udfread "Found udfread: ${UDFREAD_LIBRARY} (version: \"${UDFREAD_VERSION}\")" "[${UDFREAD_LIBRARY}][${UDFREAD_INCLUDE_DIR}]") + buildudfread() + + else() + if(TARGET PkgConfig::libudfread) + get_target_property(UDFREAD_LIBRARY PkgConfig::libudfread INTERFACE_LINK_LIBRARIES) + get_target_property(UDFREAD_INCLUDE_DIR PkgConfig::libudfread INTERFACE_INCLUDE_DIRECTORIES) + set(UDFREAD_VERSION ${libudfread_VERSION}) + endif() endif() - endif() - # pkgconfig populate target that is sufficient version - if(TARGET PkgConfig::udfread AND NOT TARGET udfread_build) - add_library(udfread::udfread ALIAS PkgConfig::udfread) - set_target_properties(PkgConfig::udfread PROPERTIES - INTERFACE_COMPILE_DEFINITIONS HAS_UDFREAD=1) - # windows cmake config populated target - elseif(TARGET libudfread::libudfread) - add_library(udfread::udfread ALIAS libudfread::libudfread) - set_target_properties(libudfread::libudfread PROPERTIES - INTERFACE_COMPILE_DEFINITIONS HAS_UDFREAD=1) - # otherwise we are building - elseif(TARGET udfread_build) - add_library(udfread::udfread UNKNOWN IMPORTED) - set_target_properties(udfread::udfread PROPERTIES - IMPORTED_LOCATION "${UDFREAD_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${UDFREAD_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_UDFREAD=1) - add_dependencies(udfread::udfread udfread_build) - endif() + find_package_handle_standard_args(Udfread + REQUIRED_VARS UDFREAD_LIBRARY UDFREAD_INCLUDE_DIR + VERSION_VAR UDFREAD_VERSION) - if(TARGET udfread::udfread) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP udfread::udfread) + if(UDFREAD_FOUND) + # pkgconfig populate target that is sufficient version + if(TARGET PkgConfig::libudfread AND NOT TARGET udfread_build) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS PkgConfig::libudfread) + set_target_properties(PkgConfig::libudfread PROPERTIES + INTERFACE_COMPILE_DEFINITIONS HAS_UDFREAD) + # windows cmake config populated target + elseif(TARGET libudfread::libudfread) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS libudfread::libudfread) + set_target_properties(libudfread::libudfread PROPERTIES + INTERFACE_COMPILE_DEFINITIONS HAS_UDFREAD) + # otherwise we are building + elseif(TARGET udfread_build) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${UDFREAD_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${UDFREAD_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_UDFREAD) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} udfread_build) + endif() endif() endif() diff --git a/xbmc/filesystem/CMakeLists.txt b/xbmc/filesystem/CMakeLists.txt index 9d653ae0f9cbe..1416872da0b1a 100644 --- a/xbmc/filesystem/CMakeLists.txt +++ b/xbmc/filesystem/CMakeLists.txt @@ -131,7 +131,7 @@ if(ISO9660PP_FOUND) ISO9660File.h) endif() -if(TARGET udfread::udfread) +if(TARGET ${APP_NAME_LC}::Udfread) list(APPEND SOURCES UDFBlockInput.cpp UDFDirectory.cpp UDFFile.cpp) From dcec96dc89606dc5efcdd5e0830c493a197862f8 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 15:37:48 +1000 Subject: [PATCH 109/651] [cmake][modules] FindVDPAU cleanup and use core_target_link_libraries --- cmake/modules/FindVDPAU.cmake | 21 ++++++++----------- .../DVDCodecs/Video/CMakeLists.txt | 2 +- .../VideoRenderers/HwDecRender/CMakeLists.txt | 2 +- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/cmake/modules/FindVDPAU.cmake b/cmake/modules/FindVDPAU.cmake index 680a43f366223..bb19a5439383d 100644 --- a/cmake/modules/FindVDPAU.cmake +++ b/cmake/modules/FindVDPAU.cmake @@ -5,20 +5,18 @@ # # This will define the following target: # -# VDPAU::VDPAU - The VDPAU library +# ${APP_NAME_LC}::VDPAU - The VDPAU library -if(NOT TARGET VDPAU::VDPAU) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(PC_VDPAU vdpau QUIET) endif() find_path(VDPAU_INCLUDE_DIR NAMES vdpau/vdpau.h vdpau/vdpau_x11.h - HINTS ${PC_VDPAU_INCLUDEDIR} - NO_CACHE) + HINTS ${PC_VDPAU_INCLUDEDIR}) find_library(VDPAU_LIBRARY NAMES vdpau - HINTS ${PC_VDPAU_LIBDIR} - NO_CACHE) + HINTS ${PC_VDPAU_LIBDIR}) set(VDPAU_VERSION ${PC_VDPAU_VERSION}) @@ -28,11 +26,10 @@ if(NOT TARGET VDPAU::VDPAU) VERSION_VAR VDPAU_VERSION) if(VDPAU_FOUND) - add_library(VDPAU::VDPAU UNKNOWN IMPORTED) - set_target_properties(VDPAU::VDPAU PROPERTIES - IMPORTED_LOCATION "${VDPAU_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${VDPAU_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAVE_LIBVDPAU=1) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP VDPAU::VDPAU) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${VDPAU_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${VDPAU_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAVE_LIBVDPAU) endif() endif() diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt b/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt index 452ed873be3be..d21f6cc99567a 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt @@ -16,7 +16,7 @@ if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) list(APPEND HEADERS DXVA.h) endif() -if(TARGET VDPAU::VDPAU) +if(TARGET ${APP_NAME_LC}::VDPAU) list(APPEND SOURCES VDPAU.cpp) list(APPEND HEADERS VDPAU.h) endif() diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt index ac93f18d314a3..14bd61c5b8550 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt @@ -20,7 +20,7 @@ if(VAAPI_FOUND) endif() endif() -if(TARGET VDPAU::VDPAU) +if(TARGET ${APP_NAME_LC}::VDPAU) list(APPEND SOURCES RendererVDPAU.cpp VdpauGL.cpp) list(APPEND HEADERS RendererVDPAU.h From 48abdb5a32b610bcaa148295951252d69b8e0280 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 15:41:00 +1000 Subject: [PATCH 110/651] [cmake][modules] FindWebOSHelpers cleanup and use core_target_link_libraries --- cmake/modules/FindWebOSHelpers.cmake | 40 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/cmake/modules/FindWebOSHelpers.cmake b/cmake/modules/FindWebOSHelpers.cmake index 0c862b7a7913d..facfbcc03e1b6 100644 --- a/cmake/modules/FindWebOSHelpers.cmake +++ b/cmake/modules/FindWebOSHelpers.cmake @@ -5,29 +5,26 @@ # # This will define the following target: # -# WEBOSHELPERS::WEBOSHELPERS - The webOS helpers library - -if(NOT TARGET WEBOSHELPERS::WEBOSHELPERS) - - if(WebOSHelpers_FIND_VERSION) - if(WebOSHelpers_FIND_VERSION_EXACT) - set(WebOSHelpers_FIND_SPEC "=${WebOSHelpers_FIND_VERSION_COMPLETE}") - else() - set(WebOSHelpers_FIND_SPEC ">=${WebOSHelpers_FIND_VERSION_COMPLETE}") - endif() - endif() +# ${APP_NAME_LC}::WebOSHelpers - The webOS helpers library +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) if(PKG_CONFIG_FOUND) + if(WebOSHelpers_FIND_VERSION) + if(WebOSHelpers_FIND_VERSION_EXACT) + set(WebOSHelpers_FIND_SPEC "=${WebOSHelpers_FIND_VERSION_COMPLETE}") + else() + set(WebOSHelpers_FIND_SPEC ">=${WebOSHelpers_FIND_VERSION_COMPLETE}") + endif() + endif() + pkg_check_modules(PC_WEBOSHELPERS helpers${WebOSHelpers_FIND_SPEC} QUIET) endif() find_path(WEBOSHELPERS_INCLUDE_DIR NAMES webos-helpers/libhelpers.h - HINTS ${PC_WEBOSHELPERS_INCLUDEDIR} - NO_CACHE) + HINTS ${PC_WEBOSHELPERS_INCLUDEDIR}) find_library(WEBOSHELPERS_LIBRARY NAMES helpers - HINTS ${PC_WEBOSHELPERS_LIBDIR} - NO_CACHE) + HINTS ${PC_WEBOSHELPERS_LIBDIR}) set(WEBOSHELPERS_VERSION ${PC_WEBOSHELPERS_VERSION}) @@ -37,10 +34,13 @@ if(NOT TARGET WEBOSHELPERS::WEBOSHELPERS) VERSION_VAR WEBOSHELPERS_VERSION) if(WEBOSHELPERS_FOUND) - add_library(WEBOSHELPERS::WEBOSHELPERS UNKNOWN IMPORTED) - set_target_properties(WEBOSHELPERS::WEBOSHELPERS PROPERTIES - IMPORTED_LOCATION "${WEBOSHELPERS_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${WEBOSHELPERS_INCLUDE_DIR}") - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP WEBOSHELPERS::WEBOSHELPERS) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${WEBOSHELPERS_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${WEBOSHELPERS_INCLUDE_DIR}") + else() + if(WebOSHelpers_FIND_REQUIRED) + message(FATAL_ERROR "WebOSHelpers libraries were not found.") + endif() endif() endif() From 0611695a346598dd1eddbef0520c94ad337d7235 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 15:44:47 +1000 Subject: [PATCH 111/651] [cmake][modules] FindX cleanup and use core_target_link_libraries --- cmake/modules/FindX.cmake | 37 +++++++++++--------------- cmake/scripts/linux/ExtraTargets.cmake | 4 +-- cmake/scripts/linux/Install.cmake | 4 +-- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/cmake/modules/FindX.cmake b/cmake/modules/FindX.cmake index 0dd69405f48b0..cec47008378d9 100644 --- a/cmake/modules/FindX.cmake +++ b/cmake/modules/FindX.cmake @@ -5,24 +5,21 @@ # # This will define the following targets: # -# X::X - The X11 library -# X::Xext - The X11 extension library +# ${APP_NAME_LC}::X - The X11 library +# ${APP_NAME_LC}::Xext - The X11 extension library -if(NOT TARGET X::X) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(PC_X x11 xext QUIET) endif() find_path(X_INCLUDE_DIR NAMES X11/Xlib.h - HINTS ${PC_X_x11_INCLUDEDIR} - NO_CACHE) + HINTS ${PC_X_x11_INCLUDEDIR}) find_library(X_LIBRARY NAMES X11 - HINTS ${PC_X_x11_LIBDIR} - NO_CACHE) + HINTS ${PC_X_x11_LIBDIR}) find_library(X_EXT_LIBRARY NAMES Xext - HINTS ${PC_X_xext_LIBDIR} - NO_CACHE) + HINTS ${PC_X_xext_LIBDIR}) set(X_VERSION ${PC_X_x11_VERSION}) @@ -32,17 +29,15 @@ if(NOT TARGET X::X) VERSION_VAR X_VERSION) if(X_FOUND) - add_library(X::Xext UNKNOWN IMPORTED) - set_target_properties(X::Xext PROPERTIES - IMPORTED_LOCATION "${X_EXT_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${X_INCLUDE_DIR}") - add_library(X::X UNKNOWN IMPORTED) - set_target_properties(X::X PROPERTIES - IMPORTED_LOCATION "${X_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${X_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAVE_X11=1 - INTERFACE_LINK_LIBRARIES X::Xext) - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP X::X) + add_library(${APP_NAME_LC}::Xext UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::Xext PROPERTIES + IMPORTED_LOCATION "${X_EXT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${X_INCLUDE_DIR}") + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${X_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${X_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAVE_X11 + INTERFACE_LINK_LIBRARIES ${APP_NAME_LC}::Xext) endif() endif() diff --git a/cmake/scripts/linux/ExtraTargets.cmake b/cmake/scripts/linux/ExtraTargets.cmake index d3b3f1807c657..947c89301bc15 100644 --- a/cmake/scripts/linux/ExtraTargets.cmake +++ b/cmake/scripts/linux/ExtraTargets.cmake @@ -1,9 +1,9 @@ # xrandr -if(TARGET X::X AND TARGET XRandR::XRandR) +if(TARGET ${APP_NAME_LC}::X AND TARGET XRandR::XRandR) find_package(X QUIET) find_package(XRandR QUIET) add_executable(${APP_NAME_LC}-xrandr ${CMAKE_SOURCE_DIR}/xbmc-xrandr.c) - target_link_libraries(${APP_NAME_LC}-xrandr ${SYSTEM_LDFLAGS} X::X m XRandR::XRandR) + target_link_libraries(${APP_NAME_LC}-xrandr ${SYSTEM_LDFLAGS} ${APP_NAME_LC}::X m XRandR::XRandR) endif() # WiiRemote diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index efe7674b74a5a..c23983c68bb07 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -1,4 +1,4 @@ -if(X_FOUND) +if(TARGET ${APP_NAME_LC}::X) set(USE_X11 1) else() set(USE_X11 0) @@ -49,7 +49,7 @@ configure_file(${CMAKE_SOURCE_DIR}/tools/Linux/kodi.metainfo.xml.in install(TARGETS ${APP_NAME_LC} DESTINATION ${libdir}/${APP_NAME_LC} COMPONENT kodi-bin) -if(TARGET X::X AND TARGET XRandR::XRandR) +if(TARGET ${APP_NAME_LC}::X AND TARGET XRandR::XRandR) install(TARGETS ${APP_NAME_LC}-xrandr DESTINATION ${libdir}/${APP_NAME_LC} COMPONENT kodi-bin) From a9439e9a2ef1cb37dd959fcf93a6e638da3d32e3 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 15:46:13 +1000 Subject: [PATCH 112/651] [cmake][modules] FindXkbcommon cleanup and use core_target_link_libraries --- cmake/modules/FindXkbcommon.cmake | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/cmake/modules/FindXkbcommon.cmake b/cmake/modules/FindXkbcommon.cmake index 51916f36b62a4..5fbda7a1e85ee 100644 --- a/cmake/modules/FindXkbcommon.cmake +++ b/cmake/modules/FindXkbcommon.cmake @@ -4,20 +4,18 @@ # # This will define the following target: # -# XKBCOMMON::XKBCOMMON - The libxkbcommon library +# ${APP_NAME_LC}::Xkbcommon - The libxkbcommon library -if(NOT TARGET XKBCOMMON::XKBCOMMON) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(PC_XKBCOMMON xkbcommon QUIET) endif() find_path(XKBCOMMON_INCLUDE_DIR NAMES xkbcommon/xkbcommon.h - HINTS ${PC_XKBCOMMON_INCLUDEDIR} - NO_CACHE) + HINTS ${PC_XKBCOMMON_INCLUDEDIR}) find_library(XKBCOMMON_LIBRARY NAMES xkbcommon - HINTS ${PC_XKBCOMMON_LIBDIR} - NO_CACHE) + HINTS ${PC_XKBCOMMON_LIBDIR}) set(XKBCOMMON_VERSION ${PC_XKBCOMMON_VERSION}) @@ -27,10 +25,9 @@ if(NOT TARGET XKBCOMMON::XKBCOMMON) VERSION_VAR XKBCOMMON_VERSION) if(XKBCOMMON_FOUND) - add_library(XKBCOMMON::XKBCOMMON UNKNOWN IMPORTED) - set_target_properties(XKBCOMMON::XKBCOMMON PROPERTIES - IMPORTED_LOCATION "${XKBCOMMON_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${XKBCOMMON_INCLUDE_DIR}") - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP XKBCOMMON::XKBCOMMON) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${XKBCOMMON_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${XKBCOMMON_INCLUDE_DIR}") endif() endif() From c9b2a030801f697726362f5e3f872ec3951abbb9 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 15:50:50 +1000 Subject: [PATCH 113/651] [cmake][modules] FindXRandR cleanup and use core_target_link_libraries --- cmake/modules/FindXRandR.cmake | 23 +++++++++-------------- cmake/scripts/linux/ExtraTargets.cmake | 4 ++-- cmake/scripts/linux/Install.cmake | 2 +- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/cmake/modules/FindXRandR.cmake b/cmake/modules/FindXRandR.cmake index f0ad366d90038..c09f2bd3c10c8 100644 --- a/cmake/modules/FindXRandR.cmake +++ b/cmake/modules/FindXRandR.cmake @@ -5,10 +5,9 @@ # # This will define the following target: # -# XRandR::XRandR - The XRANDR library - -if(NOT TARGET XRandR::XRandR) +# ${APP_NAME_LC}::XRandR - The XRANDR library +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) if(PKG_CONFIG_FOUND) @@ -16,11 +15,9 @@ if(NOT TARGET XRandR::XRandR) endif() find_path(XRANDR_INCLUDE_DIR NAMES X11/extensions/Xrandr.h - HINTS ${PC_XRANDR_INCLUDEDIR} - NO_CACHE) + HINTS ${PC_XRANDR_INCLUDEDIR}) find_library(XRANDR_LIBRARY NAMES Xrandr - HINTS ${PC_XRANDR_LIBDIR} - NO_CACHE) + HINTS ${PC_XRANDR_LIBDIR}) set(XRANDR_VERSION ${PC_XRANDR_VERSION}) @@ -30,12 +27,10 @@ if(NOT TARGET XRandR::XRandR) VERSION_VAR XRANDR_VERSION) if(XRANDR_FOUND) - add_library(XRandR::XRandR UNKNOWN IMPORTED) - set_target_properties(XRandR::XRandR PROPERTIES - IMPORTED_LOCATION "${XRANDR_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${XRANDR_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAVE_LIBXRANDR=1) - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP XRandR::XRandR) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${XRANDR_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${XRANDR_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAVE_LIBXRANDR) endif() endif() diff --git a/cmake/scripts/linux/ExtraTargets.cmake b/cmake/scripts/linux/ExtraTargets.cmake index 947c89301bc15..c6ad66b18ea26 100644 --- a/cmake/scripts/linux/ExtraTargets.cmake +++ b/cmake/scripts/linux/ExtraTargets.cmake @@ -1,9 +1,9 @@ # xrandr -if(TARGET ${APP_NAME_LC}::X AND TARGET XRandR::XRandR) +if(TARGET ${APP_NAME_LC}::X AND TARGET ${APP_NAME_LC}::XRandR) find_package(X QUIET) find_package(XRandR QUIET) add_executable(${APP_NAME_LC}-xrandr ${CMAKE_SOURCE_DIR}/xbmc-xrandr.c) - target_link_libraries(${APP_NAME_LC}-xrandr ${SYSTEM_LDFLAGS} ${APP_NAME_LC}::X m XRandR::XRandR) + target_link_libraries(${APP_NAME_LC}-xrandr ${SYSTEM_LDFLAGS} ${APP_NAME_LC}::X m ${APP_NAME_LC}::XRandR) endif() # WiiRemote diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index c23983c68bb07..fba3211bc7e60 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -49,7 +49,7 @@ configure_file(${CMAKE_SOURCE_DIR}/tools/Linux/kodi.metainfo.xml.in install(TARGETS ${APP_NAME_LC} DESTINATION ${libdir}/${APP_NAME_LC} COMPONENT kodi-bin) -if(TARGET ${APP_NAME_LC}::X AND TARGET XRandR::XRandR) +if(TARGET ${APP_NAME_LC}::X AND TARGET ${APP_NAME_LC}::XRandR) install(TARGETS ${APP_NAME_LC}-xrandr DESTINATION ${libdir}/${APP_NAME_LC} COMPONENT kodi-bin) From ab8c0e646f0d973ae46782645dd96b25b6168f48 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 15:52:57 +1000 Subject: [PATCH 114/651] [cmake][modules] FindXSLT cleanup and use core_target_link_libraries --- cmake/modules/FindXSLT.cmake | 27 ++++++++++++++------------- xbmc/utils/CMakeLists.txt | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/cmake/modules/FindXSLT.cmake b/cmake/modules/FindXSLT.cmake index 34ef75e14cd22..1c24ab35cf73f 100644 --- a/cmake/modules/FindXSLT.cmake +++ b/cmake/modules/FindXSLT.cmake @@ -5,21 +5,23 @@ # # This will define the following target: # -# XSLT::XSLT - The XSLT library +# ${APP_NAME_LC}::XSLT - The XSLT library -if(NOT TARGET XSLT::XSLT) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(LibXml2 REQUIRED) find_package(PkgConfig) - if(PKG_CONFIG_FOUND) + if(PKG_CONFIG_FOUND AND NOT (WIN32 OR WINDOWS_STORE)) pkg_check_modules(PC_XSLT libxslt QUIET) endif() find_path(XSLT_INCLUDE_DIR NAMES libxslt/xslt.h - HINTS ${PC_XSLT_INCLUDEDIR}) + HINTS ${DEPENDS_PATH}/include ${PC_XSLT_INCLUDEDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) find_library(XSLT_LIBRARY NAMES xslt libxslt - HINTS ${PC_XSLT_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_XSLT_LIBDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) set(XSLT_VERSION ${PC_XSLT_VERSION}) @@ -29,13 +31,12 @@ if(NOT TARGET XSLT::XSLT) VERSION_VAR XSLT_VERSION) if(XSLT_FOUND) - add_library(XSLT::XSLT UNKNOWN IMPORTED) - set_target_properties(XSLT::XSLT PROPERTIES - IMPORTED_LOCATION "${XSLT_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${XSLT_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAVE_LIBXSLT=1) - - target_link_libraries(XSLT::XSLT INTERFACE LibXml2::LibXml2) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP XSLT::XSLT) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${XSLT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${XSLT_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAVE_LIBXSLT) + + target_link_libraries(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE LibXml2::LibXml2) endif() endif() diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index e0f70aa08b2e8..8fcad352af667 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -183,7 +183,7 @@ set(HEADERS ActorProtocol.h XMLUtils.h XTimeUtils.h) -if(TARGET XSLT::XSLT) +if(TARGET ${APP_NAME_LC}::XSLT) list(APPEND SOURCES XSLTUtils.cpp) list(APPEND HEADERS XSLTUtils.h) endif() From 90c8b0fdb7c4d2bd939bfcf4b18ccb923d653334 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 17:20:43 +1000 Subject: [PATCH 115/651] [cmake][modules] FindGLU update to target usage --- cmake/modules/FindGLU.cmake | 46 +++++++++++++------------- cmake/scripts/linux/ExtraTargets.cmake | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/cmake/modules/FindGLU.cmake b/cmake/modules/FindGLU.cmake index 5d232970e0ff0..900f30baeb939 100644 --- a/cmake/modules/FindGLU.cmake +++ b/cmake/modules/FindGLU.cmake @@ -3,33 +3,33 @@ # ----- # Finds the GLU library # -# This will define the following variables:: -# -# GLU_FOUND - system has GLU -# GLU_INCLUDE_DIRS - the GLU include directory -# GLU_LIBRARIES - the GLU libraries -# GLU_DEFINITIONS - the GLU definitions +# This will define the following target: # +# ${APP_NAME_LC}::GLU - The GLU library -find_package(PkgConfig) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_GLU glu QUIET) -endif() + find_package(PkgConfig) -find_path(GLU_INCLUDE_DIR NAMES GL/glu.h - HINTS ${PC_GLU_INCLUDEDIR}) -find_library(GLU_LIBRARY NAMES GLU - HINTS ${PC_GLU_LIBDIR}) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_GLU glu QUIET) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(GLU - REQUIRED_VARS GLU_LIBRARY GLU_INCLUDE_DIR) + find_path(GLU_INCLUDE_DIR NAMES GL/glu.h + HINTS ${PC_GLU_INCLUDEDIR}) + find_library(GLU_LIBRARY NAMES GLU + HINTS ${PC_GLU_LIBDIR}) -if(GLU_FOUND) - set(GLU_LIBRARIES ${GLU_LIBRARY}) - set(GLU_INCLUDE_DIRS ${GLU_INCLUDE_DIR}) - set(GLU_DEFINITIONS -DHAS_GLU=1) -endif() + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(GLU + REQUIRED_VARS GLU_LIBRARY GLU_INCLUDE_DIR) -mark_as_advanced(GLU_INCLUDE_DIR GLU_LIBRARY) + if(GLU_FOUND) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${GLU_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${GLU_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_GLU) + endif() + +endif() diff --git a/cmake/scripts/linux/ExtraTargets.cmake b/cmake/scripts/linux/ExtraTargets.cmake index c6ad66b18ea26..15756a9b23b30 100644 --- a/cmake/scripts/linux/ExtraTargets.cmake +++ b/cmake/scripts/linux/ExtraTargets.cmake @@ -10,7 +10,7 @@ endif() if(ENABLE_EVENTCLIENTS AND TARGET ${APP_NAME_LC}::Bluetooth) find_package(CWiid QUIET) find_package(GLU QUIET) - if(CWIID_FOUND AND GLU_FOUND) + if(CWIID_FOUND AND TARGET ${APP_NAME_LC}::GLU) add_subdirectory(${CMAKE_SOURCE_DIR}/tools/EventClients/Clients/WiiRemote build/WiiRemote) endif() endif() From 44bbe622f1ad619b16e769a50d776e0522aec8b6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 17:21:07 +1000 Subject: [PATCH 116/651] [cmake][modules] FindGLU remove unused COMPILE_DEFINITION --- cmake/modules/FindGLU.cmake | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmake/modules/FindGLU.cmake b/cmake/modules/FindGLU.cmake index 900f30baeb939..68ec1eb2eb77a 100644 --- a/cmake/modules/FindGLU.cmake +++ b/cmake/modules/FindGLU.cmake @@ -28,8 +28,7 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES IMPORTED_LOCATION "${GLU_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${GLU_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_GLU) + INTERFACE_INCLUDE_DIRECTORIES "${GLU_INCLUDE_DIR}") endif() endif() From 7ba52133d5b73b9a360e81c68cc349e5f86e4664 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 17:24:13 +1000 Subject: [PATCH 117/651] [cmake][modules] FindGLX update to target usage --- cmake/modules/FindGLX.cmake | 51 ++++++++++++++----------------- xbmc/windowing/X11/CMakeLists.txt | 2 +- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/cmake/modules/FindGLX.cmake b/cmake/modules/FindGLX.cmake index 25c650e7048a8..67e19f4ebaa64 100644 --- a/cmake/modules/FindGLX.cmake +++ b/cmake/modules/FindGLX.cmake @@ -3,39 +3,34 @@ # ----- # Finds the GLX library # -# This will define the following variables:: +# This will define the following target: # -# GLX_FOUND - system has GLX -# GLX_INCLUDE_DIRS - the GLX include directory -# GLX_LIBRARIES - the GLX libraries -# GLX_DEFINITIONS - the GLX definitions -# -# and the following imported targets:: -# -# GLX::GLX - The GLX library +# ${APP_NAME_LC}::GLX - The GLX library -find_package(PkgConfig) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + find_package(PkgConfig) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_GLX glx QUIET) -endif() + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_GLX glx QUIET) + endif() -find_path(GLX_INCLUDE_DIR NAMES GL/glx.h - HINTS ${PC_GLX_INCLUDEDIR}) -find_library(GLX_LIBRARY NAMES GL - HINTS ${PC_GLX_LIBDIR}) + find_path(GLX_INCLUDE_DIR NAMES GL/glx.h + HINTS ${PC_GLX_INCLUDEDIR}) + find_library(GLX_LIBRARY NAMES GL + HINTS ${PC_GLX_LIBDIR}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(GLX - REQUIRED_VARS GLX_LIBRARY GLX_INCLUDE_DIR) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(GLX + REQUIRED_VARS GLX_LIBRARY GLX_INCLUDE_DIR) -if(GLX_FOUND) - list(APPEND GL_INTERFACES_LIST glx) - set(GL_INTERFACES_LIST ${GL_INTERFACES_LIST} PARENT_SCOPE) + if(GLX_FOUND) + list(APPEND GL_INTERFACES_LIST glx) + set(GL_INTERFACES_LIST ${GL_INTERFACES_LIST} PARENT_SCOPE) - set(GLX_LIBRARIES ${GLX_LIBRARY}) - set(GLX_INCLUDE_DIRS ${GLX_INCLUDE_DIR}) - set(GLX_DEFINITIONS -DHAS_GLX=1) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${GLX_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${GLX_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_GLX) + endif() endif() - -mark_as_advanced(GLX_INCLUDE_DIR GLX_LIBRARY) diff --git a/xbmc/windowing/X11/CMakeLists.txt b/xbmc/windowing/X11/CMakeLists.txt index b96ff8271aa54..b7d35d9bb5fa5 100644 --- a/xbmc/windowing/X11/CMakeLists.txt +++ b/xbmc/windowing/X11/CMakeLists.txt @@ -16,7 +16,7 @@ set(HEADERS GLContext.h XRandR.h X11DPMSSupport.h) -if(GLX_FOUND) +if(TARGET ${APP_NAME_LC}::GLX) list(APPEND SOURCES GLContextGLX.cpp VideoSyncGLX.cpp) list(APPEND HEADERS GLContextGLX.h From 15cd71aff10b3949100b2fad7286de61b99b6487 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 17:42:09 +1000 Subject: [PATCH 118/651] [cmake][modules] FindAtomic update to target usage --- cmake/modules/FindAtomic.cmake | 75 +++++++++++++++------------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/cmake/modules/FindAtomic.cmake b/cmake/modules/FindAtomic.cmake index 8ea3c815d74b1..91e0b39c13a8f 100644 --- a/cmake/modules/FindAtomic.cmake +++ b/cmake/modules/FindAtomic.cmake @@ -3,54 +3,45 @@ # ----- # Finds the ATOMIC library # -# This will define the following variables:: +# This will define the following target: # -# ATOMIC_FOUND - system has ATOMIC -# ATOMIC_LIBRARIES - the ATOMIC libraries -# -# and the following imported targets:: -# -# ATOMIC::ATOMIC - The ATOMIC library - +# ${APP_NAME_LC}::ATOMIC - The ATOMIC library -include(CheckCXXSourceCompiles) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + include(CheckCXXSourceCompiles) + include(FindPackageMessage) -set(atomic_code - " - #include - #include - std::atomic n8 (0); // riscv64 - std::atomic n64 (0); // armel, mipsel, powerpc - int main() { - ++n8; - ++n64; - return 0; - }") + set(atomic_code + " + #include + #include + std::atomic n8 (0); // riscv64 + std::atomic n64 (0); // armel, mipsel, powerpc + int main() { + ++n8; + ++n64; + return 0; + }") -check_cxx_source_compiles("${atomic_code}" ATOMIC_LOCK_FREE_INSTRUCTIONS) + check_cxx_source_compiles("${atomic_code}" ATOMIC_LOCK_FREE_INSTRUCTIONS) -if(ATOMIC_LOCK_FREE_INSTRUCTIONS) - set(ATOMIC_FOUND TRUE) - set(ATOMIC_LIBRARIES) -else() - set(CMAKE_REQUIRED_LIBRARIES "-latomic") - check_cxx_source_compiles("${atomic_code}" ATOMIC_IN_LIBRARY) - set(CMAKE_REQUIRED_LIBRARIES) - if(ATOMIC_IN_LIBRARY) - set(ATOMIC_LIBRARY atomic) - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(Atomic DEFAULT_MSG ATOMIC_LIBRARY) - set(ATOMIC_LIBRARIES ${ATOMIC_LIBRARY}) - if(NOT TARGET ATOMIC::ATOMIC) - add_library(ATOMIC::ATOMIC UNKNOWN IMPORTED) - set_target_properties(ATOMIC::ATOMIC PROPERTIES - IMPORTED_LOCATION "${ATOMIC_LIBRARY}") - endif() - unset(ATOMIC_LIBRARY) + if(ATOMIC_LOCK_FREE_INSTRUCTIONS) + find_package_message(Atomic "Found Atomic: Lock Free" "") else() - if(Atomic_FIND_REQUIRED) - message(FATAL_ERROR "Neither lock free instructions nor -latomic found.") + set(CMAKE_REQUIRED_LIBRARIES "-latomic") + check_cxx_source_compiles("${atomic_code}" ATOMIC_IN_LIBRARY) + set(CMAKE_REQUIRED_LIBRARIES) + if(ATOMIC_IN_LIBRARY) + find_package_message(Atomic "Found Atomic library: -latomic" "") + + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "-latomic") + else() + if(Atomic_FIND_REQUIRED) + message(FATAL_ERROR "Neither lock free instructions nor -latomic found.") + endif() endif() endif() + unset(atomic_code) endif() -unset(atomic_code) From 7658b607954ac3e6734ba54b1d50c78f41154ae9 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 18:22:01 +1000 Subject: [PATCH 119/651] [cmake][modules] FindCdio update to target usage --- cmake/modules/FindCdio.cmake | 86 +++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 36 deletions(-) diff --git a/cmake/modules/FindCdio.cmake b/cmake/modules/FindCdio.cmake index 16a7aef9c613f..5f8b33c8d14b0 100644 --- a/cmake/modules/FindCdio.cmake +++ b/cmake/modules/FindCdio.cmake @@ -3,42 +3,56 @@ # -------- # Finds the cdio library # -# This will define the following variables:: +# This will define the following target: +# +# ${APP_NAME_LC}::Cdio - The LibCap library # -# CDIO_FOUND - system has cdio -# CDIO_INCLUDE_DIRS - the cdio include directory -# CDIO_LIBRARIES - the cdio libraries - -find_package(PkgConfig) - -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_CDIO libcdio>=0.80 QUIET) - pkg_check_modules(PC_CDIOPP libcdio++>=2.1.0 QUIET) -endif() - -find_path(CDIO_INCLUDE_DIR NAMES cdio/cdio.h - HINTS ${PC_CDIO_INCLUDEDIR}) - -find_library(CDIO_LIBRARY NAMES cdio libcdio - HINTS ${PC_CDIO_LIBDIR}) - -if(DEFINED PC_CDIO_VERSION AND DEFINED PC_CDIOPP_VERSION AND NOT "${PC_CDIO_VERSION}" VERSION_EQUAL "${PC_CDIOPP_VERSION}") - message(WARNING "Detected libcdio (${PC_CDIO_VERSION}) and libcdio++ (${PC_CDIOPP_VERSION}) version mismatch. libcdio++ will not be used.") -else() - find_path(CDIOPP_INCLUDE_DIR NAMES cdio++/cdio.hpp - HINTS ${PC_CDIOPP_INCLUDEDIR} ${CDIO_INCLUDE_DIR}) - - set(CDIO_VERSION ${PC_CDIO_VERSION}) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Cdio - REQUIRED_VARS CDIO_LIBRARY CDIO_INCLUDE_DIR - VERSION_VAR CDIO_VERSION) -if(CDIO_FOUND) - set(CDIO_LIBRARIES ${CDIO_LIBRARY}) - set(CDIO_INCLUDE_DIRS ${CDIO_INCLUDE_DIR}) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + find_package(PkgConfig) + + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_CDIO libcdio>=0.80 QUIET) + pkg_check_modules(PC_CDIOPP libcdio++>=2.1.0 QUIET) + endif() + + find_path(CDIO_INCLUDE_DIR NAMES cdio/cdio.h + HINTS ${PC_CDIO_INCLUDEDIR}) + + find_library(CDIO_LIBRARY NAMES cdio libcdio + HINTS ${PC_CDIO_LIBDIR}) + + if(DEFINED PC_CDIO_VERSION AND DEFINED PC_CDIOPP_VERSION AND NOT "${PC_CDIO_VERSION}" VERSION_EQUAL "${PC_CDIOPP_VERSION}") + message(WARNING "Detected libcdio (${PC_CDIO_VERSION}) and libcdio++ (${PC_CDIOPP_VERSION}) version mismatch. libcdio++ will not be used.") + else() + find_path(CDIOPP_INCLUDE_DIR NAMES cdio++/cdio.hpp + HINTS ${PC_CDIOPP_INCLUDEDIR} ${CDIO_INCLUDE_DIR}) + + set(CDIO_VERSION ${PC_CDIO_VERSION}) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Cdio + REQUIRED_VARS CDIO_LIBRARY CDIO_INCLUDE_DIR + VERSION_VAR CDIO_VERSION) + + if(CDIO_FOUND) + + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${CDIO_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${CDIO_INCLUDE_DIR}") + + if(CDIOPP_INCLUDE_DIR) + add_library(${APP_NAME_LC}::CdioPP INTERFACE IMPORTED) + set_target_properties(${APP_NAME_LC}::CdioPP PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${CDIOPP_INCLUDE_DIR}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES "${APP_NAME_LC}::CdioPP") + endif() + else() + if(Cdio_FIND_REQUIRED) + message(FATAL_ERROR "cdio library not found.") + endif() + endif() endif() - -mark_as_advanced(CDIO_INCLUDE_DIR CDIOPP_INCLUDE_DIR CDIO_LIBRARY) From 1aadf31a78f6c15b64e9f941345c6ad9d8c06d42 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 18:35:03 +1000 Subject: [PATCH 120/651] [cmake][modules] FindCWiid update to target usage --- cmake/modules/FindCWiid.cmake | 50 ++++++++----------- cmake/scripts/linux/ExtraTargets.cmake | 2 +- cmake/scripts/linux/Install.cmake | 2 +- .../Clients/WiiRemote/CMakeLists.txt | 4 +- 4 files changed, 24 insertions(+), 34 deletions(-) diff --git a/cmake/modules/FindCWiid.cmake b/cmake/modules/FindCWiid.cmake index e1cb02260c5ab..db8a67ed82d05 100644 --- a/cmake/modules/FindCWiid.cmake +++ b/cmake/modules/FindCWiid.cmake @@ -3,42 +3,32 @@ # --------- # Finds the CWiid library # -# This will define the following variables:: +# This will define the following target: # -# CWIID_FOUND - system has CWiid -# CWIID_INCLUDE_DIRS - the CWiid include directory -# CWIID_LIBRARIES - the CWiid libraries -# -# and the following imported targets:: -# -# CWiid::CWiid - The CWiid library +# ${APP_NAME_LC}::CWiid - The CWiid library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_CWIID cwiid QUIET) -endif() +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_CWIID cwiid QUIET) + endif() -find_path(CWIID_INCLUDE_DIR NAMES cwiid.h - PATHS ${PC_CWIID_INCLUDEDIR}) -find_library(CWIID_LIBRARY NAMES cwiid - PATHS ${PC_CWIID_LIBDIR}) + find_path(CWIID_INCLUDE_DIR NAMES cwiid.h + PATHS ${PC_CWIID_INCLUDEDIR}) + find_library(CWIID_LIBRARY NAMES cwiid + PATHS ${PC_CWIID_LIBDIR}) -set(CWIID_VERSION ${PC_CWIID_VERSION}) + set(CWIID_VERSION ${PC_CWIID_VERSION}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(CWiid - REQUIRED_VARS CWIID_LIBRARY CWIID_INCLUDE_DIR - VERSION_VAR CWIID_VERSION) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(CWiid + REQUIRED_VARS CWIID_LIBRARY CWIID_INCLUDE_DIR + VERSION_VAR CWIID_VERSION) -if(CWIID_FOUND) - set(CWIID_INCLUDE_DIRS ${CWIID_INCLUDE_DIR}) - set(CWIID_LIBRARIES ${CWIID_LIBRARY}) + if(CWIID_FOUND) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${CWIID_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${CWIID_INCLUDE_DIR}") - if(NOT TARGET CWiid::CWiid) - add_library(CWiid::CWiid UNKNOWN IMPORTED) - set_target_properties(CWiid::CWiid PROPERTIES - IMPORTED_LOCATION "${CWIID_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${CWIID_INCLUDE_DIR}") endif() endif() - -mark_as_advanced(CWIID_INCLUDE_DIR CWIID_LIBRARY) diff --git a/cmake/scripts/linux/ExtraTargets.cmake b/cmake/scripts/linux/ExtraTargets.cmake index 15756a9b23b30..f402f7c491126 100644 --- a/cmake/scripts/linux/ExtraTargets.cmake +++ b/cmake/scripts/linux/ExtraTargets.cmake @@ -10,7 +10,7 @@ endif() if(ENABLE_EVENTCLIENTS AND TARGET ${APP_NAME_LC}::Bluetooth) find_package(CWiid QUIET) find_package(GLU QUIET) - if(CWIID_FOUND AND TARGET ${APP_NAME_LC}::GLU) + if(TARGET ${APP_NAME_LC}::CWiid AND TARGET ${APP_NAME_LC}::GLU) add_subdirectory(${CMAKE_SOURCE_DIR}/tools/EventClients/Clients/WiiRemote build/WiiRemote) endif() endif() diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index fba3211bc7e60..5b4216badb30c 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -275,7 +275,7 @@ if(ENABLE_EVENTCLIENTS) DESTINATION ${bindir} COMPONENT kodi-eventclients-ps3) - if(TARGET ${APP_NAME_LC}::Bluetooth AND CWIID_FOUND AND GLU_FOUND) + if(TARGET ${APP_NAME_LC}::Bluetooth AND ${APP_NAME_LC}::CWiid AND GLU_FOUND) # Install kodi-eventclients-wiiremote install(PROGRAMS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/WiiRemote/${APP_NAME_LC}-wiiremote DESTINATION ${bindir} diff --git a/tools/EventClients/Clients/WiiRemote/CMakeLists.txt b/tools/EventClients/Clients/WiiRemote/CMakeLists.txt index 2a8e41cc913d2..f638b56c76acd 100644 --- a/tools/EventClients/Clients/WiiRemote/CMakeLists.txt +++ b/tools/EventClients/Clients/WiiRemote/CMakeLists.txt @@ -12,12 +12,12 @@ add_executable(${APP_NAME_LC}-wiiremote ${SOURCES} ${HEADERS}) target_include_directories(${APP_NAME_LC}-wiiremote PRIVATE $ - ${CWIID_INCLUDE_DIRS}) + $) target_link_libraries(${APP_NAME_LC}-wiiremote PRIVATE ${SYSTEM_LDFLAGS} ${APP_NAME_LC}::Bluetooth - ${CWIID_LIBRARIES}) + ${APP_NAME_LC}::CWiid) target_compile_options(${APP_NAME_LC}-wiiremote PRIVATE ${ARCH_DEFINES}) From 4873052cca1b9f46da3cac817ffe5b2d6c46f6d6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 18:39:32 +1000 Subject: [PATCH 121/651] [cmake][modules] FindDav1d update to target usage --- cmake/modules/FindDav1d.cmake | 23 ++++++++++------------- cmake/modules/FindFFMPEG.cmake | 6 +++--- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/cmake/modules/FindDav1d.cmake b/cmake/modules/FindDav1d.cmake index 8bfd4153814cf..f87db06d0a073 100644 --- a/cmake/modules/FindDav1d.cmake +++ b/cmake/modules/FindDav1d.cmake @@ -5,10 +5,10 @@ # # This will define the following target: # -# dav1d::dav1d - The dav1d library +# ${APP_NAME_LC}::Dav1d - The dav1d library -if(NOT TARGET dav1d::dav1d) - if(ENABLE_INTERNAL_DAV1D) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + if(ENABLE_INTERNAL_DAV1D AND NOT (WIN32 OR WINDOWS_STORE)) include(cmake/scripts/common/ModuleHelpers.cmake) set(MODULE_LC dav1d) @@ -43,13 +43,11 @@ if(NOT TARGET dav1d::dav1d) find_library(DAV1D_LIBRARY NAMES dav1d libdav1d HINTS ${DEPENDS_PATH}/lib ${PC_DAV1D_LIBDIR} - ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} - NO_CACHE) + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) find_path(DAV1D_INCLUDE_DIR NAMES dav1d/dav1d.h HINTS ${DEPENDS_PATH}/include ${PC_DAV1D_INCLUDEDIR} - ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} - NO_CACHE) + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) set(DAV1D_VERSION ${PC_DAV1D_VERSION}) endif() @@ -60,14 +58,13 @@ if(NOT TARGET dav1d::dav1d) VERSION_VAR DAV1D_VERSION) if(DAV1D_FOUND) - add_library(dav1d::dav1d UNKNOWN IMPORTED) - set_target_properties(dav1d::dav1d PROPERTIES - IMPORTED_LOCATION "${DAV1D_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${DAV1D_INCLUDE_DIR}") - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP dav1d::dav1d) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${DAV1D_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${DAV1D_INCLUDE_DIR}") if(TARGET dav1d) - add_dependencies(dav1d::dav1d dav1d) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} dav1d) endif() endif() endif() diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake index 76857b0ccab66..e04b52c97cbbc 100644 --- a/cmake/modules/FindFFMPEG.cmake +++ b/cmake/modules/FindFFMPEG.cmake @@ -32,7 +32,7 @@ macro(buildFFMPEG) # Check for dependencies - Must be done before SETUP_BUILD_VARS get_libversion_data("dav1d" "target") find_package(Dav1d ${LIB_DAV1D_VER} MODULE) - if(NOT TARGET dav1d::dav1d) + if(NOT TARGET ${APP_NAME_LC}::Dav1d) message(STATUS "dav1d not found, internal ffmpeg build will be missing AV1 support!") else() set(FFMPEG_OPTIONS -DENABLE_DAV1D=ON) @@ -93,8 +93,8 @@ macro(buildFFMPEG) BUILD_DEP_TARGET() - if(TARGET dav1d::dav1d) - add_dependencies(ffmpeg dav1d::dav1d) + if(TARGET ${APP_NAME_LC}::Dav1d) + add_dependencies(ffmpeg ${APP_NAME_LC}::Dav1d) endif() find_program(BASH_COMMAND bash) From 841024852b4322d03fb7c5b002f5a366d0fc8fbd Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 18:42:14 +1000 Subject: [PATCH 122/651] [cmake][modules] FindEpollShim update to target usage --- cmake/modules/FindEpollShim.cmake | 40 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/cmake/modules/FindEpollShim.cmake b/cmake/modules/FindEpollShim.cmake index a4c705f9f2364..abd37884edd8e 100644 --- a/cmake/modules/FindEpollShim.cmake +++ b/cmake/modules/FindEpollShim.cmake @@ -2,30 +2,30 @@ # ------------- # Finds the epoll-shim library # -# This will define the following variables:: +# This will define the following target: # -# EPOLLSHIM_FOUND - the system has epoll-shim -# EPOLLSHIM_INCLUDE_DIR - the epoll-shim include directory -# EPOLLSHIM_LIBRARY - the epoll-shim library +# ${APP_NAME_LC}::EpollShim - The epoll-shim library -find_package(PkgConfig) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + find_package(PkgConfig) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_EPOLLSHIM epoll-shim QUIET) -endif() + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_EPOLLSHIM epoll-shim QUIET) + endif() -find_path(EPOLLSHIM_INCLUDE_DIR NAMES sys/epoll.h - HINTS ${PC_EPOLLSHIM_INCLUDE_DIRS}) -find_library(EPOLLSHIM_LIBRARY NAMES epoll-shim - HINTS ${PC_EPOLLSHIM_LIBDIR}) + find_path(EPOLLSHIM_INCLUDE_DIR NAMES sys/epoll.h + HINTS ${PC_EPOLLSHIM_INCLUDE_DIRS}) + find_library(EPOLLSHIM_LIBRARY NAMES epoll-shim + HINTS ${PC_EPOLLSHIM_LIBDIR}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(EpollShim - REQUIRED_VARS EPOLLSHIM_LIBRARY EPOLLSHIM_INCLUDE_DIR) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(EpollShim + REQUIRED_VARS EPOLLSHIM_LIBRARY EPOLLSHIM_INCLUDE_DIR) -if(EPOLLSHIM_FOUND) - set(EPOLLSHIM_INCLUDE_DIRS ${EPOLLSHIM_INCLUDE_DIR}) - set(EPOLLSHIM_LIBRARIES ${EPOLLSHIM_LIBRARY}) + if(EPOLLSHIM_FOUND) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${EPOLLSHIM_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${EPOLLSHIM_INCLUDE_DIR}") + endif() endif() - -mark_as_advanced(EPOLLSHIM_INCLUDE_DIR EPOLLSHIM_LIBRARY) From 50292633005ad94452fc4aa5026413d79db5c7c3 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 19:07:13 +1000 Subject: [PATCH 123/651] [cmake][modules] FindFriBidi update target usage --- cmake/modules/FindFriBidi.cmake | 72 +++++++++++++++------------------ 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/cmake/modules/FindFriBidi.cmake b/cmake/modules/FindFriBidi.cmake index 310152f846357..6a265e1c2a3b8 100644 --- a/cmake/modules/FindFriBidi.cmake +++ b/cmake/modules/FindFriBidi.cmake @@ -3,52 +3,44 @@ # ----------- # Finds the GNU FriBidi library # -# This will define the following variables:: +# This will define the following target: # -# FRIBIDI_FOUND - system has FriBidi -# FRIBIDI_INCLUDE_DIRS - the FriBidi include directory -# FRIBIDI_LIBRARIES - the FriBidi libraries -# -# and the following imported targets:: -# -# FriBidi::FriBidi - The FriBidi library +# ${APP_NAME_LC}::Fribidi - The FriBidi library -find_package(PkgConfig) - -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_FRIBIDI fribidi QUIET) -endif() +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + find_package(PkgConfig) -find_path(FRIBIDI_INCLUDE_DIR NAMES fribidi.h - PATH_SUFFIXES fribidi - HINTS ${PC_FRIBIDI_INCLUDEDIR}) -find_library(FRIBIDI_LIBRARY NAMES fribidi libfribidi - HINTS ${PC_FRIBIDI_LIBDIR}) + if(PKG_CONFIG_FOUND AND NOT (WIN32 OR WINDOWS_STORE)) + pkg_check_modules(FRIBIDI fribidi IMPORTED_TARGET GLOBAL QUIET) -set(FRIBIDI_VERSION ${PC_FRIBIDI_VERSION}) + get_target_property(FRIBIDI_LIBRARY PkgConfig::FRIBIDI INTERFACE_LINK_LIBRARIES) + get_target_property(FRIBIDI_INCLUDE_DIR PkgConfig::FRIBIDI INTERFACE_INCLUDE_DIRECTORIES) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(FriBidi - REQUIRED_VARS FRIBIDI_LIBRARY FRIBIDI_INCLUDE_DIR - VERSION_VAR FRIBIDI_VERSION) - -if(FRIBIDI_FOUND) - set(FRIBIDI_LIBRARIES ${FRIBIDI_LIBRARY}) - set(FRIBIDI_INCLUDE_DIRS ${FRIBIDI_INCLUDE_DIR}) - if(PC_FRIBIDI_INCLUDE_DIRS) - list(APPEND FRIBIDI_INCLUDE_DIRS ${PC_FRIBIDI_INCLUDE_DIRS}) - endif() - if(PC_FRIBIDI_CFLAGS_OTHER) - set(FRIBIDI_DEFINITIONS ${PC_FRIBIDI_CFLAGS_OTHER}) + else() + find_path(FRIBIDI_INCLUDE_DIR NAMES fribidi.h + PATH_SUFFIXES fribidi + HINTS ${DEPENDS_PATH}/include) + find_library(FRIBIDI_LIBRARY NAMES fribidi libfribidi + HINTS ${DEPENDS_PATH}/lib) endif() - if(NOT TARGET FriBidi::FriBidi) - add_library(FriBidi::FriBidi UNKNOWN IMPORTED) - set_target_properties(FriBidi::FriBidi PROPERTIES - IMPORTED_LOCATION "${FRIBIDI_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${FRIBIDI_INCLUDE_DIRS}" - INTERFACE_COMPILE_OPTIONS "${FRIBIDI_DEFINITIONS}") + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(FriBidi + REQUIRED_VARS FRIBIDI_LIBRARY FRIBIDI_INCLUDE_DIR + VERSION_VAR FRIBIDI_VERSION) + + if(FRIBIDI_FOUND) + if(TARGET PkgConfig::FRIBIDI) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS PkgConfig::FRIBIDI) + else() + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${FRIBIDI_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FRIBIDI_INCLUDE_DIR}") + endif() + else() + if(FriBidi_FIND_REQUIRED) + message(FATAL_ERROR "FriBidi library was not found.") + endif() endif() endif() - -mark_as_advanced(FRIBIDI_INCLUDE_DIR FRIBIDI_LIBRARY) From 37bc4849eb5c5daf6a97813c0554e9f6933f5e12 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 22 Jun 2024 14:03:05 +1000 Subject: [PATCH 124/651] [cmake] Increase project cmake_minimum_required to 3.16 The expectation of a system FindGnuTLS is only valid when cmake 3.16+ is used Cmake 3.16 was released in 2019, and is available in Ubuntu 20.04 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cfc77719e39f..635b7b52185cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.16) if(WIN32) # Version 3.20 is required for multi-config generator expressions to work cmake_minimum_required(VERSION 3.20) From 5a5cf0843ef242e345c974bbd472fc67dd411866 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 19:23:14 +1000 Subject: [PATCH 125/651] [cmake][modules] Remove FindGnuTLS.cmake cmake has supplied a FindGnuTLS since cmake 3.16. use it --- cmake/modules/FindGnuTLS.cmake | 30 ------------------------------ cmake/modules/FindLibZip.cmake | 2 +- 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 cmake/modules/FindGnuTLS.cmake diff --git a/cmake/modules/FindGnuTLS.cmake b/cmake/modules/FindGnuTLS.cmake deleted file mode 100644 index b323e9e27787b..0000000000000 --- a/cmake/modules/FindGnuTLS.cmake +++ /dev/null @@ -1,30 +0,0 @@ -# - Try to find gnutls -# Once done this will define -# -# GNUTLS_FOUND - system has gnutls -# GNUTLS_INCLUDE_DIRS - the gnutls include directory -# GNUTLS_LIBRARIES - The gnutls libraries - -find_package(PkgConfig) - -if(PKG_CONFIG_FOUND) - pkg_check_modules(GNUTLS gnutls QUIET) -endif() - -if(NOT GNUTLS_FOUND) - find_path(GNUTLS_INCLUDE_DIRS gnutls/gnutls.h) - find_library(GNUTLS_LIBRARIES gnutls) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(GnuTLS DEFAULT_MSG GNUTLS_INCLUDE_DIRS GNUTLS_LIBRARIES) - -if(GNUTLS_FOUND) - list(APPEND GNUTLS_DEFINITIONS -DHAVE_GNUTLS=1) -else() - if(GNUTLS_FIND_REQUIRED) - message(FATAL_ERROR "GNUTLS Not Found.") - endif() -endif() - -mark_as_advanced(GNUTLS_INCLUDE_DIRS GNUTLS_LIBRARIES GNUTLS_DEFINITIONS) diff --git a/cmake/modules/FindLibZip.cmake b/cmake/modules/FindLibZip.cmake index c108970c34b7e..139514590e344 100644 --- a/cmake/modules/FindLibZip.cmake +++ b/cmake/modules/FindLibZip.cmake @@ -19,7 +19,7 @@ find_package(libzip CONFIG QUIET if(NOT LIBZIP_FOUND OR libzip_VERSION VERSION_LESS ${${MODULE}_VER}) # Check for dependencies - find_package(GnuTLS MODULE REQUIRED) + find_package(GnuTLS REQUIRED) # Eventually we will want Find modules for the following deps # bzip2 From 261836907befcee6cca2344193b830dd77295fd6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 19:32:02 +1000 Subject: [PATCH 126/651] [cmake][modules] FindFFMPEG cleanup and use core_target_link_libraries --- cmake/modules/FindFFMPEG.cmake | 26 ++++++++++++-------------- xbmc/cores/AudioEngine/CMakeLists.txt | 2 +- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake index e04b52c97cbbc..26dd6e8301da1 100644 --- a/cmake/modules/FindFFMPEG.cmake +++ b/cmake/modules/FindFFMPEG.cmake @@ -20,7 +20,7 @@ # -------- # This will define the following target: # -# ffmpeg::ffmpeg - The FFmpeg interface target +# ${APP_NAME_LC}::FFMPEG - The FFmpeg interface target # -------- # @@ -306,24 +306,22 @@ endif() if(FFMPEG_FOUND) set(_ffmpeg_definitions FFMPEG_VER_SHA=${FFMPEG_VERSION}) - if(NOT TARGET ffmpeg::ffmpeg) - add_library(ffmpeg::ffmpeg INTERFACE IMPORTED) - set_target_properties(ffmpeg::ffmpeg PROPERTIES + if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIRS}" INTERFACE_COMPILE_DEFINITIONS "${_ffmpeg_definitions}") endif() - target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libavcodec) - target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libavfilter) - target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libavformat) - target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libavutil) - target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libswscale) - target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libswresample) - target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libpostproc) + target_link_libraries(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE ffmpeg::libavcodec) + target_link_libraries(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE ffmpeg::libavfilter) + target_link_libraries(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE ffmpeg::libavformat) + target_link_libraries(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE ffmpeg::libavutil) + target_link_libraries(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE ffmpeg::libswscale) + target_link_libraries(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE ffmpeg::libswresample) + target_link_libraries(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE ffmpeg::libpostproc) if(TARGET ffmpeg) - add_dependencies(ffmpeg::ffmpeg ffmpeg) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ffmpeg) endif() - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP ffmpeg::ffmpeg) endif() diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt index 9185e1e81d7d0..f3eaf2711c9f1 100644 --- a/xbmc/cores/AudioEngine/CMakeLists.txt +++ b/xbmc/cores/AudioEngine/CMakeLists.txt @@ -98,7 +98,7 @@ if(TARGET ${APP_NAME_LC}::Sndio) list(APPEND HEADERS Sinks/AESinkSNDIO.h) endif() -if(TARGET ffmpeg::ffmpeg) +if(TARGET ${APP_NAME_LC}::FFMPEG) list(APPEND SOURCES Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp) list(APPEND HEADERS Engines/ActiveAE/ActiveAEResampleFFMPEG.h) endif() From 81f68da633d581dc6e142bc5ca6809a8997c3565 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 12 May 2024 19:57:39 +1000 Subject: [PATCH 127/651] [cmake][modules] FindIso9660pp update target usage --- cmake/modules/FindIso9660pp.cmake | 90 ++++++++++++++++++------------- xbmc/filesystem/CMakeLists.txt | 2 +- 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/cmake/modules/FindIso9660pp.cmake b/cmake/modules/FindIso9660pp.cmake index b6fbdc83e4998..b8fcd420f1840 100644 --- a/cmake/modules/FindIso9660pp.cmake +++ b/cmake/modules/FindIso9660pp.cmake @@ -3,52 +3,66 @@ # -------- # Finds the iso9660++ library # -# This will define the following variables:: +# This will define the following target: # -# ISO9660PP_FOUND - system has iso9660++ -# ISO9660PP_INCLUDE_DIRS - the iso9660++ include directory -# ISO9660PP_LIBRARIES - the iso9660++ libraries -# ISO9660PP_DEFINITIONS - the iso9660++ definitions - -if(Iso9660pp_FIND_VERSION) - if(Iso9660pp_FIND_VERSION_EXACT) - set(Iso9660pp_FIND_SPEC "=${Iso9660pp_FIND_VERSION_COMPLETE}") - else() - set(Iso9660pp_FIND_SPEC ">=${Iso9660pp_FIND_VERSION_COMPLETE}") - endif() -endif() +# ${APP_NAME_LC}::Iso9660pp - The Iso9660pp library -find_package(PkgConfig) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_ISO9660PP libiso9660++${Iso9660pp_FIND_SPEC} QUIET) - pkg_check_modules(PC_ISO9660 libiso9660${Iso9660pp_FIND_SPEC} QUIET) -endif() +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + find_package(Cdio) -find_package(Cdio) + if(Cdio_FOUND) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND AND NOT (WIN32 OR WINDOWS_STORE)) + if(Iso9660pp_FIND_VERSION) + if(Iso9660pp_FIND_VERSION_EXACT) + set(Iso9660pp_FIND_SPEC "=${Iso9660pp_FIND_VERSION_COMPLETE}") + else() + set(Iso9660pp_FIND_SPEC ">=${Iso9660pp_FIND_VERSION_COMPLETE}") + endif() + endif() -find_path(ISO9660PP_INCLUDE_DIR NAMES cdio++/iso9660.hpp - HINTS ${PC_ISO9660PP_INCLUDEDIR}) + pkg_check_modules(PC_ISO9660PP libiso9660++${Iso9660pp_FIND_SPEC} QUIET) + pkg_check_modules(PC_ISO9660 libiso9660${Iso9660pp_FIND_SPEC} QUIET) + endif() -find_library(ISO9660PP_LIBRARY NAMES libiso9660++ iso9660++ - HINTS ${PC_ISO9660PP_LIBDIR}) + find_path(ISO9660PP_INCLUDE_DIR NAMES cdio++/iso9660.hpp + HINTS ${DEPENDS_PATH}/include ${PC_ISO9660PP_INCLUDEDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) -find_path(ISO9660_INCLUDE_DIR NAMES cdio/iso9660.h - HINTS ${PC_ISO9660_INCLUDEDIR}) + find_library(ISO9660PP_LIBRARY NAMES libiso9660++ iso9660++ + HINTS ${DEPENDS_PATH}/lib ${PC_ISO9660PP_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) -find_library(ISO9660_LIBRARY NAMES libiso9660 iso9660 - HINTS ${PC_ISO9660_LIBDIR}) + find_path(ISO9660_INCLUDE_DIR NAMES cdio/iso9660.h + HINTS ${DEPENDS_PATH}/include ${PC_ISO9660_INCLUDEDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) -set(ISO9660PP_VERSION ${PC_ISO9660PP_VERSION}) + find_library(ISO9660_LIBRARY NAMES libiso9660 iso9660 + HINTS ${DEPENDS_PATH}/lib ${PC_ISO9660_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Iso9660pp - REQUIRED_VARS ISO9660PP_LIBRARY ISO9660PP_INCLUDE_DIR ISO9660_LIBRARY ISO9660_INCLUDE_DIR CDIO_LIBRARY CDIO_INCLUDE_DIR CDIOPP_INCLUDE_DIR - VERSION_VAR ISO9660PP_VERSION) + set(ISO9660PP_VERSION ${PC_ISO9660PP_VERSION}) -if(ISO9660PP_FOUND) - set(ISO9660PP_LIBRARIES ${ISO9660PP_LIBRARY} ${ISO9660_LIBRARY} ${CDIO_LIBRARY}) - set(ISO9660PP_INCLUDE_DIRS ${CDIO_INCLUDE_DIR} ${CDIOPP_INCLUDE_DIR} ${ISO9660_INCLUDE_DIR} ${ISO9660PP_INCLUDE_DIR}) - set(ISO9660PP_DEFINITIONS -DHAS_ISO9660PP=1) -endif() + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Iso9660pp + REQUIRED_VARS ISO9660PP_LIBRARY ISO9660PP_INCLUDE_DIR ISO9660_LIBRARY ISO9660_INCLUDE_DIR + VERSION_VAR ISO9660PP_VERSION) -mark_as_advanced(ISO9660PP_INCLUDE_DIR ISO9660PP_LIBRARY ISO9660_INCLUDE_DIR ISO9660_LIBRARY) + if(ISO9660PP_FOUND) + add_library(${APP_NAME_LC}::Iso9660 UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::Iso9660 PROPERTIES + IMPORTED_LOCATION "${ISO9660_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ISO9660_INCLUDE_DIR}") + + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${ISO9660PP_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ISO9660PP_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${APP_NAME_LC}::Iso9660;${APP_NAME_LC}::Cdio" + INTERFACE_COMPILE_DEFINITIONS HAS_ISO9660PP) + endif() + else() + include(FindPackageMessage) + find_package_message(Iso9660pp "Iso9660pp: Can not find libcdio (REQUIRED)" "") + endif() +endif() diff --git a/xbmc/filesystem/CMakeLists.txt b/xbmc/filesystem/CMakeLists.txt index 1416872da0b1a..379cdc3a79f32 100644 --- a/xbmc/filesystem/CMakeLists.txt +++ b/xbmc/filesystem/CMakeLists.txt @@ -124,7 +124,7 @@ set(HEADERS AddonsDirectory.h ZipFile.h ZipManager.h) -if(ISO9660PP_FOUND) +if(TARGET ${APP_NAME_LC}::Iso9660pp) list(APPEND SOURCES ISO9660Directory.cpp ISO9660File.cpp) list(APPEND HEADERS ISO9660Directory.h From 81cea424122737dfd99a244b6e88ec2833457b8d Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 17:40:08 +1000 Subject: [PATCH 128/651] [cmake][modules] FindFlatBuffers cleanup and use core_target_link_libraries --- cmake/modules/FindFlatBuffers.cmake | 16 +++++++--------- xbmc/cores/RetroPlayer/messages/CMakeLists.txt | 6 +++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/cmake/modules/FindFlatBuffers.cmake b/cmake/modules/FindFlatBuffers.cmake index 841d14bc6a369..dbac20d361f3e 100644 --- a/cmake/modules/FindFlatBuffers.cmake +++ b/cmake/modules/FindFlatBuffers.cmake @@ -4,11 +4,11 @@ # # This will define the following target: # -# flatbuffers::flatheaders - The flatbuffers headers +# ${APP_NAME_LC}::FlatBuffers - The flatbuffers headers find_package(FlatC REQUIRED) -if(NOT TARGET flatbuffers::flatheaders) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) include(cmake/scripts/common/ModuleHelpers.cmake) @@ -58,14 +58,14 @@ if(NOT TARGET flatbuffers::flatheaders) if(FlatBuffers_FOUND) - add_library(flatbuffers::flatheaders INTERFACE IMPORTED) - set_target_properties(flatbuffers::flatheaders PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${FLATBUFFERS_INCLUDE_DIR}") + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${FLATBUFFERS_INCLUDE_DIR}") - add_dependencies(flatbuffers::flatheaders flatbuffers::flatc) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} flatbuffers::flatc) if(TARGET flatbuffers) - add_dependencies(flatbuffers::flatheaders flatbuffers) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} flatbuffers) endif() # Add internal build target when a Multi Config Generator is used @@ -83,8 +83,6 @@ if(NOT TARGET flatbuffers::flatheaders) endif() add_dependencies(build_internal_depends flatbuffers) endif() - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP flatbuffers::flatheaders) else() if(FlatBuffers_FIND_REQUIRED) message(FATAL_ERROR "Flatbuffer schema headers were not found. You may want to try -DENABLE_INTERNAL_FLATBUFFERS=ON to build the internal headers package") diff --git a/xbmc/cores/RetroPlayer/messages/CMakeLists.txt b/xbmc/cores/RetroPlayer/messages/CMakeLists.txt index 4d68b7988a9fa..86174823709f4 100644 --- a/xbmc/cores/RetroPlayer/messages/CMakeLists.txt +++ b/xbmc/cores/RetroPlayer/messages/CMakeLists.txt @@ -20,8 +20,8 @@ set_target_properties(retroplayer_messages PROPERTIES FOLDER "Generated Messages INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR} SOURCES "${FLATC_OUTPUTS}") -if(TARGET flatbuffers::flatheaders) - set_property(TARGET flatbuffers::flatheaders APPEND PROPERTY +if(TARGET ${APP_NAME_LC}::FlatBuffers) + set_property(TARGET ${APP_NAME_LC}::FlatBuffers APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}") - add_dependencies(retroplayer_messages flatbuffers::flatheaders) + add_dependencies(retroplayer_messages ${APP_NAME_LC}::FlatBuffers) endif() From e0caae17000f10901743b958c24790c21ac395a1 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 17:43:21 +1000 Subject: [PATCH 129/651] [cmake][modules] FindLibDisplayInfo cleanup and use core_target_link_libraries --- cmake/modules/FindLibDisplayInfo.cmake | 51 ++++++++++++++------------ xbmc/utils/CMakeLists.txt | 2 +- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/cmake/modules/FindLibDisplayInfo.cmake b/cmake/modules/FindLibDisplayInfo.cmake index b74e66e9489de..cfe48aa1db994 100644 --- a/cmake/modules/FindLibDisplayInfo.cmake +++ b/cmake/modules/FindLibDisplayInfo.cmake @@ -3,36 +3,39 @@ # ------- # Finds the libdisplay-info library # -# This will define the following variables:: -# -# LIBDISPLAYINFO_FOUND - system has LIBDISPLAY-INFO -# LIBDISPLAYINFO_INCLUDE_DIRS - the LIBDISPLAY-INFO include directory -# LIBDISPLAYINFO_LIBRARIES - the LIBDISPLAY-INFO libraries -# LIBDISPLAYINFO_DEFINITIONS - the LIBDISPLAY-INFO definitions +# This will define the following target: # +# ${APP_NAME_LC}::LibDisplayInfo - The LibDisplayInfo library -find_package(PkgConfig) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_LIBDISPLAYINFO libdisplay-info QUIET) -endif() + find_package(PkgConfig) -find_path(LIBDISPLAYINFO_INCLUDE_DIR libdisplay-info/edid.h - HINTS ${PC_LIBDISPLAYINFO_INCLUDEDIR}) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_LIBDISPLAYINFO libdisplay-info QUIET) + endif() -find_library(LIBDISPLAYINFO_LIBRARY NAMES display-info - HINTS ${PC_LIBDISPLAYINFO_LIBDIR}) + find_path(LIBDISPLAYINFO_INCLUDE_DIR libdisplay-info/edid.h + HINTS ${PC_LIBDISPLAYINFO_INCLUDEDIR}) -set(LIBDISPLAYINFO_VERSION ${PC_LIBDISPLAYINFO_VERSION}) + find_library(LIBDISPLAYINFO_LIBRARY NAMES display-info + HINTS ${PC_LIBDISPLAYINFO_LIBDIR}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibDisplayInfo - REQUIRED_VARS LIBDISPLAYINFO_LIBRARY LIBDISPLAYINFO_INCLUDE_DIR - VERSION_VAR LIBDISPLAYINFO_VERSION) + set(LIBDISPLAYINFO_VERSION ${PC_LIBDISPLAYINFO_VERSION}) -if(LIBDISPLAYINFO_FOUND) - set(LIBDISPLAYINFO_LIBRARIES ${LIBDISPLAYINFO_LIBRARY}) - set(LIBDISPLAYINFO_INCLUDE_DIRS ${LIBDISPLAYINFO_INCLUDE_DIR}) -endif() + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LibDisplayInfo + REQUIRED_VARS LIBDISPLAYINFO_LIBRARY LIBDISPLAYINFO_INCLUDE_DIR + VERSION_VAR LIBDISPLAYINFO_VERSION) -mark_as_advanced(LIBDISPLAYINFO_INCLUDE_DIR LIBDISPLAYINFO_LIBRARY) + if(LIBDISPLAYINFO_FOUND) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${LIBDISPLAYINFO_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBDISPLAYINFO_INCLUDE_DIR}") + else() + if(LibDisplayInfo_FIND_REQUIRED) + message(FATAL_ERROR "Libdisplayinfo libraries were not found.") + endif() + endif() +endif() diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index 8fcad352af667..13dbe058a9d8e 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -240,7 +240,7 @@ if("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_L list(APPEND HEADERS DRMHelpers.h) endif() - if(LIBDISPLAYINFO_FOUND) + if(TARGET ${APP_NAME_LC}::LibDisplayInfo) list(APPEND SOURCES DisplayInfo.cpp) list(APPEND HEADERS DisplayInfo.h) endif() From f369d5887d5d0cd84cd42a2f673c901e0b657ec4 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 17:50:47 +1000 Subject: [PATCH 130/651] [cmake][modules] FindLibDovi update to target usage --- cmake/modules/FindLibDovi.cmake | 45 ++++++++++++++++----------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/cmake/modules/FindLibDovi.cmake b/cmake/modules/FindLibDovi.cmake index 2f9f29632e520..e6b44048b1a4a 100644 --- a/cmake/modules/FindLibDovi.cmake +++ b/cmake/modules/FindLibDovi.cmake @@ -2,33 +2,32 @@ # ------- # Finds the libdovi library # -# This will define the following variables:: +# This will define the following target: # -# LIBDOVI_FOUND - system has libdovi -# LIBDOVI_INCLUDE_DIRS - the libdovi include directories -# LIBDOVI_LIBRARIES - the libdovi libraries -# LIBDOVI_DEFINITIONS - the libdovi compile definitions +# ${APP_NAME_LC}::LibDovi - The libDovi library -find_package(PkgConfig) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_LIBDOVI libdovi QUIET) -endif() + find_package(PkgConfig) -find_library(LIBDOVI_LIBRARY NAMES dovi libdovi - HINTS ${PC_LIBDOVI_LIBDIR} -) -find_path(LIBDOVI_INCLUDE_DIR NAMES libdovi/rpu_parser.h - HINTS ${PC_LIBDOVI_INCLUDEDIR}) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_LIBDOVI libdovi QUIET) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibDovi - REQUIRED_VARS LIBDOVI_LIBRARY LIBDOVI_INCLUDE_DIR) + find_library(LIBDOVI_LIBRARY NAMES dovi libdovi + HINTS ${PC_LIBDOVI_LIBDIR}) + find_path(LIBDOVI_INCLUDE_DIR NAMES libdovi/rpu_parser.h + HINTS ${PC_LIBDOVI_INCLUDEDIR}) -if(LIBDOVI_FOUND) - set(LIBDOVI_INCLUDE_DIRS ${LIBDOVI_INCLUDE_DIR}) - set(LIBDOVI_LIBRARIES ${LIBDOVI_LIBRARY}) - set(LIBDOVI_DEFINITIONS -DHAVE_LIBDOVI=1) -endif() + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LibDovi + REQUIRED_VARS LIBDOVI_LIBRARY LIBDOVI_INCLUDE_DIR) -mark_as_advanced(LIBDOVI_INCLUDE_DIR LIBDOVI_LIBRARY) + if(LIBDOVI_FOUND) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${LIBDOVI_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBDOVI_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAVE_LIBDOVI) + endif() +endif() From c0f36e41a5d9584eefabf9e775aed05235d22f44 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 17:52:34 +1000 Subject: [PATCH 131/651] [cmake][modules] FindLibInput update to target usage --- cmake/modules/FindLibInput.cmake | 64 ++++++++++++------------ xbmc/platform/linux/input/CMakeLists.txt | 2 +- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/cmake/modules/FindLibInput.cmake b/cmake/modules/FindLibInput.cmake index a1530f3a25bfb..0e4681fbf9ac0 100644 --- a/cmake/modules/FindLibInput.cmake +++ b/cmake/modules/FindLibInput.cmake @@ -3,36 +3,38 @@ # -------- # Finds the libinput library # -# This will define the following variables:: +# This will define the following target: # -# LIBINPUT_FOUND - system has libinput -# LIBINPUT_INCLUDE_DIRS - the libinput include directory -# LIBINPUT_LIBRARIES - the libinput libraries -# LIBINPUT_DEFINITIONS - the libinput compile definitions -# - -find_package(PkgConfig) - -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_LIBINPUT libinput QUIET) -endif() - -find_path(LIBINPUT_INCLUDE_DIR NAMES libinput.h - HINTS ${PC_LIBINPUT_INCLUDEDIR}) - -find_library(LIBINPUT_LIBRARY NAMES input - HINTS ${PC_LIBINPUT_LIBDIR}) - -set(LIBINPUT_VERSION ${PC_LIBINPUT_VERSION}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibInput - REQUIRED_VARS LIBINPUT_LIBRARY LIBINPUT_INCLUDE_DIR - VERSION_VAR LIBINPUT_VERSION) - -if(LIBINPUT_FOUND) - set(LIBINPUT_INCLUDE_DIRS ${LIBINPUT_INCLUDE_DIR}) - set(LIBINPUT_LIBRARIES ${LIBINPUT_LIBRARY}) +# ${APP_NAME_LC}::LibInput - The LibInput library + +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + find_package(PkgConfig) + + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_LIBINPUT libinput QUIET) + endif() + + find_path(LIBINPUT_INCLUDE_DIR NAMES libinput.h + HINTS ${PC_LIBINPUT_INCLUDEDIR}) + + find_library(LIBINPUT_LIBRARY NAMES input + HINTS ${PC_LIBINPUT_LIBDIR}) + + set(LIBINPUT_VERSION ${PC_LIBINPUT_VERSION}) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LibInput + REQUIRED_VARS LIBINPUT_LIBRARY LIBINPUT_INCLUDE_DIR + VERSION_VAR LIBINPUT_VERSION) + + if(LIBINPUT_FOUND) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${LIBINPUT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBINPUT_INCLUDE_DIR}") + else() + if(LibInput_FIND_REQUIRED) + message(FATAL_ERROR "Libinput libraries were not found.") + endif() + endif() endif() - -mark_as_advanced(LIBINPUT_INCLUDE_DIR LIBINPUT_LIBRARY) diff --git a/xbmc/platform/linux/input/CMakeLists.txt b/xbmc/platform/linux/input/CMakeLists.txt index d3088d44c2420..6f3bb68eee884 100644 --- a/xbmc/platform/linux/input/CMakeLists.txt +++ b/xbmc/platform/linux/input/CMakeLists.txt @@ -7,7 +7,7 @@ if(TARGET ${APP_NAME_LC}::LircClient) endif() if("gbm" IN_LIST CORE_PLATFORM_NAME_LC) - if(LIBINPUT_FOUND) + if(TARGET ${APP_NAME_LC}::LibInput) list(APPEND SOURCES LibInputHandler.cpp LibInputKeyboard.cpp LibInputPointer.cpp From edc5e4387d14b4068c80440bfe3fc3761024e075 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 18:00:04 +1000 Subject: [PATCH 132/651] [cmake][modules] FindLzo2 cleanup and use core_target_link_libraries --- cmake/modules/FindLzo2.cmake | 46 +++++++++---------- .../native/TexturePacker/src/CMakeLists.txt | 4 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cmake/modules/FindLzo2.cmake b/cmake/modules/FindLzo2.cmake index 5e3e6865bd910..eb6aaa21d72c6 100644 --- a/cmake/modules/FindLzo2.cmake +++ b/cmake/modules/FindLzo2.cmake @@ -3,35 +3,33 @@ # -------- # Finds the Lzo2 library # -# This will define the following variables:: +# This will define the following target: # -# LZO2_FOUND - system has Lzo2 -# LZO2_INCLUDE_DIRS - the Lzo2 include directory -# LZO2_LIBRARIES - the Lzo2 libraries -# -# and the following imported targets:: -# -# Lzo2::Lzo2 - The Lzo2 library +# ${APP_NAME_LC}::Lzo2 - The Lzo2 library -find_path(LZO2_INCLUDE_DIR NAMES lzo1x.h - PATH_SUFFIXES lzo) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) -find_library(LZO2_LIBRARY NAMES lzo2 liblzo2) + find_path(LZO2_INCLUDE_DIR NAMES lzo1x.h + PATH_SUFFIXES lzo + HINTS ${DEPENDS_PATH}/include + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Lzo2 - REQUIRED_VARS LZO2_LIBRARY LZO2_INCLUDE_DIR) + find_library(LZO2_LIBRARY NAMES lzo2 liblzo2 + HINTS ${DEPENDS_PATH}/lib + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) -if(LZO2_FOUND) - set(LZO2_LIBRARIES ${LZO2_LIBRARY}) - set(LZO2_INCLUDE_DIRS ${LZO2_INCLUDE_DIR}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Lzo2 + REQUIRED_VARS LZO2_LIBRARY LZO2_INCLUDE_DIR) - if(NOT TARGET Lzo2::Lzo2) - add_library(Lzo2::Lzo2 UNKNOWN IMPORTED) - set_target_properties(Lzo2::Lzo2 PROPERTIES - IMPORTED_LOCATION "${LZO2_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${LZO2_INCLUDE_DIR}") + if(LZO2_FOUND) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${LZO2_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LZO2_INCLUDE_DIR}") + else() + if(LibLzo2_FIND_REQUIRED) + message(FATAL_ERROR "Lzo2 library was not found.") + endif() endif() endif() - -mark_as_advanced(LZO2_INCLUDE_DIR LZO2_LIBRARY) diff --git a/tools/depends/native/TexturePacker/src/CMakeLists.txt b/tools/depends/native/TexturePacker/src/CMakeLists.txt index c8b0f76cb9846..25b1be7a9660a 100644 --- a/tools/depends/native/TexturePacker/src/CMakeLists.txt +++ b/tools/depends/native/TexturePacker/src/CMakeLists.txt @@ -13,6 +13,8 @@ if(ENABLE_STATIC) set(ZLIB_USE_STATIC_LIBS ON) endif() +set(APP_NAME_LC texturepacker) + find_package(Lzo2 REQUIRED) find_package(PNG REQUIRED) find_package(GIF REQUIRED) @@ -68,7 +70,7 @@ target_link_libraries(TexturePacker ${GIF_LIBRARIES} ${PNG_LIBRARIES} ${JPEG_LIBRARIES} - ${LZO2_LIBRARIES}) + texturepacker::Lzo2) target_compile_definitions(TexturePacker PRIVATE ${ARCH_DEFINES} ${SYSTEM_DEFINES}) target_compile_features(TexturePacker PUBLIC cxx_std_17) From 99c1ee96f3a5f010566551bea814b4344186b7d9 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 18:09:48 +1000 Subject: [PATCH 133/651] [cmake][modules] FindMicroHttpd update to target usage --- cmake/modules/FindMicroHttpd.cmake | 92 ++++++++++++------- xbmc/filesystem/test/CMakeLists.txt | 2 +- xbmc/interfaces/legacy/wsgi/CMakeLists.txt | 2 +- xbmc/network/CMakeLists.txt | 2 +- .../network/httprequesthandler/CMakeLists.txt | 2 +- .../httprequesthandler/python/CMakeLists.txt | 2 +- xbmc/network/test/CMakeLists.txt | 2 +- 7 files changed, 64 insertions(+), 40 deletions(-) diff --git a/cmake/modules/FindMicroHttpd.cmake b/cmake/modules/FindMicroHttpd.cmake index 02c94637d6233..1f2e9d69cec25 100644 --- a/cmake/modules/FindMicroHttpd.cmake +++ b/cmake/modules/FindMicroHttpd.cmake @@ -3,48 +3,72 @@ # -------------- # Finds the MicroHttpd library # -# This will define the following variables:: -# -# MICROHTTPD_FOUND - system has MicroHttpd -# MICROHTTPD_INCLUDE_DIRS - the MicroHttpd include directory -# MICROHTTPD_LIBRARIES - the MicroHttpd libraries -# MICROHTTPD_DEFINITIONS - the MicroHttpd definitions +# This will define the following target: # +# ${APP_NAME_LC}::MicroHttpd - The microhttpd library -if(MicroHttpd_FIND_VERSION) - if(MicroHttpd_FIND_VERSION_EXACT) - set(MicroHttpd_FIND_SPEC "=${MicroHttpd_FIND_VERSION_COMPLETE}") - else() - set(MicroHttpd_FIND_SPEC ">=${MicroHttpd_FIND_VERSION_COMPLETE}") - endif() -endif() +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) -find_package(PkgConfig) + find_package(PkgConfig) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_MICROHTTPD libmicrohttpd${MicroHttpd_FIND_SPEC} QUIET) -endif() + if(PKG_CONFIG_FOUND AND NOT (WIN32 OR WINDOWS_STORE)) + if(MicroHttpd_FIND_VERSION) + if(MicroHttpd_FIND_VERSION_EXACT) + set(MicroHttpd_FIND_SPEC "=${MicroHttpd_FIND_VERSION_COMPLETE}") + else() + set(MicroHttpd_FIND_SPEC ">=${MicroHttpd_FIND_VERSION_COMPLETE}") + endif() + endif() + + pkg_check_modules(MICROHTTPD libmicrohttpd${MicroHttpd_FIND_SPEC} QUIET) + + # First item is the full path of the library file found + # pkg_check_modules does not populate a variable of the found library explicitly + list(GET MICROHTTPD_LINK_LIBRARIES 0 MICROHTTPD_LIBRARY) -find_path(MICROHTTPD_INCLUDE_DIR NAMES microhttpd.h - HINTS ${PC_MICROHTTPD_INCLUDEDIR}) -find_library(MICROHTTPD_LIBRARY NAMES microhttpd libmicrohttpd - HINTS ${PC_MICROHTTPD_LIBDIR}) + # Add link libraries for static lib usage + if(${MICROHTTPD_LIBRARY} MATCHES ".+\.a$" AND MICROHTTPD_LINK_LIBRARIES) + # Remove duplicates + list(REMOVE_DUPLICATES MICROHTTPD_LINK_LIBRARIES) -set(MICROHTTPD_VERSION ${PC_MICROHTTPD_VERSION}) + # Remove own library - eg libmicrohttpd.a + list(FILTER MICROHTTPD_LINK_LIBRARIES EXCLUDE REGEX ".*microhttpd.*\.a$") + set(PC_MICROHTTPD_LINK_LIBRARIES ${MICROHTTPD_LINK_LIBRARIES}) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(MicroHttpd - REQUIRED_VARS MICROHTTPD_LIBRARY MICROHTTPD_INCLUDE_DIR - VERSION_VAR MICROHTTPD_VERSION) + # pkgconfig sets MICROHTTPD_INCLUDEDIR, map this to our "standard" variable name + set(MICROHTTPD_INCLUDE_DIR ${MICROHTTPD_INCLUDEDIR}) + else() -if(MICROHTTPD_FOUND) - set(MICROHTTPD_LIBRARIES ${MICROHTTPD_LIBRARY}) - set(MICROHTTPD_INCLUDE_DIRS ${MICROHTTPD_INCLUDE_DIR}) - set(MICROHTTPD_DEFINITIONS -DHAS_WEB_SERVER=1 -DHAS_WEB_INTERFACE=1) + find_path(MICROHTTPD_INCLUDE_DIR NAMES microhttpd.h + HINTS ${DEPENDS_PATH}/include + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) - if(${MICROHTTPD_LIBRARY} MATCHES ".+\.a$" AND PC_MICROHTTPD_STATIC_LIBRARIES) - list(APPEND MICROHTTPD_LIBRARIES ${PC_MICROHTTPD_STATIC_LIBRARIES}) + find_library(MICROHTTPD_LIBRARY NAMES microhttpd libmicrohttpd + HINTS ${DEPENDS_PATH}/lib + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) endif() -endif() -mark_as_advanced(MICROHTTPD_LIBRARY MICROHTTPD_INCLUDE_DIR) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(MicroHttpd + REQUIRED_VARS MICROHTTPD_LIBRARY MICROHTTPD_INCLUDE_DIR + VERSION_VAR MICROHTTPD_VERSION) + + if(MICROHTTPD_FOUND) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${MICROHTTPD_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${MICROHTTPD_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS "HAS_WEB_SERVER;HAS_WEB_INTERFACE") + + # Add link libraries for static lib usage found from pkg-config + if(PC_MICROHTTPD_LINK_LIBRARIES) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_LINK_LIBRARIES "${PC_MICROHTTPD_LINK_LIBRARIES}") + endif() + + if(${MICROHTTPD_LIBRARY} MATCHES ".+\.a$" AND PC_MICROHTTPD_STATIC_LIBRARIES) + list(APPEND MICROHTTPD_LIBRARIES ${PC_MICROHTTPD_STATIC_LIBRARIES}) + endif() + endif() +endif() diff --git a/xbmc/filesystem/test/CMakeLists.txt b/xbmc/filesystem/test/CMakeLists.txt index 5d2f0344c7a6a..c8ecfe29889d4 100644 --- a/xbmc/filesystem/test/CMakeLists.txt +++ b/xbmc/filesystem/test/CMakeLists.txt @@ -4,7 +4,7 @@ set(SOURCES TestDirectory.cpp TestZipFile.cpp TestZipManager.cpp) -if(MICROHTTPD_FOUND) +if(TARGET ${APP_NAME_LC}::MicroHttpd) list(APPEND SOURCES TestHTTPDirectory.cpp) endif() diff --git a/xbmc/interfaces/legacy/wsgi/CMakeLists.txt b/xbmc/interfaces/legacy/wsgi/CMakeLists.txt index cc29eb464f84b..cd8fbdd91426a 100644 --- a/xbmc/interfaces/legacy/wsgi/CMakeLists.txt +++ b/xbmc/interfaces/legacy/wsgi/CMakeLists.txt @@ -1,4 +1,4 @@ -if(MICROHTTPD_FOUND) +if(TARGET ${APP_NAME_LC}::MicroHttpd) set(SOURCES WsgiErrorStream.cpp WsgiInputStream.cpp WsgiResponseBody.cpp diff --git a/xbmc/network/CMakeLists.txt b/xbmc/network/CMakeLists.txt index a4dac89ecbdd2..ad301ac7de27f 100644 --- a/xbmc/network/CMakeLists.txt +++ b/xbmc/network/CMakeLists.txt @@ -47,7 +47,7 @@ if(TARGET ${APP_NAME_LC}::SmbClient) list(APPEND HEADERS IWSDiscovery.h) endif() -if(MICROHTTPD_FOUND) +if(TARGET ${APP_NAME_LC}::MicroHttpd) list(APPEND SOURCES WebServer.cpp) list(APPEND HEADERS WebServer.h) endif() diff --git a/xbmc/network/httprequesthandler/CMakeLists.txt b/xbmc/network/httprequesthandler/CMakeLists.txt index ea514c51f5329..d46d75b485458 100644 --- a/xbmc/network/httprequesthandler/CMakeLists.txt +++ b/xbmc/network/httprequesthandler/CMakeLists.txt @@ -1,4 +1,4 @@ -if(MICROHTTPD_FOUND) +if(TARGET ${APP_NAME_LC}::MicroHttpd) set(SOURCES HTTPFileHandler.cpp HTTPImageHandler.cpp HTTPImageTransformationHandler.cpp diff --git a/xbmc/network/httprequesthandler/python/CMakeLists.txt b/xbmc/network/httprequesthandler/python/CMakeLists.txt index 7bbbad9b4d353..b7b4c43a6a16d 100644 --- a/xbmc/network/httprequesthandler/python/CMakeLists.txt +++ b/xbmc/network/httprequesthandler/python/CMakeLists.txt @@ -1,4 +1,4 @@ -if(MICROHTTPD_FOUND AND PYTHON_FOUND) +if(TARGET ${APP_NAME_LC}::MicroHttpd AND PYTHON_FOUND) set(SOURCES HTTPPythonInvoker.cpp HTTPPythonWsgiInvoker.cpp) diff --git a/xbmc/network/test/CMakeLists.txt b/xbmc/network/test/CMakeLists.txt index b07577573631b..f623acc02cfa3 100644 --- a/xbmc/network/test/CMakeLists.txt +++ b/xbmc/network/test/CMakeLists.txt @@ -1,7 +1,7 @@ set(SOURCES TestNetwork.cpp TestNetworkFileItemClassify.cpp) -if(MICROHTTPD_FOUND) +if(TARGET ${APP_NAME_LC}::MicroHttpd) list(APPEND SOURCES TestWebServer.cpp) endif() From 622eee7a61408f5e8411e79c07fad2be56e7cbb6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 18:16:45 +1000 Subject: [PATCH 134/651] [cmake][modules] FindPipewire update to target usage --- cmake/modules/FindPipewire.cmake | 97 +++++++++++++-------------- xbmc/cores/AudioEngine/CMakeLists.txt | 2 +- 2 files changed, 49 insertions(+), 50 deletions(-) diff --git a/cmake/modules/FindPipewire.cmake b/cmake/modules/FindPipewire.cmake index 653a891eecdb9..c0d2c5704c38c 100644 --- a/cmake/modules/FindPipewire.cmake +++ b/cmake/modules/FindPipewire.cmake @@ -3,63 +3,62 @@ # -------------- # Finds the Pipewire library # -# This will define the following variables:: -# -# PIPEWIRE_FOUND - system has the Pipewire library -# PIPEWIRE_INCLUDE_DIRS - the Pipewire include directory -# PIPEWIRE_LIBRARIES - the libraries needed to use Pipewire -# PIPEWIRE_DEFINITIONS - the definitions needed to use Pipewire +# This will define the following targets: # +# ${APP_NAME_LC}::Pipewire - The pipewire library + +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + if(Pipewire_FIND_VERSION) + if(Pipewire_FIND_VERSION_EXACT) + set(Pipewire_FIND_SPEC "=${Pipewire_FIND_VERSION_COMPLETE}") + else() + set(Pipewire_FIND_SPEC ">=${Pipewire_FIND_VERSION_COMPLETE}") + endif() + endif() -if(Pipewire_FIND_VERSION) - if(Pipewire_FIND_VERSION_EXACT) - set(Pipewire_FIND_SPEC "=${Pipewire_FIND_VERSION_COMPLETE}") - else() - set(Pipewire_FIND_SPEC ">=${Pipewire_FIND_VERSION_COMPLETE}") + pkg_check_modules(PC_PIPEWIRE libpipewire-0.3${Pipewire_FIND_SPEC} QUIET) + pkg_check_modules(PC_SPA libspa-0.2>=0.2 QUIET) endif() -endif() -find_package(PkgConfig) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_PIPEWIRE libpipewire-0.3${Pipewire_FIND_SPEC} QUIET) - pkg_check_modules(PC_SPA libspa-0.2>=0.2 QUIET) -endif() + find_path(PIPEWIRE_INCLUDE_DIR NAMES pipewire/pipewire.h + HINTS ${PC_PIPEWIRE_INCLUDEDIR} + PATH_SUFFIXES pipewire-0.3) -find_path(PIPEWIRE_INCLUDE_DIR NAMES pipewire/pipewire.h - HINTS ${PC_PIPEWIRE_INCLUDEDIR} - PATH_SUFFIXES pipewire-0.3) + find_path(SPA_INCLUDE_DIR NAMES spa/support/plugin.h + HINTS ${PC_SPA_INCLUDEDIR} + PATH_SUFFIXES spa-0.2) -find_path(SPA_INCLUDE_DIR NAMES spa/support/plugin.h - HINTS ${PC_SPA_INCLUDEDIR} - PATH_SUFFIXES spa-0.2) + find_library(PIPEWIRE_LIBRARY NAMES pipewire-0.3 + HITNS ${PC_PIPEWIRE_LIBDIR}) -find_library(PIPEWIRE_LIBRARY NAMES pipewire-0.3 - HITNS ${PC_PIPEWIRE_LIBDIR}) + if(PC_PIPEWIRE_VERSION) + set(PIPEWIRE_VERSION_STRING ${PC_PIPEWIRE_VERSION}) + elseif(PIPEWIRE_INCLUDE_DIR AND EXISTS ${PIPEWIRE_INCLUDE_DIR}/pipewire/version.h) + file(STRINGS ${PIPEWIRE_INCLUDE_DIR}/pipewire/version.h PIPEWIRE_STRINGS) + string(REGEX MATCH "#define PW_MAJOR \([0-9]+\)" MAJOR_VERSION "${PIPEWIRE_STRINGS}") + set(MAJOR_VERSION ${CMAKE_MATCH_1}) + string(REGEX MATCH "#define PW_MINOR \([0-9]+\)" MINOR_VERSION "${PIPEWIRE_STRINGS}") + set(MINOR_VERSION ${CMAKE_MATCH_1}) + string(REGEX MATCH "#define PW_MICRO \([0-9]+\)" MICRO_VERSION "${PIPEWIRE_STRINGS}") + set(MICRO_VERSION ${CMAKE_MATCH_1}) + set(PIPEWIRE_VERSION_STRING ${MAJOR_VERSION}.${MINOR_VERSION}.${MICRO_VERSION}) + endif() -if(PC_PIPEWIRE_VERSION) - set(PIPEWIRE_VERSION_STRING ${PC_PIPEWIRE_VERSION}) -elseif(PIPEWIRE_INCLUDE_DIR AND EXISTS ${PIPEWIRE_INCLUDE_DIR}/pipewire/version.h) - file(STRINGS ${PIPEWIRE_INCLUDE_DIR}/pipewire/version.h PIPEWIRE_STRINGS) - string(REGEX MATCH "#define PW_MAJOR \([0-9]+\)" MAJOR_VERSION "${PIPEWIRE_STRINGS}") - set(MAJOR_VERSION ${CMAKE_MATCH_1}) - string(REGEX MATCH "#define PW_MINOR \([0-9]+\)" MINOR_VERSION "${PIPEWIRE_STRINGS}") - set(MINOR_VERSION ${CMAKE_MATCH_1}) - string(REGEX MATCH "#define PW_MICRO \([0-9]+\)" MICRO_VERSION "${PIPEWIRE_STRINGS}") - set(MICRO_VERSION ${CMAKE_MATCH_1}) - set(PIPEWIRE_VERSION_STRING ${MAJOR_VERSION}.${MINOR_VERSION}.${MICRO_VERSION}) -endif() + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Pipewire + REQUIRED_VARS PIPEWIRE_LIBRARY PIPEWIRE_INCLUDE_DIR SPA_INCLUDE_DIR + VERSION_VAR PIPEWIRE_VERSION_STRING) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Pipewire - REQUIRED_VARS PIPEWIRE_LIBRARY PIPEWIRE_INCLUDE_DIR SPA_INCLUDE_DIR - VERSION_VAR PIPEWIRE_VERSION_STRING) + if(PIPEWIRE_FOUND) + list(APPEND AUDIO_BACKENDS_LIST "pipewire") + set(AUDIO_BACKENDS_LIST ${AUDIO_BACKENDS_LIST} PARENT_SCOPE) -if(PIPEWIRE_FOUND) - set(PIPEWIRE_INCLUDE_DIRS ${PIPEWIRE_INCLUDE_DIR} ${SPA_INCLUDE_DIR}) - set(PIPEWIRE_LIBRARIES ${PIPEWIRE_LIBRARY}) - set(PIPEWIRE_DEFINITIONS -DHAS_PIPEWIRE=1) - list(APPEND AUDIO_BACKENDS_LIST "pipewire") - set(AUDIO_BACKENDS_LIST ${AUDIO_BACKENDS_LIST} PARENT_SCOPE) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${PIPEWIRE_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${PIPEWIRE_INCLUDE_DIR};${SPA_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_PIPEWIRE) + endif() endif() - -mark_as_advanced(PIPEWIRE_INCLUDE_DIR PIPEWIRE_LIBRARY) diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt index f3eaf2711c9f1..fba64eda89eea 100644 --- a/xbmc/cores/AudioEngine/CMakeLists.txt +++ b/xbmc/cores/AudioEngine/CMakeLists.txt @@ -70,7 +70,7 @@ if(TARGET ${APP_NAME_LC}::PulseAudio) list(APPEND HEADERS Sinks/AESinkPULSE.h) endif() -if(PIPEWIRE_FOUND) +if(TARGET ${APP_NAME_LC}::Pipewire) list(APPEND SOURCES Sinks/pipewire/AESinkPipewire.cpp Sinks/pipewire/Pipewire.cpp Sinks/pipewire/PipewireContext.cpp From 45c6459b6ea6d2ecb6a1ae7d4056ca4fb96535cf Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 18:55:26 +1000 Subject: [PATCH 135/651] [cmake][modules] FindPython update to target usage --- cmake/modules/FindPython.cmake | 115 +++++++++--------- xbmc/interfaces/python/test/CMakeLists.txt | 2 +- .../network/httprequesthandler/CMakeLists.txt | 4 +- .../httprequesthandler/python/CMakeLists.txt | 2 +- 4 files changed, 62 insertions(+), 61 deletions(-) diff --git a/cmake/modules/FindPython.cmake b/cmake/modules/FindPython.cmake index 56d320e1a9ea7..3fc61da50c290 100644 --- a/cmake/modules/FindPython.cmake +++ b/cmake/modules/FindPython.cmake @@ -16,75 +16,76 @@ # # -------- # -# This module will define the following variables: -# -# PYTHON_FOUND - system has PYTHON -# PYTHON_VERSION - Python version number (Major.Minor) -# PYTHON_INCLUDE_DIRS - the python include directory -# PYTHON_LIBRARIES - The python libraries -# PYTHON_LDFLAGS - Python provided link options -# -# -------- +# This will define the following targets: # +# ${APP_NAME_LC}::Python - The Python library -# for Depends/Windows builds, set search root dir to libdir path -if(KODI_DEPENDSBUILD - OR CMAKE_SYSTEM_NAME STREQUAL WINDOWS - OR CMAKE_SYSTEM_NAME STREQUAL WindowsStore) - set(Python3_USE_STATIC_LIBS TRUE) - set(Python3_ROOT_DIR ${libdir}) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + # for Depends/Windows builds, set search root dir to libdir path + if(KODI_DEPENDSBUILD + OR CMAKE_SYSTEM_NAME STREQUAL WINDOWS + OR CMAKE_SYSTEM_NAME STREQUAL WindowsStore) + set(Python3_USE_STATIC_LIBS TRUE) + set(Python3_ROOT_DIR ${libdir}) - if(KODI_DEPENDSBUILD) - # Force set to tools/depends python version - set(PYTHON_VER 3.11) + if(KODI_DEPENDSBUILD) + # Force set to tools/depends python version + set(PYTHON_VER 3.11) + endif() endif() -endif() -# Provide root dir to search for Python if provided -if(PYTHON_PATH) - set(Python3_ROOT_DIR ${PYTHON_PATH}) + # Provide root dir to search for Python if provided + if(PYTHON_PATH) + set(Python3_ROOT_DIR ${PYTHON_PATH}) - # unset cache var so we can generate again with a different dir (or none) if desired - unset(PYTHON_PATH CACHE) -endif() + # unset cache var so we can generate again with a different dir (or none) if desired + unset(PYTHON_PATH CACHE) + endif() -# Set specific version of Python to find if provided -if(PYTHON_VER) - set(VERSION ${PYTHON_VER}) - set(EXACT_VER "EXACT") + # Set specific version of Python to find if provided + if(PYTHON_VER) + set(VERSION ${PYTHON_VER}) + set(EXACT_VER "EXACT") - # unset cache var so we can generate again with a different ver (or none) if desired - unset(PYTHON_VER CACHE) -endif() + # unset cache var so we can generate again with a different ver (or none) if desired + unset(PYTHON_VER CACHE) + endif() + + find_package(Python3 ${VERSION} ${EXACT_VER} COMPONENTS Development) -find_package(Python3 ${VERSION} ${EXACT_VER} COMPONENTS Development) + if(Python3_FOUND) + if(KODI_DEPENDSBUILD) + find_library(EXPAT_LIBRARY expat REQUIRED) + find_library(FFI_LIBRARY ffi REQUIRED) + find_library(GMP_LIBRARY gmp REQUIRED) + find_library(INTL_LIBRARY intl REQUIRED) + find_library(LZMA_LIBRARY lzma REQUIRED) -if(KODI_DEPENDSBUILD) - find_library(FFI_LIBRARY ffi REQUIRED) - find_library(EXPAT_LIBRARY expat REQUIRED) - find_library(INTL_LIBRARY intl REQUIRED) - find_library(GMP_LIBRARY gmp REQUIRED) - find_library(LZMA_LIBRARY lzma REQUIRED) + if(NOT CORE_SYSTEM_NAME STREQUAL android) + set(PYTHON_DEP_LIBRARIES pthread dl util) + if(CORE_SYSTEM_NAME STREQUAL linux) + # python archive built via depends requires librt for _posixshmem library + list(APPEND PYTHON_DEP_LIBRARIES rt) + endif() + endif() - if(NOT CORE_SYSTEM_NAME STREQUAL android) - set(PYTHON_DEP_LIBRARIES pthread dl util) - if(CORE_SYSTEM_NAME STREQUAL linux) - # python archive built via depends requires librt for _posixshmem library - list(APPEND PYTHON_DEP_LIBRARIES rt) + set(Py_LINK_LIBRARIES ${EXPAT_LIBRARY} ${FFI_LIBRARY} ${GMP_LIBRARY} ${INTL_LIBRARY} ${LZMA_LIBRARY} ${PYTHON_DEP_LIBRARIES}) endif() - endif() - list(APPEND Python3_LIBRARIES ${LZMA_LIBRARY} ${FFI_LIBRARY} ${EXPAT_LIBRARY} ${INTL_LIBRARY} ${GMP_LIBRARY} ${PYTHON_DEP_LIBRARIES}) -endif() + # We use this all over the place. Maybe it would be nice to keep it as a TARGET property + # but for now a cached variable will do + set(PYTHON_VERSION "${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}" CACHE INTERNAL "" FORCE) -if(Python3_FOUND) - list(APPEND PYTHON_DEFINITIONS -DHAS_PYTHON=1) - # These are all set for easy integration with the rest of our build system - set(PYTHON_FOUND ${Python3_FOUND}) - set(PYTHON_INCLUDE_DIRS ${Python3_INCLUDE_DIRS}) - set(PYTHON_LIBRARIES ${Python3_LIBRARIES}) - set(PYTHON_VERSION "${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}" CACHE INTERNAL "" FORCE) - set(PYTHON_LDFLAGS ${Python3_LINK_OPTIONS}) -endif() + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${Python3_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${Python3_INCLUDE_DIRS}" + INTERFACE_LINK_OPTIONS "${Python3_LINK_OPTIONS}" + INTERFACE_COMPILE_DEFINITIONS HAS_PYTHON) -mark_as_advanced(PYTHON_EXECUTABLE PYTHON_VERSION PYTHON_INCLUDE_DIRS PYTHON_LDFLAGS LZMA_LIBRARY FFI_LIBRARY EXPAT_LIBRARY INTL_LIBRARY GMP_LIBRARY) + if(Py_LINK_LIBRARIES) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_LINK_LIBRARIES "${Py_LINK_LIBRARIES}") + endif() + endif() +endif() diff --git a/xbmc/interfaces/python/test/CMakeLists.txt b/xbmc/interfaces/python/test/CMakeLists.txt index ec38a51931c74..b916358455671 100644 --- a/xbmc/interfaces/python/test/CMakeLists.txt +++ b/xbmc/interfaces/python/test/CMakeLists.txt @@ -1,4 +1,4 @@ -if(PYTHON_FOUND) +if(TARGET ${APP_NAME_LC}::Python) set(SOURCES TestSwig.cpp) core_add_test_library(python_test) diff --git a/xbmc/network/httprequesthandler/CMakeLists.txt b/xbmc/network/httprequesthandler/CMakeLists.txt index d46d75b485458..c78c5b4f741f7 100644 --- a/xbmc/network/httprequesthandler/CMakeLists.txt +++ b/xbmc/network/httprequesthandler/CMakeLists.txt @@ -9,7 +9,7 @@ if(TARGET ${APP_NAME_LC}::MicroHttpd) HTTPWebinterfaceHandler.cpp IHTTPRequestHandler.cpp) - if(PYTHON_FOUND) + if(TARGET ${APP_NAME_LC}::Python) list(APPEND SOURCES HTTPPythonHandler.cpp) endif() @@ -22,7 +22,7 @@ if(TARGET ${APP_NAME_LC}::MicroHttpd) HTTPWebinterfaceAddonsHandler.h HTTPWebinterfaceHandler.h IHTTPRequestHandler.h) - if(PYTHON_FOUND) + if(TARGET ${APP_NAME_LC}::Python) list(APPEND HEADERS HTTPPythonHandler.h) endif() diff --git a/xbmc/network/httprequesthandler/python/CMakeLists.txt b/xbmc/network/httprequesthandler/python/CMakeLists.txt index b7b4c43a6a16d..23f327832854a 100644 --- a/xbmc/network/httprequesthandler/python/CMakeLists.txt +++ b/xbmc/network/httprequesthandler/python/CMakeLists.txt @@ -1,4 +1,4 @@ -if(TARGET ${APP_NAME_LC}::MicroHttpd AND PYTHON_FOUND) +if(TARGET ${APP_NAME_LC}::MicroHttpd AND TARGET ${APP_NAME_LC}::Python) set(SOURCES HTTPPythonInvoker.cpp HTTPPythonWsgiInvoker.cpp) From 6e0f90becdcac533f8b480fe0a5bcd68c30be7b2 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 19:36:44 +1000 Subject: [PATCH 136/651] [cmake][modules] FindVAAPI update to target usage --- cmake/modules/FindVAAPI.cmake | 124 ++++++++++-------- .../DVDCodecs/Video/CMakeLists.txt | 2 +- .../VideoRenderers/HwDecRender/CMakeLists.txt | 2 +- 3 files changed, 73 insertions(+), 55 deletions(-) diff --git a/cmake/modules/FindVAAPI.cmake b/cmake/modules/FindVAAPI.cmake index 90c4bf6e5cd77..68d08bddfb27b 100644 --- a/cmake/modules/FindVAAPI.cmake +++ b/cmake/modules/FindVAAPI.cmake @@ -3,66 +3,84 @@ # --------- # Finds the VAAPI library # -# This will define the following variables:: +# This will define the following target: # -# VAAPI_FOUND - system has VAAPI -# VAAPI_INCLUDE_DIRS - the VAAPI include directory -# VAAPI_LIBRARIES - the VAAPI libraries -# VAAPI_DEFINITIONS - the VAAPI definitions +# ${APP_NAME_LC}::VAAPI - The VAAPI library -find_package(PkgConfig) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + find_package(PkgConfig) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_VAAPI libva libva-drm libva-wayland libva-x11 QUIET) -endif() + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_VAAPI libva libva-drm libva-wayland libva-x11 QUIET) + endif() -set(REQUIRED_VARS "VAAPI_libva_LIBRARY" "VAAPI_libva-drm_LIBRARY" "VAAPI_libva_INCLUDE_DIR" "VAAPI_libva-drm_INCLUDE_DIR") + set(REQUIRED_VARS "VAAPI_libva_LIBRARY" "VAAPI_libva-drm_LIBRARY" "VAAPI_libva_INCLUDE_DIR" "VAAPI_libva-drm_INCLUDE_DIR") -find_path(VAAPI_libva_INCLUDE_DIR va/va.h - HINTS ${PC_VAAPI_libva_INCLUDEDIR}) -find_library(VAAPI_libva_LIBRARY NAMES va - HINTS ${PC_VAAPI_libva_LIBDIR}) -find_path(VAAPI_libva-drm_INCLUDE_DIR va/va_drm.h - HINTS ${PC_VAAPI_libva-drm_INCLUDEDIR}) -find_library(VAAPI_libva-drm_LIBRARY NAMES va-drm - HINTS ${PC_VAAPI_libva-drm_LIBDIR}) -if("wayland" IN_LIST CORE_PLATFORM_NAME_LC) - find_path(VAAPI_libva-wayland_INCLUDE_DIR va/va_wayland.h - HINTS ${PC_VAAPI_libva-wayland_INCLUDEDIR}) - find_library(VAAPI_libva-wayland_LIBRARY NAMES va-wayland - HINTS ${PC_VAAPI_libva-wayland_LIBDIR}) - list(APPEND REQUIRED_VARS "VAAPI_libva-wayland_INCLUDE_DIR" "VAAPI_libva-wayland_LIBRARY") -endif() -if("x11" IN_LIST CORE_PLATFORM_NAME_LC) - find_path(VAAPI_libva-x11_INCLUDE_DIR va/va_x11.h - HINTS ${PC_VAAPI_libva-x11_INCLUDEDIR}) - find_library(VAAPI_libva-x11_LIBRARY NAMES va-x11 - HINTS ${PC_VAAPI_libva-x11_LIBDIR}) - list(APPEND REQUIRED_VARS "VAAPI_libva-x11_INCLUDE_DIR" "VAAPI_libva-x11_LIBRARY") -endif() + find_path(VAAPI_libva_INCLUDE_DIR va/va.h + HINTS ${PC_VAAPI_libva_INCLUDEDIR}) + find_library(VAAPI_libva_LIBRARY NAMES va + HINTS ${PC_VAAPI_libva_LIBDIR}) + find_path(VAAPI_libva-drm_INCLUDE_DIR va/va_drm.h + HINTS ${PC_VAAPI_libva-drm_INCLUDEDIR}) + find_library(VAAPI_libva-drm_LIBRARY NAMES va-drm + HINTS ${PC_VAAPI_libva-drm_LIBDIR}) + if("wayland" IN_LIST CORE_PLATFORM_NAME_LC) + find_path(VAAPI_libva-wayland_INCLUDE_DIR va/va_wayland.h + HINTS ${PC_VAAPI_libva-wayland_INCLUDEDIR}) + find_library(VAAPI_libva-wayland_LIBRARY NAMES va-wayland + HINTS ${PC_VAAPI_libva-wayland_LIBDIR}) + list(APPEND REQUIRED_VARS "VAAPI_libva-wayland_INCLUDE_DIR" "VAAPI_libva-wayland_LIBRARY") + endif() + if("x11" IN_LIST CORE_PLATFORM_NAME_LC) + find_path(VAAPI_libva-x11_INCLUDE_DIR va/va_x11.h + HINTS ${PC_VAAPI_libva-x11_INCLUDEDIR}) + find_library(VAAPI_libva-x11_LIBRARY NAMES va-x11 + HINTS ${PC_VAAPI_libva-x11_LIBDIR}) + list(APPEND REQUIRED_VARS "VAAPI_libva-x11_INCLUDE_DIR" "VAAPI_libva-x11_LIBRARY") + endif() -if(PC_VAAPI_libva_VERSION) - set(VAAPI_VERSION_STRING ${PC_VAAPI_libva_VERSION}) -elseif(VAAPI_INCLUDE_DIR AND EXISTS "${VAAPI_INCLUDE_DIR}/va/va_version.h") - file(STRINGS "${VAAPI_INCLUDE_DIR}/va/va_version.h" vaapi_version_str REGEX "^#define[\t ]+VA_VERSION_S[\t ]+\".*\".*") - string(REGEX REPLACE "^#define[\t ]+VA_VERSION_S[\t ]+\"([^\"]+)\".*" "\\1" VAAPI_VERSION_STRING "${vaapi_version_str}") - unset(vaapi_version_str) -endif() + if(PC_VAAPI_libva_VERSION) + set(VAAPI_VERSION_STRING ${PC_VAAPI_libva_VERSION}) + elseif(VAAPI_INCLUDE_DIR AND EXISTS "${VAAPI_INCLUDE_DIR}/va/va_version.h") + file(STRINGS "${VAAPI_INCLUDE_DIR}/va/va_version.h" vaapi_version_str REGEX "^#define[\t ]+VA_VERSION_S[\t ]+\".*\".*") + string(REGEX REPLACE "^#define[\t ]+VA_VERSION_S[\t ]+\"([^\"]+)\".*" "\\1" VAAPI_VERSION_STRING "${vaapi_version_str}") + unset(vaapi_version_str) + endif() -if(NOT VAAPI_FIND_VERSION) - set(VAAPI_FIND_VERSION 0.39.0) -endif() + if(NOT VAAPI_FIND_VERSION) + set(VAAPI_FIND_VERSION 0.39.0) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(VAAPI - REQUIRED_VARS ${REQUIRED_VARS} - VERSION_VAR VAAPI_VERSION_STRING) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(VAAPI + REQUIRED_VARS ${REQUIRED_VARS} + VERSION_VAR VAAPI_VERSION_STRING) -if(VAAPI_FOUND) - set(VAAPI_INCLUDE_DIRS ${VAAPI_INCLUDE_DIR} ${VAAPI_DRM_INCLUDE_DIR} ${VAAPI_WAYLAND_INCLUDE_DIR} ${VAAPI_X11_INCLUDE_DIR}) - set(VAAPI_LIBRARIES ${VAAPI_libva_LIBRARY} ${VAAPI_libva-drm_LIBRARY} ${VAAPI_libva-wayland_LIBRARY} ${VAAPI_libva-x11_LIBRARY}) - set(VAAPI_DEFINITIONS -DHAVE_LIBVA=1) -endif() + if(VAAPI_FOUND) + if(VAAPI_libva-x11_LIBRARY) + add_library(${APP_NAME_LC}::va-x11 UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::va-x11 PROPERTIES + IMPORTED_LOCATION "${VAAPI_libva-x11_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${VAAPI_libva-x11_INCLUDE_DIR}") + endif() + + if(VAAPI_libva-wayland_LIBRARY) + add_library(${APP_NAME_LC}::va-wayland UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::va-wayland PROPERTIES + IMPORTED_LOCATION "${VAAPI_libva-wayland_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${VAAPI_libva-wayland_INCLUDE_DIR}") + endif() -mark_as_advanced(VAAPI_libva_INCLUDE_DIR VAAPI_libva-drm_INCLUDE_DIR VAAPI_libva-wayland_INCLUDE_DIR VAAPI_libva-x11_INCLUDE_DIR - VAAPI_libva_LIBRARY VAAPI_libva-drm_LIBRARY VAAPI_libva-wayland_LIBRARY VAAPI_libva-x11_LIBRARY) + add_library(${APP_NAME_LC}::va-drm UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::va-drm PROPERTIES + IMPORTED_LOCATION "${VAAPI_libva-drm_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${VAAPI_libva-drm_INCLUDE_DIR}") + + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${VAAPI_libva_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${VAAPI_libva_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${APP_NAME_LC}::va-drm;$;$" + INTERFACE_COMPILE_DEFINITIONS "HAVE_LIBVA") + endif() +endif() diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt b/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt index d21f6cc99567a..6790d8b0abda3 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt @@ -21,7 +21,7 @@ if(TARGET ${APP_NAME_LC}::VDPAU) list(APPEND HEADERS VDPAU.h) endif() -if(VAAPI_FOUND) +if(TARGET ${APP_NAME_LC}::VAAPI) list(APPEND SOURCES VAAPI.cpp) list(APPEND HEADERS VAAPI.h) endif() diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt index 14bd61c5b8550..495101203c78f 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt @@ -5,7 +5,7 @@ if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) list(APPEND HEADERS DXVAEnumeratorHD.h) endif() -if(VAAPI_FOUND) +if(TARGET ${APP_NAME_LC}::VAAPI) if(TARGET ${APP_NAME_LC}::OpenGl) list(APPEND SOURCES RendererVAAPIGL.cpp) list(APPEND HEADERS RendererVAAPIGL.h) From 8611aef2a092c0027d7bb22a93721cc11bfcc76b Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 19:59:30 +1000 Subject: [PATCH 137/651] [cmake][modules/buildtools] Separate WaylandPP scanner to individual find module --- .../buildtools/FindWaylandPPScanner.cmake | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 cmake/modules/buildtools/FindWaylandPPScanner.cmake diff --git a/cmake/modules/buildtools/FindWaylandPPScanner.cmake b/cmake/modules/buildtools/FindWaylandPPScanner.cmake new file mode 100644 index 0000000000000..ff80dbd3f4c27 --- /dev/null +++ b/cmake/modules/buildtools/FindWaylandPPScanner.cmake @@ -0,0 +1,34 @@ +# FindWaylandPPScanner +# -------- +# Find the WaylandPPScanner Tool +# +# This will define the following target: +# +# wayland::waylandppscanner - The FXC compiler + +if(NOT wayland::waylandppscanner) + + find_package(PkgConfig) + pkg_check_modules(PC_WAYLANDPP_SCANNER wayland-scanner++ QUIET) + + if(PC_WAYLANDPP_SCANNER_FOUND) + pkg_get_variable(PC_WAYLANDPP_SCANNER wayland-scanner++ wayland_scannerpp) + endif() + + find_program(WAYLANDPP_SCANNER wayland-scanner++ HINTS ${PC_WAYLANDPP_SCANNER}) + + if(WAYLANDPP_SCANNER) + + include(FindPackageMessage) + find_package_message(WaylandPPScanner "Found WaylandPP Scanner: ${WAYLANDPP_SCANNER}" "[${WAYLANDPP_SCANNER}]") + + add_executable(wayland::waylandppscanner IMPORTED) + set_target_properties(wayland::waylandppscanner PROPERTIES + IMPORTED_LOCATION "${WAYLANDPP_SCANNER}" + FOLDER "External Projects") + else() + if(WaylandPPScanner_FIND_REQUIRED) + message(FATAL_ERROR "Could NOT find WaylandPP Scanner") + endif() + endif() +endif() From 4f66b90d077f723c833e00fc6708167647185ac2 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 20:00:05 +1000 Subject: [PATCH 138/651] [cmake][modules] FindWaylandpp update to target usage --- cmake/modules/FindWaylandpp.cmake | 174 +++++++++++++------------ cmake/scripts/linux/ExtraTargets.cmake | 5 +- cmake/scripts/webos/ExtraTargets.cmake | 5 +- 3 files changed, 97 insertions(+), 87 deletions(-) diff --git a/cmake/modules/FindWaylandpp.cmake b/cmake/modules/FindWaylandpp.cmake index 7b28892a04e65..cc9d0827fc906 100644 --- a/cmake/modules/FindWaylandpp.cmake +++ b/cmake/modules/FindWaylandpp.cmake @@ -2,98 +2,106 @@ # ------------- # Finds the waylandpp library # -# This will define the following variables:: +# This will define the following target: # -# WAYLANDPP_FOUND - the system has waylandpp -# WAYLANDPP_INCLUDE_DIRS - the waylandpp include directory -# WAYLANDPP_LIBRARIES - the waylandpp libraries -# WAYLANDPP_DEFINITIONS - the waylandpp definitions -# WAYLANDPP_SCANNER - path to wayland-scanner++ - -find_package(PkgConfig) -pkg_check_modules(PC_WAYLANDPP wayland-client++ wayland-egl++ wayland-cursor++ QUIET) - -if(PC_WAYLANDPP_FOUND) - pkg_get_variable(PC_WAYLANDPP_PKGDATADIR wayland-client++ pkgdatadir) -else() - message(SEND_ERROR "wayland-client++ not found via pkg-config") -endif() +# ${APP_NAME_LC}::Waylandpp - The waylandpp library -pkg_check_modules(PC_WAYLANDPP_SCANNER wayland-scanner++ QUIET) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + find_package(PkgConfig) + pkg_check_modules(PC_WAYLANDPP wayland-client++ wayland-egl++ wayland-cursor++ QUIET) -if(PC_WAYLANDPP_SCANNER_FOUND) - pkg_get_variable(PC_WAYLANDPP_SCANNER wayland-scanner++ wayland_scannerpp) -else() - message(SEND_ERROR "wayland-scanner++ not found via pkg-config") -endif() + if(PC_WAYLANDPP_FOUND) + pkg_get_variable(PC_WAYLANDPP_PKGDATADIR wayland-client++ pkgdatadir) + else() + message(SEND_ERROR "wayland-client++ not found via pkg-config") + endif() -find_path(WAYLANDPP_INCLUDE_DIR wayland-client.hpp HINTS ${PC_WAYLANDPP_INCLUDEDIR}) + find_path(WAYLANDPP_INCLUDE_DIR wayland-client.hpp HINTS ${PC_WAYLANDPP_INCLUDEDIR}) + + find_library(WAYLANDPP_CLIENT_LIBRARY NAMES wayland-client++ + HINTS ${PC_WAYLANDPP_LIBRARY_DIRS}) + + find_library(WAYLANDPP_CURSOR_LIBRARY NAMES wayland-cursor++ + HINTS ${PC_WAYLANDPP_LIBRARY_DIRS}) + + find_library(WAYLANDPP_EGL_LIBRARY NAMES wayland-egl++ + HINTS ${PC_WAYLANDPP_LIBRARY_DIRS}) + + if(KODI_DEPENDSBUILD) + pkg_check_modules(PC_WAYLANDC wayland-client wayland-egl wayland-cursor QUIET) + + if(PREFER_TOOLCHAIN_PATH) + set(WAYLAND_SEARCH_PATH ${PREFER_TOOLCHAIN_PATH} + NO_DEFAULT_PATH + PATH_SUFFIXES usr/lib) + else() + set(WAYLAND_SEARCH_PATH ${PC_WAYLANDC_LIBRARY_DIRS}) + endif() + + find_library(WAYLANDC_CLIENT_LIBRARY NAMES wayland-client + HINTS ${WAYLAND_SEARCH_PATH} + REQUIRED) + find_library(WAYLANDC_CURSOR_LIBRARY NAMES wayland-cursor + HINTS ${WAYLAND_SEARCH_PATH} + REQUIRED) + find_library(WAYLANDC_EGL_LIBRARY NAMES wayland-egl + HINTS ${WAYLAND_SEARCH_PATH} + REQUIRED) + + set(WAYLANDPP_STATIC_DEPS ${WAYLANDC_CLIENT_LIBRARY} + ${WAYLANDC_CURSOR_LIBRARY} + ${WAYLANDC_EGL_LIBRARY}) + endif() -find_library(WAYLANDPP_CLIENT_LIBRARY NAMES wayland-client++ - HINTS ${PC_WAYLANDPP_LIBRARY_DIRS}) + # Promote to cache variables so all code can access it + set(WAYLANDPP_PROTOCOLS_DIR "${PC_WAYLANDPP_PKGDATADIR}/protocols" CACHE INTERNAL "") -find_library(WAYLANDPP_CURSOR_LIBRARY NAMES wayland-cursor++ - HINTS ${PC_WAYLANDPP_LIBRARY_DIRS}) + include (FindPackageHandleStandardArgs) + find_package_handle_standard_args(Waylandpp + REQUIRED_VARS WAYLANDPP_INCLUDE_DIR + WAYLANDPP_CLIENT_LIBRARY + WAYLANDPP_CURSOR_LIBRARY + WAYLANDPP_EGL_LIBRARY + VERSION_VAR WAYLANDPP_wayland-client++_VERSION) -find_library(WAYLANDPP_EGL_LIBRARY NAMES wayland-egl++ - HINTS ${PC_WAYLANDPP_LIBRARY_DIRS}) + if(WAYLANDPP_FOUND) -if(KODI_DEPENDSBUILD) - pkg_check_modules(PC_WAYLANDC wayland-client wayland-egl wayland-cursor QUIET) + find_package(WaylandPPScanner REQUIRED) - if(PREFER_TOOLCHAIN_PATH) - set(WAYLAND_SEARCH_PATH ${PREFER_TOOLCHAIN_PATH} - NO_DEFAULT_PATH - PATH_SUFFIXES usr/lib) - else() - set(WAYLAND_SEARCH_PATH ${PC_WAYLANDC_LIBRARY_DIRS}) - endif() + set(WAYLANDPP_INCLUDE_DIRS ${WAYLANDPP_INCLUDE_DIR}) + set(WAYLANDPP_LIBRARIES + ${WAYLANDPP_STATIC_DEPS}) - find_library(WAYLANDC_CLIENT_LIBRARY NAMES wayland-client - HINTS ${WAYLAND_SEARCH_PATH} - REQUIRED) - find_library(WAYLANDC_CURSOR_LIBRARY NAMES wayland-cursor - HINTS ${WAYLAND_SEARCH_PATH} - REQUIRED) - find_library(WAYLANDC_EGL_LIBRARY NAMES wayland-egl - HINTS ${WAYLAND_SEARCH_PATH} - REQUIRED) - - set(WAYLANDPP_STATIC_DEPS ${WAYLANDC_CLIENT_LIBRARY} - ${WAYLANDC_CURSOR_LIBRARY} - ${WAYLANDC_EGL_LIBRARY}) -endif() + if(KODI_DEPENDSBUILD) + add_library(${APP_NAME_LC}::waylandc-egl UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::waylandc-egl PROPERTIES + IMPORTED_LOCATION "${WAYLANDC_EGL_LIBRARY}") -# Promote to cache variables so all code can access it -set(WAYLANDPP_PROTOCOLS_DIR "${PC_WAYLANDPP_PKGDATADIR}/protocols" CACHE INTERNAL "") - -# wayland-scanner++ is from native/host system in case of cross-compilation, so -# it's ok if we don't find it with pkgconfig -find_program(WAYLANDPP_SCANNER wayland-scanner++ HINTS ${PC_WAYLANDPP_SCANNER}) - -include (FindPackageHandleStandardArgs) -find_package_handle_standard_args(Waylandpp - REQUIRED_VARS WAYLANDPP_INCLUDE_DIR - WAYLANDPP_CLIENT_LIBRARY - WAYLANDPP_CURSOR_LIBRARY - WAYLANDPP_EGL_LIBRARY - WAYLANDPP_SCANNER - VERSION_VAR WAYLANDPP_wayland-client++_VERSION) - -if(WAYLANDPP_FOUND) - set(WAYLANDPP_INCLUDE_DIRS ${WAYLANDPP_INCLUDE_DIR}) - set(WAYLANDPP_LIBRARIES ${WAYLANDPP_CLIENT_LIBRARY} - ${WAYLANDPP_CURSOR_LIBRARY} - ${WAYLANDPP_EGL_LIBRARY} - ${WAYLANDPP_STATIC_DEPS}) - set(WAYLANDPP_DEFINITIONS -DHAVE_WAYLAND=1) -endif() + add_library(${APP_NAME_LC}::waylandc-cursor UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::waylandc-cursor PROPERTIES + IMPORTED_LOCATION "${WAYLANDC_CURSOR_LIBRARY}") + + add_library(${APP_NAME_LC}::waylandc-client UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::waylandc-client PROPERTIES + IMPORTED_LOCATION "${WAYLANDC_CLIENT_LIBRARY}") + endif() + + add_library(${APP_NAME_LC}::waylandpp-egl UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::waylandpp-egl PROPERTIES + IMPORTED_LOCATION "${WAYLANDPP_EGL_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${WAYLANDPP_INCLUDE_DIR}") + + add_library(${APP_NAME_LC}::waylandpp-cursor UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::waylandpp-cursor PROPERTIES + IMPORTED_LOCATION "${WAYLANDPP_CURSOR_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${WAYLANDPP_INCLUDE_DIR}") -mark_as_advanced(WAYLANDPP_INCLUDE_DIR - WAYLANDPP_CLIENT_LIBRARY - WAYLANDC_CLIENT_LIBRARY - WAYLANDPP_CURSOR_LIBRARY - WAYLANDC_CURSOR_LIBRARY - WAYLANDPP_EGL_LIBRARY - WAYLANDC_EGL_LIBRARY - WAYLANDPP_SCANNER) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${WAYLANDPP_CLIENT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${WAYLANDPP_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAVE_WAYLAND + INTERFACE_LINK_LIBRARIES "${APP_NAME_LC}::waylandpp-cursor;${APP_NAME_LC}::waylandpp-egl;$;$;$") + + endif() +endif() diff --git a/cmake/scripts/linux/ExtraTargets.cmake b/cmake/scripts/linux/ExtraTargets.cmake index f402f7c491126..37156923b9c8d 100644 --- a/cmake/scripts/linux/ExtraTargets.cmake +++ b/cmake/scripts/linux/ExtraTargets.cmake @@ -24,8 +24,9 @@ if("wayland" IN_LIST CORE_PLATFORM_NAME_LC) "${WAYLAND_PROTOCOLS_DIR}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml") add_custom_command(OUTPUT "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-extra-protocols.hpp" "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-extra-protocols.cpp" - COMMAND "${WAYLANDPP_SCANNER}" ${PROTOCOL_XMLS} "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-extra-protocols.hpp" "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-extra-protocols.cpp" - DEPENDS "${WAYLANDPP_SCANNER}" ${PROTOCOL_XMLS} + COMMAND wayland::waylandppscanner + ARGS ${PROTOCOL_XMLS} "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-extra-protocols.hpp" "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-extra-protocols.cpp" + DEPENDS wayland::waylandppscanner ${PROTOCOL_XMLS} COMMENT "Generating wayland-protocols C++ wrappers") if("webos" IN_LIST CORE_PLATFORM_NAME_LC) diff --git a/cmake/scripts/webos/ExtraTargets.cmake b/cmake/scripts/webos/ExtraTargets.cmake index 3380c6a3be656..46c78edf624dc 100644 --- a/cmake/scripts/webos/ExtraTargets.cmake +++ b/cmake/scripts/webos/ExtraTargets.cmake @@ -3,8 +3,9 @@ set(WEBOS_PROTOCOL_XMLS "${WAYLANDPROTOCOLSWEBOS_PROTOCOLSDIR}/webos-shell.xml" "${WAYLANDPROTOCOLSWEBOS_PROTOCOLSDIR}/webos-foreign.xml" ) add_custom_command(OUTPUT "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-webos-protocols.hpp" "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-webos-protocols.cpp" - COMMAND "${WAYLANDPP_SCANNER}" ${WEBOS_PROTOCOL_XMLS} "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-webos-protocols.hpp" "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-webos-protocols.cpp" - DEPENDS "${WAYLANDPP_SCANNER}" ${WEBOS_PROTOCOL_XMLS} + COMMAND wayland::waylandppscanner + ARGS ${WEBOS_PROTOCOL_XMLS} "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-webos-protocols.hpp" "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-webos-protocols.cpp" + DEPENDS wayland::waylandppscanner ${WEBOS_PROTOCOL_XMLS} COMMENT "Generating wayland-webos C++ wrappers") add_custom_target(generate-wayland-webos-protocols DEPENDS wayland-webos-protocols.hpp) # ToDo: turn this into a TARGET OBJECT. For now, a custum target doesnt play nice with From 8393c54f92ce27ab4cae33f0471e2cc52ea7894e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 20:05:13 +1000 Subject: [PATCH 139/651] [cmake][modules] FindBluray cleanup and use core_target_link_libraries --- cmake/modules/FindBluray.cmake | 24 +++++++++---------- .../DVDInputStreams/CMakeLists.txt | 2 +- xbmc/filesystem/CMakeLists.txt | 2 +- xbmc/settings/CMakeLists.txt | 2 +- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/cmake/modules/FindBluray.cmake b/cmake/modules/FindBluray.cmake index 30d27d9bdc1a3..2afb98841e0af 100644 --- a/cmake/modules/FindBluray.cmake +++ b/cmake/modules/FindBluray.cmake @@ -5,9 +5,9 @@ # # This will define the following target: # -# Bluray::Bluray - The libbluray library +# ${APP_NAME_LC}::Bluray - The libbluray library -if(NOT TARGET Bluray::Bluray) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) @@ -50,11 +50,11 @@ if(NOT TARGET Bluray::Bluray) VERSION_VAR BLURAY_VERSION) if(BLURAY_FOUND) - add_library(Bluray::Bluray UNKNOWN IMPORTED) - set_target_properties(Bluray::Bluray PROPERTIES - IMPORTED_LOCATION "${BLURAY_LIBRARY}" - INTERFACE_COMPILE_DEFINITIONS "HAVE_LIBBLURAY=1" - INTERFACE_INCLUDE_DIRECTORIES "${BLURAY_INCLUDEDIR}") + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${BLURAY_LIBRARY}" + INTERFACE_COMPILE_DEFINITIONS "HAVE_LIBBLURAY=1" + INTERFACE_INCLUDE_DIRECTORIES "${BLURAY_INCLUDEDIR}") # Add link libraries for static lib usage if(${BLURAY_LIBRARY} MATCHES ".+\.a$" AND BLURAY_LINK_LIBRARIES) @@ -64,15 +64,13 @@ if(NOT TARGET Bluray::Bluray) # Remove own library - eg libbluray.a list(FILTER BLURAY_LINK_LIBRARIES EXCLUDE REGEX ".*bluray.*\.a$") - set_target_properties(Bluray::Bluray PROPERTIES - INTERFACE_LINK_LIBRARIES "${BLURAY_LINK_LIBRARIES}") + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_LINK_LIBRARIES "${BLURAY_LINK_LIBRARIES}") endif() if(NOT CORE_PLATFORM_NAME_LC STREQUAL windowsstore) - set_property(TARGET Bluray::Bluray APPEND PROPERTY - INTERFACE_COMPILE_DEFINITIONS "HAVE_LIBBLURAY_BDJ") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS "HAVE_LIBBLURAY_BDJ") endif() - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP Bluray::Bluray) endif() endif() diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/CMakeLists.txt b/xbmc/cores/VideoPlayer/DVDInputStreams/CMakeLists.txt index 576ddda150a0a..6e3789c99dd43 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/CMakeLists.txt @@ -30,7 +30,7 @@ set(HEADERS BlurayStateSerializer.h InputStreamPVRChannel.h InputStreamPVRRecording.h) -if(BLURAY_FOUND) +if(TARGET ${APP_NAME_LC}::Bluray) list(APPEND SOURCES DVDInputStreamBluray.cpp) list(APPEND HEADERS DVDInputStreamBluray.h) endif() diff --git a/xbmc/filesystem/CMakeLists.txt b/xbmc/filesystem/CMakeLists.txt index 379cdc3a79f32..c0c699b8b9635 100644 --- a/xbmc/filesystem/CMakeLists.txt +++ b/xbmc/filesystem/CMakeLists.txt @@ -140,7 +140,7 @@ if(TARGET ${APP_NAME_LC}::Udfread) UDFFile.h) endif() -if(BLURAY_FOUND) +if(TARGET ${APP_NAME_LC}::Bluray) list(APPEND SOURCES BlurayCallback.cpp BlurayDirectory.cpp BlurayFile.cpp) diff --git a/xbmc/settings/CMakeLists.txt b/xbmc/settings/CMakeLists.txt index d477f51d5d91f..8aff642054f1d 100644 --- a/xbmc/settings/CMakeLists.txt +++ b/xbmc/settings/CMakeLists.txt @@ -44,7 +44,7 @@ set(HEADERS AdvancedSettings.h SettingsComponent.h SubtitlesSettings.h) -if(BLURAY_FOUND) +if(TARGET ${APP_NAME_LC}::Bluray) list(APPEND SOURCES DiscSettings.cpp) endif() From 83c2599107e3855e28b83ac0a590593f266dc901 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 10:08:13 +1000 Subject: [PATCH 140/651] [cmake][interfaces/swig] Remove unused cmake variable --- xbmc/interfaces/swig/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/xbmc/interfaces/swig/CMakeLists.txt b/xbmc/interfaces/swig/CMakeLists.txt index ae23c77261af4..9d9934cf00ba1 100644 --- a/xbmc/interfaces/swig/CMakeLists.txt +++ b/xbmc/interfaces/swig/CMakeLists.txt @@ -110,7 +110,6 @@ set(GROOVY_DIR ${CMAKE_SOURCE_DIR}/tools/codegenerator/groovy) foreach(INPUT IN LISTS INPUTS) generate_file(${INPUT}) - list(APPEND GEN_SRCS ${CMAKE_CURRENT_BINARY_DIR}/${INPUT}.cpp) endforeach() add_library(python_binding STATIC ${SOURCES}) From 5a35fb7cf8fb4bbd23a0fb85fb6c2fafec5d2066 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 13:14:45 +1000 Subject: [PATCH 141/651] [cmake] Enable CMP0079 https://cmake.org/cmake/help/latest/policy/CMP0079.html This allows using target_link_libraries to a target not in the same calling directory --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 635b7b52185cb..18c3bc2977e9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,11 @@ if(POLICY CMP0069) cmake_policy(SET CMP0069 NEW) endif() +if(POLICY CMP0079) + set(CMAKE_POLICY_DEFAULT_CMP0079 NEW) + cmake_policy(SET CMP0079 NEW) +endif() + if(POLICY CMP0135) set(CMAKE_POLICY_DEFAULT_CMP0135 NEW) cmake_policy(SET CMP0135 NEW) From ee3e70f903c68f37993a8d91d201ffa2fb04dd54 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 10:14:50 +1000 Subject: [PATCH 142/651] [cmake][interfaces/swig] use modern target linking for python_binding --- xbmc/interfaces/swig/CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/xbmc/interfaces/swig/CMakeLists.txt b/xbmc/interfaces/swig/CMakeLists.txt index 9d9934cf00ba1..1951b2a336789 100644 --- a/xbmc/interfaces/swig/CMakeLists.txt +++ b/xbmc/interfaces/swig/CMakeLists.txt @@ -115,10 +115,6 @@ endforeach() add_library(python_binding STATIC ${SOURCES}) set_target_properties(python_binding PROPERTIES POSITION_INDEPENDENT_CODE TRUE FOLDER "Build Utilities") -set(core_DEPENDS python_binding ${core_DEPENDS} CACHE STRING "" FORCE) - -# This target is not run through our regular macro's. Explicitly link required target -target_link_libraries(python_binding PRIVATE ${APP_NAME_LC}::Spdlog) if(CORE_SYSTEM_NAME STREQUAL windowsstore) set_target_properties(python_binding PROPERTIES STATIC_LIBRARY_FLAGS "/ignore:4264") @@ -127,3 +123,8 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL GNU) set_target_properties(python_binding PROPERTIES COMPILE_FLAGS -Wno-cast-function-type) # from -Wextra endif() + +# Add target dependencies to lib +core_target_link_libraries(python_binding) +# Link this target lib to core +target_link_libraries(lib${APP_NAME_LC} PUBLIC python_binding) From 720666492bbd8a5f05653cfdb2f12faed19857e7 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 16 May 2024 10:30:12 +1000 Subject: [PATCH 143/651] [cmake][libupnp] use target linking to core --- lib/libUPnP/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/libUPnP/CMakeLists.txt b/lib/libUPnP/CMakeLists.txt index c610060649e19..313893e41d037 100644 --- a/lib/libUPnP/CMakeLists.txt +++ b/lib/libUPnP/CMakeLists.txt @@ -127,7 +127,9 @@ set_target_properties(upnp PROPERTIES CXX_CPPCHECK "") set_target_properties(upnp PROPERTIES CXX_INCLUDE_WHAT_YOU_USE "") set_target_properties(upnp PROPERTIES FOLDER lib) source_group_by_folder(upnp) -set(core_DEPENDS upnp ${core_DEPENDS} CACHE STRING "" FORCE) + +# Link this target lib to core +target_link_libraries(lib${APP_NAME_LC} PUBLIC upnp) if(CORE_SYSTEM_NAME STREQUAL windowsstore) set_target_properties(upnp PROPERTIES STATIC_LIBRARY_FLAGS "/ignore:4264") From 35d29f02ed2ce7822cbfc84dbf3dfd6a496fac18 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Fri, 17 May 2024 22:10:20 +1000 Subject: [PATCH 144/651] [cmake][macros] update core_link_library to allow targets for data_arg --- cmake/scripts/darwin_embedded/Macros.cmake | 8 +++++++- cmake/scripts/freebsd/Macros.cmake | 8 +++++++- cmake/scripts/linux/Macros.cmake | 8 +++++++- cmake/scripts/osx/Macros.cmake | 8 +++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/cmake/scripts/darwin_embedded/Macros.cmake b/cmake/scripts/darwin_embedded/Macros.cmake index 5084bc1a54042..f101044b11082 100644 --- a/cmake/scripts/darwin_embedded/Macros.cmake +++ b/cmake/scripts/darwin_embedded/Macros.cmake @@ -37,7 +37,13 @@ function(core_link_library lib wraplib) list(APPEND export ${arg}) endforeach() elseif(check_arg STREQUAL archives) - set(extra_libs ${data_arg}) + foreach(_data_arg ${data_arg}) + if(TARGET ${_data_arg}) + list(APPEND extra_libs $) + else() + list(APPEND extra_libs ${_data_arg}) + endif() + endforeach() endif() get_filename_component(dir ${wraplib} DIRECTORY) diff --git a/cmake/scripts/freebsd/Macros.cmake b/cmake/scripts/freebsd/Macros.cmake index ef5aed38d17a6..f1877cf1e4603 100644 --- a/cmake/scripts/freebsd/Macros.cmake +++ b/cmake/scripts/freebsd/Macros.cmake @@ -28,7 +28,13 @@ function(core_link_library lib wraplib) list(APPEND export ${arg}) endforeach() elseif(check_arg STREQUAL archives) - set(extra_libs ${data_arg}) + foreach(_data_arg ${data_arg}) + if(TARGET ${_data_arg}) + list(APPEND extra_libs $) + else() + list(APPEND extra_libs ${_data_arg}) + endif() + endforeach() endif() string(REGEX REPLACE "[ ]+" ";" _flags "${CMAKE_SHARED_LINKER_FLAGS}") diff --git a/cmake/scripts/linux/Macros.cmake b/cmake/scripts/linux/Macros.cmake index 37243a77cdbc6..5ffb8171ebc7b 100644 --- a/cmake/scripts/linux/Macros.cmake +++ b/cmake/scripts/linux/Macros.cmake @@ -28,7 +28,13 @@ function(core_link_library lib wraplib) list(APPEND export ${arg}) endforeach() elseif(check_arg STREQUAL archives) - set(extra_libs ${data_arg}) + foreach(_data_arg ${data_arg}) + if(TARGET ${_data_arg}) + list(APPEND extra_libs $) + else() + list(APPEND extra_libs ${_data_arg}) + endif() + endforeach() endif() string(REGEX REPLACE "[ ]+" ";" _flags "${CMAKE_SHARED_LINKER_FLAGS}") diff --git a/cmake/scripts/osx/Macros.cmake b/cmake/scripts/osx/Macros.cmake index f32a7358b5e38..6d41981db453d 100644 --- a/cmake/scripts/osx/Macros.cmake +++ b/cmake/scripts/osx/Macros.cmake @@ -24,7 +24,13 @@ function(core_link_library lib wraplib) list(APPEND export ${arg}) endforeach() elseif(check_arg STREQUAL archives) - set(extra_libs ${data_arg}) + foreach(_data_arg ${data_arg}) + if(TARGET ${_data_arg}) + list(APPEND extra_libs $) + else() + list(APPEND extra_libs ${_data_arg}) + endif() + endforeach() endif() get_filename_component(dir ${wraplib} DIRECTORY) From 807dddf86350a154a390a495e06776bf77a7e01d Mon Sep 17 00:00:00 2001 From: fuzzard Date: Fri, 17 May 2024 22:09:18 +1000 Subject: [PATCH 145/651] [cmake][modules] FindDvd* update target usage --- cmake/modules/FindLibDvd.cmake | 19 ++++++----------- cmake/modules/FindLibDvdCSS.cmake | 24 ++++++++-------------- cmake/modules/FindLibDvdNav.cmake | 33 +++++++++++++++--------------- cmake/modules/FindLibDvdRead.cmake | 28 +++++++++---------------- 4 files changed, 41 insertions(+), 63 deletions(-) diff --git a/cmake/modules/FindLibDvd.cmake b/cmake/modules/FindLibDvd.cmake index dd7d8a26d2168..25277b587867d 100644 --- a/cmake/modules/FindLibDvd.cmake +++ b/cmake/modules/FindLibDvd.cmake @@ -5,25 +5,18 @@ set(FPHSA_NAME_MISMATCHED 1) find_package(LibDvdNav MODULE REQUIRED) unset(FPHSA_NAME_MISMATCHED) -set(_dvdlibs ${LIBDVDREAD_LIBRARY} ${LIBDVDCSS_LIBRARY}) +set(_dvdlibs LibDvdNav::LibDvdNav + $<$:LibDvdCSS::LibDvdCSS>>) if(NOT CORE_SYSTEM_NAME MATCHES windows) # link a shared dvdnav library that includes the whole archives of dvdread and dvdcss as well # the quotes around _dvdlibs are on purpose, since we want to pass a list to the function that will be unpacked automatically - core_link_library(${LIBDVDNAV_LIBRARY} system/players/VideoPlayer/libdvdnav libdvdnav archives "${_dvdlibs}") + core_link_library(LibDvdNav::LibDvdNav system/players/VideoPlayer/libdvdnav libdvdnav archives "${_dvdlibs}") else() set(LIBDVD_TARGET_DIR .) copy_file_to_buildtree(${DEPENDS_PATH}/bin/libdvdnav.dll DIRECTORY ${LIBDVD_TARGET_DIR}) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP LibDvdNav::LibDvdNav) endif() -set(LIBDVD_INCLUDE_DIRS ${LIBDVDREAD_INCLUDE_DIR} ${LIBDVDNAV_INCLUDE_DIR}) -set(LIBDVD_LIBRARIES ${LIBDVDNAV_LIBRARY} ${LIBDVDREAD_LIBRARY}) -if(TARGET LibDvdCSS::LibDvdCSS) - list(APPEND LIBDVD_LIBRARIES ${LIBDVDCSS_LIBRARY}) - list(APPEND LIBDVD_INCLUDE_DIRS ${LIBDVDCSS_INCLUDE_DIR}) -endif() -set(LIBDVD_LIBRARIES ${LIBDVD_LIBRARIES} CACHE STRING "libdvd libraries" FORCE) -set(LIBDVD_FOUND 1 CACHE BOOL "libdvd found" FORCE) - -mark_as_advanced(LIBDVD_INCLUDE_DIRS LIBDVD_LIBRARIES) +add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE IMPORTED) +set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_LINK_LIBRARIES "LibDvdNav::LibDvdNav") diff --git a/cmake/modules/FindLibDvdCSS.cmake b/cmake/modules/FindLibDvdCSS.cmake index 742efd55836e0..ea8d92c407435 100644 --- a/cmake/modules/FindLibDvdCSS.cmake +++ b/cmake/modules/FindLibDvdCSS.cmake @@ -94,23 +94,17 @@ find_package_handle_standard_args(LibDvdCSS VERSION_VAR LIBDVDCSS_VERSION) if(LIBDVDCSS_FOUND) - if(NOT TARGET LibDvdCSS::LibDvdCSS) - add_library(LibDvdCSS::LibDvdCSS UNKNOWN IMPORTED) - - set_target_properties(LibDvdCSS::LibDvdCSS PROPERTIES - IMPORTED_LOCATION "${LIBDVDCSS_LIBRARY}" - INTERFACE_COMPILE_DEFINITIONS "HAVE_DVDCSS_DVDCSS_H" - INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDCSS_INCLUDE_DIR}") - - if(TARGET libdvdcss) - add_dependencies(LibDvdCSS::LibDvdCSS libdvdcss) - endif() + add_library(LibDvdCSS::LibDvdCSS UNKNOWN IMPORTED) + set_target_properties(LibDvdCSS::LibDvdCSS PROPERTIES + IMPORTED_LOCATION "${LIBDVDCSS_LIBRARY}" + INTERFACE_COMPILE_DEFINITIONS "HAVE_DVDCSS_DVDCSS_H" + INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDCSS_INCLUDE_DIR}") + + if(TARGET libdvdcss) + add_dependencies(LibDvdCSS::LibDvdCSS libdvdcss) endif() - else() - if(LIBDVDCSS_FIND_REQUIRED) + if(LibDvdCSS_FIND_REQUIRED) message(FATAL_ERROR "Libdvdcss not found. Possibly remove ENABLE_DVDCSS.") endif() endif() - -mark_as_advanced(LIBDVDCSS_INCLUDE_DIR LIBDVDCSS_LIBRARY) diff --git a/cmake/modules/FindLibDvdNav.cmake b/cmake/modules/FindLibDvdNav.cmake index ffd8d9d4d3b8f..fe319be03e400 100644 --- a/cmake/modules/FindLibDvdNav.cmake +++ b/cmake/modules/FindLibDvdNav.cmake @@ -67,7 +67,12 @@ if(NOT TARGET LibDvdNav::LibDvdNav) ${LIBDVD_ADDITIONAL_ARGS}) else() - string(APPEND LIBDVDNAV_CFLAGS " -I$") + # INTERFACE_INCLUDE_DIRECTORIES may have multiple paths. We need to separate these + # individually to then set the -I argument correctly with each path + get_target_property(_interface_include_dirs LibDvdRead::LibDvdRead INTERFACE_INCLUDE_DIRECTORIES) + foreach(_interface_include_dir ${_interface_include_dirs}) + string(APPEND LIBDVDNAV_CFLAGS " -I${_interface_include_dir}") + endforeach() if(TARGET LibDvdCSS::LibDvdCSS) string(APPEND LIBDVDNAV_CFLAGS " -I$ $<$:-D$>") @@ -114,25 +119,19 @@ find_package_handle_standard_args(LibDvdNav VERSION_VAR LIBDVDNAV_VERSION) if(LIBDVDNAV_FOUND) - if(NOT TARGET LibDvdNav::LibDvdNav) - add_library(LibDvdNav::LibDvdNav UNKNOWN IMPORTED) + add_library(LibDvdNav::LibDvdNav UNKNOWN IMPORTED) + set_target_properties(LibDvdNav::LibDvdNav PROPERTIES + IMPORTED_LOCATION "${LIBDVDNAV_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDNAV_INCLUDE_DIR}") - set_target_properties(LibDvdNav::LibDvdNav PROPERTIES - IMPORTED_LOCATION "${LIBDVDNAV_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDNAV_INCLUDE_DIR}") - - if(TARGET libdvdnav) - add_dependencies(LibDvdNav::LibDvdNav libdvdnav) - endif() - if(TARGET LibDvdRead::LibDvdRead) - add_dependencies(LibDvdNav::LibDvdNav LibDvdRead::LibDvdRead) - endif() + if(TARGET libdvdnav) + add_dependencies(LibDvdNav::LibDvdNav libdvdnav) + endif() + if(TARGET LibDvdRead::LibDvdRead) + target_link_libraries(LibDvdNav::LibDvdNav INTERFACE LibDvdRead::LibDvdRead) endif() - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP LibDvdNav::LibDvdNav) else() - if(LIBDVDNAV_FIND_REQUIRED) + if(LibDvdNav_FIND_REQUIRED) message(FATAL_ERROR "Libdvdnav not found") endif() endif() - -mark_as_advanced(LIBDVDNAV_INCLUDE_DIR LIBDVDNAV_LIBRARY) diff --git a/cmake/modules/FindLibDvdRead.cmake b/cmake/modules/FindLibDvdRead.cmake index b67ff904eda9b..9f590343af5ab 100644 --- a/cmake/modules/FindLibDvdRead.cmake +++ b/cmake/modules/FindLibDvdRead.cmake @@ -116,27 +116,19 @@ find_package_handle_standard_args(LibDvdRead VERSION_VAR LIBDVDREAD_VERSION) if(LIBDVDREAD_FOUND) - if(NOT TARGET LibDvdRead::LibDvdRead) - add_library(LibDvdRead::LibDvdRead UNKNOWN IMPORTED) + add_library(LibDvdRead::LibDvdRead UNKNOWN IMPORTED) + set_target_properties(LibDvdRead::LibDvdRead PROPERTIES + IMPORTED_LOCATION "${LIBDVDREAD_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDREAD_INCLUDE_DIR}") - set_target_properties(LibDvdRead::LibDvdRead PROPERTIES - IMPORTED_LOCATION "${LIBDVDREAD_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDREAD_INCLUDE_DIR}") - - if(TARGET libdvdread) - add_dependencies(LibDvdRead::LibDvdRead libdvdread) - endif() - if(TARGET LibDvdCSS::LibDvdCSS) - add_dependencies(LibDvdRead::LibDvdRead LibDvdCSS::LibDvdCSS) - set_target_properties(LibDvdRead::LibDvdRead PROPERTIES - INTERFACE_LINK_LIBRARIES "dvdcss") - endif() + if(TARGET libdvdread) + add_dependencies(LibDvdRead::LibDvdRead libdvdread) + endif() + if(TARGET LibDvdCSS::LibDvdCSS) + target_link_libraries(LibDvdRead::LibDvdRead INTERFACE LibDvdCSS::LibDvdCSS) endif() - else() - if(LIBDVDREAD_FIND_REQUIRED) + if(LibDvdRead_FIND_REQUIRED) message(FATAL_ERROR "Libdvdread not found") endif() endif() - -mark_as_advanced(LIBDVDREAD_INCLUDE_DIR LIBDVDREAD_LIBRARY) From f68df145d1b8be7a0973a19a70cc1cea7379ee5f Mon Sep 17 00:00:00 2001 From: fuzzard Date: Fri, 17 May 2024 22:11:36 +1000 Subject: [PATCH 146/651] [cmake] Remove global property usage for dependency tracking --- CMakeLists.txt | 12 +----------- cmake/scripts/common/Macros.cmake | 5 +---- cmake/scripts/linux/ExtraTargets.cmake | 3 +-- cmake/scripts/webos/ExtraTargets.cmake | 3 +-- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 18c3bc2977e9d..99bce671abd58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,9 +134,6 @@ if(CORE_SYSTEM_NAME STREQUAL linux) option(ADDONS_CONFIGURE_AT_STARTUP "Configure binary addons at startup?" ON) endif() -# set scope of INTERNAL_DEPS -set_property(GLOBAL PROPERTY INTERNAL_DEPS_PROP) - # Build static libraries per directory if(NOT CMAKE_GENERATOR MATCHES "Visual Studio" AND NOT CMAKE_GENERATOR STREQUAL Xcode) set(ENABLE_STATIC_LIBS TRUE) @@ -389,9 +386,6 @@ elseif(CORE_SYSTEM_NAME STREQUAL android) ${CORE_BUILD_DIR}/DllPaths_generated_android.h @ONLY) endif() -get_property(INTERNAL_DEPS GLOBAL PROPERTY INTERNAL_DEPS_PROP) -set(GLOBAL_TARGET_DEPS ${INTERNAL_DEPS} ${PLATFORM_GLOBAL_TARGET_DEPS}) - # main library (used for main binary and tests) if(CORE_SYSTEM_NAME STREQUAL "darwin_embedded") # $ as at 3.26.4 provides incorrect paths for ios/tvos platforms @@ -408,7 +402,6 @@ if(CORE_SYSTEM_NAME STREQUAL "darwin_embedded") else() add_library(lib${APP_NAME_LC} STATIC $) endif() -add_dependencies(lib${APP_NAME_LC} ${GLOBAL_TARGET_DEPS}) set_target_properties(lib${APP_NAME_LC} PROPERTIES PREFIX "") # Other files (IDE) @@ -425,7 +418,7 @@ core_add_subdirs_from_filelist(${CMAKE_SOURCE_DIR}/cmake/treedata/common/*.txt core_add_optional_subdirs_from_filelist(${CMAKE_SOURCE_DIR}/cmake/treedata/optional/common/*.txt ${CMAKE_SOURCE_DIR}/cmake/treedata/optional/${CORE_SYSTEM_NAME}/*.txt) -target_link_libraries(lib${APP_NAME_LC} PUBLIC ${core_DEPENDS} ${SYSTEM_LDFLAGS} ${DEPLIBS} ${CMAKE_DL_LIBS} ${GLOBAL_TARGET_DEPS}) +target_link_libraries(lib${APP_NAME_LC} PUBLIC ${core_DEPENDS} ${SYSTEM_LDFLAGS} ${DEPLIBS} ${CMAKE_DL_LIBS}) core_target_link_libraries(lib${APP_NAME_LC}) set_target_properties(lib${APP_NAME_LC} PROPERTIES PROJECT_LABEL "xbmc") source_group_by_folder(lib${APP_NAME_LC} RELATIVE ${CMAKE_SOURCE_DIR}/xbmc) @@ -504,9 +497,6 @@ add_custom_target(gen_skin_pack DEPENDS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${ # Packaging target. This generates system addon, xbt creation, copy files to build tree add_custom_target(generate-packaging ALL DEPENDS TexturePacker::TexturePacker::Executable export-files gen_skin_pack gen_system_addons) -# Make sure we build any libs before we look to export-files. -# We may need to export some shared libs/data (eg Python) -add_dependencies(export-files ${GLOBAL_TARGET_DEPS}) # Add to lib${APP_NAME_LC} solely for Win UWP. msix building doesnt seem to pick up the # generated buildtree if we do it later. Other platforms dont care when this happens. diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index 77c148a9116d4..3b92ebe9134d1 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -72,12 +72,9 @@ function(core_add_library name) add_library(${name} STATIC ${SOURCES} ${HEADERS} ${OTHERS}) set_target_properties(${name} PROPERTIES PREFIX "") set(core_DEPENDS ${name} ${core_DEPENDS} CACHE STRING "" FORCE) - add_dependencies(${name} ${GLOBAL_TARGET_DEPS}) # Adds global target to library. This propagates dep lib info (eg include_dir locations) core_target_link_libraries(${name}) - # ToDo: remove the next line when the GLOBAL_TARGET_DEPS is removed completely - target_link_libraries(${name} PRIVATE ${GLOBAL_TARGET_DEPS}) set(CORE_LIBRARY ${name} PARENT_SCOPE) @@ -106,7 +103,7 @@ function(core_add_test_library name) set_target_properties(${name} PROPERTIES PREFIX "" EXCLUDE_FROM_ALL 1 FOLDER "Build Utilities/tests") - add_dependencies(${name} ${GLOBAL_TARGET_DEPS}) + set(test_archives ${test_archives} ${name} CACHE STRING "" FORCE) if(NOT MSVC) diff --git a/cmake/scripts/linux/ExtraTargets.cmake b/cmake/scripts/linux/ExtraTargets.cmake index 37156923b9c8d..36757cef4125b 100644 --- a/cmake/scripts/linux/ExtraTargets.cmake +++ b/cmake/scripts/linux/ExtraTargets.cmake @@ -35,7 +35,6 @@ if("wayland" IN_LIST CORE_PLATFORM_NAME_LC) # Dummy target for dependencies add_custom_target(generate-wayland-extra-protocols DEPENDS wayland-extra-protocols.hpp) - # ToDo: turn this into a TARGET OBJECT. For now, a custum target doesnt play nice with - # our PLATFORM_GLOBAL_TARGET_DEPS usage in macros + add_dependencies(lib${APP_NAME_LC} generate-wayland-extra-protocols) endif() diff --git a/cmake/scripts/webos/ExtraTargets.cmake b/cmake/scripts/webos/ExtraTargets.cmake index 46c78edf624dc..92ef6b62fa89d 100644 --- a/cmake/scripts/webos/ExtraTargets.cmake +++ b/cmake/scripts/webos/ExtraTargets.cmake @@ -8,6 +8,5 @@ add_custom_command(OUTPUT "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-webos DEPENDS wayland::waylandppscanner ${WEBOS_PROTOCOL_XMLS} COMMENT "Generating wayland-webos C++ wrappers") add_custom_target(generate-wayland-webos-protocols DEPENDS wayland-webos-protocols.hpp) -# ToDo: turn this into a TARGET OBJECT. For now, a custum target doesnt play nice with -# our PLATFORM_GLOBAL_TARGET_DEPS usage in macros + add_dependencies(lib${APP_NAME_LC} generate-wayland-webos-protocols) From bad07bc815eb04a74f21cc769f5eb84ce8d03256 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Fri, 17 May 2024 22:37:49 +1000 Subject: [PATCH 147/651] [cmake] Document some variables and their expected usage --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 99bce671abd58..9a4e4da831261 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -418,6 +418,9 @@ core_add_subdirs_from_filelist(${CMAKE_SOURCE_DIR}/cmake/treedata/common/*.txt core_add_optional_subdirs_from_filelist(${CMAKE_SOURCE_DIR}/cmake/treedata/optional/common/*.txt ${CMAKE_SOURCE_DIR}/cmake/treedata/optional/${CORE_SYSTEM_NAME}/*.txt) +# core_DEPENDS: these are the artifact/object files of /xbmc/* +# SYSTEM_LDFLAGS: system linker flags +# DEPLIBS: system libraries for linking target_link_libraries(lib${APP_NAME_LC} PUBLIC ${core_DEPENDS} ${SYSTEM_LDFLAGS} ${DEPLIBS} ${CMAKE_DL_LIBS}) core_target_link_libraries(lib${APP_NAME_LC}) set_target_properties(lib${APP_NAME_LC} PROPERTIES PROJECT_LABEL "xbmc") From c99a18df8da22ba7aff329c32f31dd6b37d11508 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 18 May 2024 18:59:28 +1000 Subject: [PATCH 148/651] [cmake][modules] Introduce FindOpenSSL wrapper module With full target usage, and removal of globals like *_LIBRARIES, *_INCLUDE_DIRS we introduce a wrapper find module that still uses cmakes system find_package(OpenSSL) and make an alias using "our" standard --- cmake/modules/FindOpenSSL.cmake | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 cmake/modules/FindOpenSSL.cmake diff --git a/cmake/modules/FindOpenSSL.cmake b/cmake/modules/FindOpenSSL.cmake new file mode 100644 index 0000000000000..08902878eb9e7 --- /dev/null +++ b/cmake/modules/FindOpenSSL.cmake @@ -0,0 +1,44 @@ +# FindOpenSSL +# ----------- +# Finds the Openssl libraries +# +# This will define the following target: +# +# ${APP_NAME_LC}::OpenSSL - Alias of OpenSSL::SSL target +# OpenSSL::SSL - standard Openssl SSL target from system find package +# OpenSSL::Crypto - standard Openssl Crypto target from system find package + +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + + # We do this dance to utilise cmake system FindOpenssl. Saves us dealing with it + set(_temp_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) + unset(CMAKE_MODULE_PATH) + + if(OpenSSL_FIND_REQUIRED) + set(REQ "REQUIRED") + endif() + + # Only aim for static libs on windows or depends builds + if(KODI_DEPENDSBUILD OR (WIN32 OR WINDOWS_STORE)) + set(OPENSSL_USE_STATIC_LIBS ON) + set(OPENSSL_ROOT_DIR ${DEPENDS_PATH}) + endif() + + find_package(OpenSSL ${REQ}) + unset(OPENSSL_USE_STATIC_LIBS) + + # Back to our normal module paths + set(CMAKE_MODULE_PATH ${_temp_CMAKE_MODULE_PATH}) + + if(OPENSSL_FOUND) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS OpenSSL::SSL) + + # Add Crypto as a link library to easily propagate both targets to our custom target + set_target_properties(OpenSSL::SSL PROPERTIES + INTERFACE_LINK_LIBRARIES "OpenSSL::Crypto") + else() + if(OpenSSL_FIND_REQUIRED) + message(FATAL_ERROR "OpenSSL libraries were not found.") + endif() + endif() +endif() From 60b9f336fa4fb6d089f6901a75c3c4981ca9fcb0 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 18 May 2024 19:02:18 +1000 Subject: [PATCH 149/651] [cmake][modules] Introduce FindZLIB wrapper module With full target usage, and removal of globals like *_LIBRARIES, *_INCLUDE_DIRS we introduce a wrapper find module that still uses cmakes system find_package(ZLIB) and make an alias using "our" standard for macro usage --- cmake/modules/FindZlib.cmake | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 cmake/modules/FindZlib.cmake diff --git a/cmake/modules/FindZlib.cmake b/cmake/modules/FindZlib.cmake new file mode 100644 index 0000000000000..c06152ae6c26b --- /dev/null +++ b/cmake/modules/FindZlib.cmake @@ -0,0 +1,46 @@ +# FindZLIB +# ----------- +# Finds the zlib library +# +# This will define the following target: +# +# ${APP_NAME_LC}::ZLIB - Alias to ZLIB::ZLIB target +# ZLIB::ZLIB - standard Zlib target from system find package +# + +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + + # We do this dance to utilise cmake system FindZLIB. Saves us dealing with it + set(_temp_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) + unset(CMAKE_MODULE_PATH) + + if(ZLIB_FIND_REQUIRED) + set(REQ "REQUIRED") + endif() + + if(KODI_DEPENDSBUILD OR (WIN32 OR WINDOWS_STORE)) + set(ZLIB_ROOT ${DEPENDS_PATH}) + endif() + + # Darwin platforms link against toolchain provided zlib regardless + # They will fail when searching for static. All other platforms, prefer static + # if possible (requires cmake 3.24+ otherwise variable is a no-op) + # Windows still uses dynamic lib for zlib for other purposes, dont mix + if(NOT CMAKE_SYSTEM_NAME MATCHES "Darwin" AND NOT (WIN32 OR WINDOWS_STORE)) + set(ZLIB_USE_STATIC_LIBS ON) + endif() + + find_package(ZLIB ${REQ}) + unset(ZLIB_USE_STATIC_LIBS) + + # Back to our normal module paths + set(CMAKE_MODULE_PATH ${_temp_CMAKE_MODULE_PATH}) + + if(ZLIB_FOUND) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS ZLIB::ZLIB) + else() + if(ZLIB_FIND_REQUIRED) + message(FATAL_ERROR "Zlib libraries were not found.") + endif() + endif() +endif() From f2a75cd78b697269b02661f1de637e6fac43fabc Mon Sep 17 00:00:00 2001 From: fuzzard Date: Fri, 17 May 2024 22:38:29 +1000 Subject: [PATCH 150/651] [cmake] remove several non target globals --- CMakeLists.txt | 2 +- cmake/scripts/common/ArchSetup.cmake | 1 - cmake/scripts/common/Macros.cmake | 23 +---------------------- 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a4e4da831261..8d8e5e73a4532 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -357,7 +357,7 @@ endif() # Generate messages core_add_subdirs_from_filelist(${CMAKE_SOURCE_DIR}/cmake/messages/flatbuffers/*.txt) -include_directories(${INCLUDES} SYSTEM ${SYSTEM_INCLUDES}) +include_directories(${INCLUDES}) add_compile_options(${ARCH_DEFINES} "${SYSTEM_DEFINES}" ${DEP_DEFINES} ${PATH_DEFINES}) set(core_DEPENDS "" CACHE STRING "" FORCE) diff --git a/cmake/scripts/common/ArchSetup.cmake b/cmake/scripts/common/ArchSetup.cmake index a83d2ae198075..0461496bf5a98 100644 --- a/cmake/scripts/common/ArchSetup.cmake +++ b/cmake/scripts/common/ArchSetup.cmake @@ -8,7 +8,6 @@ # ARCH - the system architecture # ARCH_DEFINES - list of compiler definitions for this architecture # SYSTEM_DEFINES - list of compiler definitions for this system -# DEP_DEFINES - compiler definitions for system dependencies (e.g. LIRC) # + the results of compiler tests etc. # workaround a bug in older cmake, where binutils wouldn't be set after deleting CMakeCache.txt diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index 3b92ebe9134d1..57f5ef8712019 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -342,15 +342,6 @@ function(copy_files_from_filelist_to_buildtree pattern) set(install_data ${install_data} PARENT_SCOPE) endfunction() -# helper macro to set modified variables in parent scope -macro(export_dep) - set(SYSTEM_INCLUDES ${SYSTEM_INCLUDES} PARENT_SCOPE) - set(DEPLIBS ${DEPLIBS} PARENT_SCOPE) - set(DEP_DEFINES ${DEP_DEFINES} PARENT_SCOPE) - set(${depup}_FOUND ${${depup}_FOUND} PARENT_SCOPE) - mark_as_advanced(${depup}_LIBRARIES) -endmacro() - # split dependency specification to name and version # Arguments: # depspec dependency specification that can optionally include a required @@ -393,17 +384,11 @@ endmacro() # Arguments: # dep_list One or many dependency specifications (see split_dependency_specification) # for syntax). The dependency name is used uppercased as variable prefix. -# On return: -# dependencies added to ${SYSTEM_INCLUDES}, ${DEPLIBS} and ${DEP_DEFINES} function(core_require_dep) foreach(depspec ${ARGN}) split_dependency_specification(${depspec} dep version) find_package_with_ver(${dep} ${version} REQUIRED) string(TOUPPER ${dep} depup) - list(APPEND SYSTEM_INCLUDES ${${depup}_INCLUDE_DIRS}) - list(APPEND DEPLIBS ${${depup}_LIBRARIES}) - list(APPEND DEP_DEFINES ${${depup}_DEFINITIONS}) - export_dep() # We dont want to add a build tool if (NOT ${depspec} IN_LIST optional_buildtools AND NOT ${depspec} IN_LIST required_buildtools) @@ -432,8 +417,6 @@ endmacro() # Arguments: # dep_list One or many dependency specifications (see split_dependency_specification) # for syntax). The dependency name is used uppercased as variable prefix. -# On return: -# dependency optionally added to ${SYSTEM_INCLUDES}, ${DEPLIBS} and ${DEP_DEFINES} function(core_optional_dep) foreach(depspec ${ARGN}) set(_required False) @@ -446,12 +429,8 @@ function(core_optional_dep) set(_required True) endif() - if(${depup}_FOUND OR TARGET kodi::${dep}) - list(APPEND SYSTEM_INCLUDES ${${depup}_INCLUDE_DIRS}) - list(APPEND DEPLIBS ${${depup}_LIBRARIES}) - list(APPEND DEP_DEFINES ${${depup}_DEFINITIONS}) + if(TARGET kodi::${dep}) set(final_message ${final_message} "${depup} enabled: Yes") - export_dep() # We dont want to add a build tool if (NOT ${depspec} IN_LIST optional_buildtools AND NOT ${depspec} IN_LIST required_buildtools) From f1589990e746eed557dde7f3904bccb5b47dab22 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 18 May 2024 08:51:56 +1000 Subject: [PATCH 151/651] [cmake] relocate rfc2822stamp into CPackConfigDEB This function is only used in CPackConfigDEB, so move out of generic Macros.cmake --- cmake/cpack/CPackConfigDEB.cmake | 12 ++++++++++++ cmake/scripts/common/Macros.cmake | 10 ---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cmake/cpack/CPackConfigDEB.cmake b/cmake/cpack/CPackConfigDEB.cmake index 9aa687d5dcfe2..ef5504de148b0 100644 --- a/cmake/cpack/CPackConfigDEB.cmake +++ b/cmake/cpack/CPackConfigDEB.cmake @@ -1,6 +1,18 @@ # include Macros.cmake to automate generation of time/date stamps, maintainer, etc. include(${CMAKE_SOURCE_DIR}/cmake/scripts/common/Macros.cmake) +# Function only used in CPackConfigDEB + +# Generates an RFC2822 timestamp +# +# The following variable is set: +# RFC2822_TIMESTAMP +function(rfc2822stamp) + execute_process(COMMAND date -R + OUTPUT_VARIABLE RESULT) + set(RFC2822_TIMESTAMP ${RESULT} PARENT_SCOPE) +endfunction() + # find stuff we need find_program(LSB_RELEASE_CMD lsb_release) find_program(DPKG_CMD dpkg) diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index 57f5ef8712019..319f63244e87c 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -542,16 +542,6 @@ macro(core_add_optional_subdirs_from_filelist pattern) endforeach() endmacro() -# Generates an RFC2822 timestamp -# -# The following variable is set: -# RFC2822_TIMESTAMP -function(rfc2822stamp) - execute_process(COMMAND date -R - OUTPUT_VARIABLE RESULT) - set(RFC2822_TIMESTAMP ${RESULT} PARENT_SCOPE) -endfunction() - # Generates an user stamp from git config info # # The following variable is set: From de68fae0fcc55e9e26d37cec1a20d1440dee65c0 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 18 May 2024 08:54:49 +1000 Subject: [PATCH 152/651] [cmake] relocate userstamp function into CPackConfigDEB userstamp is only used in CPackConfigDEB. remove out of Macros.cmake --- cmake/cpack/CPackConfigDEB.cmake | 22 ++++++++++++++++++++++ cmake/scripts/common/Macros.cmake | 22 ---------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/cmake/cpack/CPackConfigDEB.cmake b/cmake/cpack/CPackConfigDEB.cmake index ef5504de148b0..d3eaf6c7d71e5 100644 --- a/cmake/cpack/CPackConfigDEB.cmake +++ b/cmake/cpack/CPackConfigDEB.cmake @@ -13,6 +13,28 @@ function(rfc2822stamp) set(RFC2822_TIMESTAMP ${RESULT} PARENT_SCOPE) endfunction() +# Generates an user stamp from git config info +# +# The following variable is set: +# PACKAGE_MAINTAINER - user stamp in the form of "username " +# if no git tree is found, value is set to "nobody " +function(userstamp) + find_package(Git) + if(GIT_FOUND AND EXISTS ${CMAKE_SOURCE_DIR}/.git) + execute_process(COMMAND ${GIT_EXECUTABLE} config user.name + OUTPUT_VARIABLE username + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT_EXECUTABLE} config user.email + OUTPUT_VARIABLE useremail + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(PACKAGE_MAINTAINER "${username} <${useremail}>" PARENT_SCOPE) + else() + set(PACKAGE_MAINTAINER "nobody " PARENT_SCOPE) + endif() +endfunction() + # find stuff we need find_program(LSB_RELEASE_CMD lsb_release) find_program(DPKG_CMD dpkg) diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index 319f63244e87c..fcb9659de9ca5 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -542,28 +542,6 @@ macro(core_add_optional_subdirs_from_filelist pattern) endforeach() endmacro() -# Generates an user stamp from git config info -# -# The following variable is set: -# PACKAGE_MAINTAINER - user stamp in the form of "username " -# if no git tree is found, value is set to "nobody " -function(userstamp) - find_package(Git) - if(GIT_FOUND AND EXISTS ${CMAKE_SOURCE_DIR}/.git) - execute_process(COMMAND ${GIT_EXECUTABLE} config user.name - OUTPUT_VARIABLE username - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT_EXECUTABLE} config user.email - OUTPUT_VARIABLE useremail - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_STRIP_TRAILING_WHITESPACE) - set(PACKAGE_MAINTAINER "${username} <${useremail}>" PARENT_SCOPE) - else() - set(PACKAGE_MAINTAINER "nobody " PARENT_SCOPE) - endif() -endfunction() - # Parses git info and sets variables used to identify the build # Arguments: # stamp variable name to return From a3046c6dd0385e92f083009b2fd3116bdcfeebea Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 18 May 2024 09:39:20 +1000 Subject: [PATCH 153/651] [cmake] remove unused variable LIBCEC_SONAME --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d8e5e73a4532..8e54cd49a9828 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -377,7 +377,6 @@ if(NOT ${CORE_SYSTEM_NAME} MATCHES "windows") file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/system/players/VideoPlayer) endif() -set(LIBCEC_SONAME "${CEC_SONAME}") if(NOT CORE_SYSTEM_NAME STREQUAL windows AND NOT CORE_SYSTEM_NAME STREQUAL android AND NOT CORE_SYSTEM_NAME STREQUAL windowsstore) configure_file(${CMAKE_SOURCE_DIR}/xbmc/DllPaths_generated.h.in ${CORE_BUILD_DIR}/DllPaths_generated.h @ONLY) From c908d1c849c5a1f6eb6a6b85df4d02394dfdce60 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Thu, 16 Nov 2023 16:49:30 +0000 Subject: [PATCH 154/651] [depends] Add exiv2 to depends Co-authored-by: fuzzard --- CMakeLists.txt | 1 + cmake/modules/FindExiv2.cmake | 150 +++++ .../scripts/0_package.target-win10-arm.list | 1 + .../scripts/0_package.target-win10-win32.list | 1 + .../scripts/0_package.target-win10-x64.list | 1 + .../scripts/0_package.target-win32.list | 1 + .../scripts/0_package.target-x64.list | 1 + tools/depends/target/Makefile | 1 + ...XIV2_ENABLE_FILESYSTEM_ACCESS-option.patch | 564 ++++++++++++++++++ ...HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch | 169 ++++++ .../0003-UWP-Disable-getLoadedLibraries.patch | 45 ++ tools/depends/target/exiv2/EXIV2-VERSION | 6 + tools/depends/target/exiv2/Makefile | 47 ++ 13 files changed, 988 insertions(+) create mode 100644 cmake/modules/FindExiv2.cmake create mode 100644 tools/depends/target/exiv2/0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch create mode 100644 tools/depends/target/exiv2/0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch create mode 100644 tools/depends/target/exiv2/0003-UWP-Disable-getLoadedLibraries.patch create mode 100644 tools/depends/target/exiv2/EXIV2-VERSION create mode 100644 tools/depends/target/exiv2/Makefile diff --git a/CMakeLists.txt b/CMakeLists.txt index d0ce337b0a4d5..2883d76f83405 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,7 @@ option(ENABLE_INTERNAL_FFMPEG "Enable internal ffmpeg?" OFF) # These are built for all platforms not using system libs or disabled by user dependent_option(ENABLE_INTERNAL_CEC "Enable internal libcec?") dependent_option(ENABLE_INTERNAL_CURL "Enable internal libcurl?") +dependent_option(ENABLE_INTERNAL_EXIV2 "Enable internal exiv2?") dependent_option(ENABLE_INTERNAL_FLATBUFFERS "Enable internal flatbuffers?") dependent_option(ENABLE_INTERNAL_FMT "Enable internal fmt?") dependent_option(ENABLE_INTERNAL_NFS "Enable internal libnfs?") diff --git a/cmake/modules/FindExiv2.cmake b/cmake/modules/FindExiv2.cmake new file mode 100644 index 0000000000000..5e6c9e0fca1be --- /dev/null +++ b/cmake/modules/FindExiv2.cmake @@ -0,0 +1,150 @@ +#.rst: +# FindExiv2 +# ------- +# Finds the exiv2 library +# +# This will define the following imported targets:: +# +# ${APP_NAME_LC}::Exiv2 - The EXIV2 library + +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + + macro(buildexiv2) + + find_package(Patch MODULE REQUIRED) + find_package(Iconv REQUIRED) + + # Note: Please drop once a release based on master is made. First 2 are already upstream. + set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch" + "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch" + "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0003-UWP-Disable-getLoadedLibraries.patch") + + generate_patchcommand("${patches}") + + set(CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF + -DEXIV2_ENABLE_WEBREADY=OFF + -DEXIV2_ENABLE_XMP=OFF + -DEXIV2_ENABLE_CURL=OFF + -DEXIV2_ENABLE_NLS=OFF + -DEXIV2_BUILD_SAMPLES=OFF + -DEXIV2_BUILD_UNIT_TESTS=OFF + -DEXIV2_ENABLE_VIDEO=OFF + -DEXIV2_ENABLE_BMFF=OFF + -DEXIV2_ENABLE_BROTLI=OFF + -DEXIV2_ENABLE_INIH=OFF + -DEXIV2_ENABLE_FILESYSTEM_ACCESS=OFF + -DEXIV2_BUILD_EXIV2_COMMAND=OFF) + + if(NOT CMAKE_CXX_COMPILER_LAUNCHER STREQUAL "") + list(APPEND CMAKE_ARGS -DBUILD_WITH_CCACHE=ON) + endif() + + BUILD_DEP_TARGET() + endmacro() + + include(cmake/scripts/common/ModuleHelpers.cmake) + + set(MODULE_LC exiv2) + + SETUP_BUILD_VARS() + + find_package(exiv2 CONFIG QUIET + HINTS ${DEPENDS_PATH}/lib/cmake + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) + + # Check for existing EXIV2. If version >= EXIV2-VERSION file version, dont build + if((exiv2_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_EXIV2) OR + ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_EXIV2)) + + buildexiv2() + else() + if(NOT TARGET Exiv2::exiv2lib) + find_package(PkgConfig) + # Fallback to pkg-config and individual lib/include file search + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_EXIV2 exiv2 QUIET) + set(EXIV2_VER ${PC_EXIV2_VERSION}) + endif() + + find_path(EXIV2_INCLUDE_DIR NAMES exiv2/exiv2.hpp + HINTS ${PC_EXIV2_INCLUDEDIR} NO_CACHE) + find_library(EXIV2_LIBRARY NAMES exiv2 + HINTS ${PC_EXIV2_LIBDIR} NO_CACHE) + endif() + endif() + + # We create variables based off TARGET data for use with FPHSA + if(TARGET Exiv2::exiv2lib AND NOT TARGET exiv2) + # This is for the case where a distro provides a non standard (Debug/Release) config type + # eg Debian's config file is exiv2Config-none.cmake + # convert this back to either DEBUG/RELEASE or just RELEASE + # we only do this because we use find_package_handle_standard_args for config time output + # and it isnt capable of handling TARGETS, so we have to extract the info + get_target_property(_FMT_CONFIGURATIONS Exiv2::exiv2lib IMPORTED_CONFIGURATIONS) + foreach(_exiv2_config IN LISTS _FMT_CONFIGURATIONS) + # Some non standard config (eg None on Debian) + # Just set to RELEASE var so select_library_configurations can continue to work its magic + string(TOUPPER ${_exiv2_config} _exiv2_config_UPPER) + if((NOT ${_exiv2_config_UPPER} STREQUAL "RELEASE") AND + (NOT ${_exiv2_config_UPPER} STREQUAL "DEBUG")) + get_target_property(EXIV2_LIBRARY_RELEASE Exiv2::exiv2lib IMPORTED_LOCATION_${_exiv2_config_UPPER}) + else() + get_target_property(EXIV2_LIBRARY_${_exiv2_config_UPPER} Exiv2::exiv2lib IMPORTED_LOCATION_${_exiv2_config_UPPER}) + endif() + endforeach() + + get_target_property(EXIV2_INCLUDE_DIR Exiv2::exiv2lib INTERFACE_INCLUDE_DIRECTORIES) + endif() + + include(SelectLibraryConfigurations) + select_library_configurations(EXIV2) + unset(EXIV2_LIBRARIES) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Exiv2 + REQUIRED_VARS EXIV2_LIBRARY EXIV2_INCLUDE_DIR + VERSION_VAR EXIV2_VER) + + if(EXIV2_FOUND) + if(TARGET Exiv2::exiv2lib AND NOT TARGET exiv2) + # Exiv2 config found. Use it + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS Exiv2::exiv2lib) + else() + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${EXIV2_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${EXIV2_INCLUDE_DIR}") + if(CORE_SYSTEM_NAME STREQUAL "freebsd") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES procstat) + elseif(CORE_SYSTEM_NAME MATCHES "windows") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES psapi) + endif() + endif() + + if(TARGET exiv2) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} exiv2) + endif() + + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + if(NOT TARGET exiv2) + buildexiv2() + set_target_properties(exiv2 PROPERTIES EXCLUDE_FROM_ALL TRUE) + endif() + add_dependencies(build_internal_depends exiv2) + endif() + else() + if(Exiv2_FIND_REQUIRED) + message(FATAL_ERROR "Could NOT find or build Exiv2 library. You may want to try -DENABLE_INTERNAL_EXIV2=ON") + endif() + endif() +endif() diff --git a/project/BuildDependencies/scripts/0_package.target-win10-arm.list b/project/BuildDependencies/scripts/0_package.target-win10-arm.list index 04b5887b2af0c..264f68847db38 100644 --- a/project/BuildDependencies/scripts/0_package.target-win10-arm.list +++ b/project/BuildDependencies/scripts/0_package.target-win10-arm.list @@ -8,6 +8,7 @@ ;PLEASE KEEP THIS LIST IN ALPHABETICAL ORDER! brotli-1.0.7-win10-arm-v141-20200105.7z dav1d-1.4.1-win10-arm-v142-20240331.7z +expat-2.2.9-win10-arm-v141-20200105.7z freetype-2.10.1-win10-arm-v141-20200105.7z fstrcmp-0.7-win10-arm-v141-20200105.7z GoogleTest-1.10.0-win10-arm-v141-20200410.7z diff --git a/project/BuildDependencies/scripts/0_package.target-win10-win32.list b/project/BuildDependencies/scripts/0_package.target-win10-win32.list index bf72fde0c8cc9..b2ad19e4ade26 100644 --- a/project/BuildDependencies/scripts/0_package.target-win10-win32.list +++ b/project/BuildDependencies/scripts/0_package.target-win10-win32.list @@ -8,6 +8,7 @@ ;PLEASE KEEP THIS LIST IN ALPHABETICAL ORDER! brotli-1.0.7-win10-win32-v141-20200105.7z dav1d-1.4.1-win10-win32-v142-20240331.7z +expat-2.2.9-win10-win32-v141-20200105.7z freetype-2.10.1-win10-win32-v141-20200105.7z fstrcmp-0.7-win10-win32-v141-20200105.7z GoogleTest-1.10.0-win10-win32-v141-20200410.7z diff --git a/project/BuildDependencies/scripts/0_package.target-win10-x64.list b/project/BuildDependencies/scripts/0_package.target-win10-x64.list index 51312322d8267..6a44f9f4d8e50 100644 --- a/project/BuildDependencies/scripts/0_package.target-win10-x64.list +++ b/project/BuildDependencies/scripts/0_package.target-win10-x64.list @@ -8,6 +8,7 @@ ;PLEASE KEEP THIS LIST IN ALPHABETICAL ORDER! brotli-1.0.7-win10-x64-v141-20200105.7z dav1d-1.4.1-win10-x64-v142-20240331.7z +expat-2.2.9-win10-x64-v141-20200105.7z freetype-2.10.1-win10-x64-v141-20200105.7z fstrcmp-0.7-win10-x64-v141-20200105.7z GoogleTest-1.10.0-win10-x64-v141-20200410.7z diff --git a/project/BuildDependencies/scripts/0_package.target-win32.list b/project/BuildDependencies/scripts/0_package.target-win32.list index 9ac7f69e622f9..f87dbf5d67e78 100644 --- a/project/BuildDependencies/scripts/0_package.target-win32.list +++ b/project/BuildDependencies/scripts/0_package.target-win32.list @@ -10,6 +10,7 @@ brotli-1.0.7-win32-v141-20200105.7z dav1d-1.4.1-win32-v142-20240331.7z detours-64ec13-win32-v141-20200105.7z dnssd-878.260.1-win32-v141-20200105.7z +expat-2.2.9-win32-v141-20200105.7z freetype-2.10.1-win32-v141-20200105.7z fstrcmp-0.7-win32-v141-20200105.7z giflib-5.2.1-win32-v141-20200105.7z diff --git a/project/BuildDependencies/scripts/0_package.target-x64.list b/project/BuildDependencies/scripts/0_package.target-x64.list index e396e54eb6493..b2705edd96d12 100644 --- a/project/BuildDependencies/scripts/0_package.target-x64.list +++ b/project/BuildDependencies/scripts/0_package.target-x64.list @@ -10,6 +10,7 @@ brotli-1.0.7-x64-v141-20200105.7z dav1d-1.4.1-x64-v142-20240331.7z detours-64ec13-x64-v141-20200105.7z dnssd-878.260.1-x64-v141-20200105.7z +expat-2.2.9-x64-v141-20200105.7z freetype-2.10.1-x64-v141-20200105.7z fstrcmp-0.7-x64-v141-20200105.7z GoogleTest-1.10.0-x64-v141-20200410.7z diff --git a/tools/depends/target/Makefile b/tools/depends/target/Makefile index e47e8bff263c4..a815853007e7d 100644 --- a/tools/depends/target/Makefile +++ b/tools/depends/target/Makefile @@ -152,6 +152,7 @@ download: $(DOWNLOAD_TARGETS) crossguid: $(LIBUUID) curl: brotli openssl nghttp2 $(ZLIB) dbus: expat +exiv2: $(ICONV) $(ZLIB) expat ffmpeg: $(ICONV) $(ZLIB) bzip2 gnutls dav1d $(LIBVA) fontconfig: freetype2 expat $(ICONV) $(LIBUUID) freetype2: bzip2 harfbuzz $(ZLIB) diff --git a/tools/depends/target/exiv2/0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch b/tools/depends/target/exiv2/0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch new file mode 100644 index 0000000000000..85983690f1c06 --- /dev/null +++ b/tools/depends/target/exiv2/0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch @@ -0,0 +1,564 @@ +From 64acd1a9b4a83ae9fb093660245bb1206cf3817b Mon Sep 17 00:00:00 2001 +From: Miguel Borges de Freitas <92enen@gmail.com> +Date: Fri, 17 Nov 2023 17:08:29 +0000 +Subject: [PATCH 1/2] Add EXIV2_ENABLE_FILESYSTEM_ACCESS option + +--- + CMakeLists.txt | 3 ++- + README.md | 1 + + cmake/config.h.cmake | 3 +++ + cmake/findDependencies.cmake | 4 +++- + cmake/generateConfigFile.cmake | 9 +++++---- + cmake/printSummary.cmake | 1 + + include/exiv2/basicio.hpp | 4 +++- + include/exiv2/exif.hpp | 6 ++++++ + include/exiv2/preview.hpp | 2 ++ + meson.build | 1 + + src/basicio.cpp | 32 +++++++++++++++++--------------- + src/exif.cpp | 6 ++++++ + src/futils.cpp | 10 ++++++++++ + src/image.cpp | 14 +++++++++++++- + src/makernote_int.cpp | 8 +++++++- + src/preview.cpp | 2 ++ + src/tiffcomposite_int.cpp | 2 +- + 17 files changed, 83 insertions(+), 25 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index e9f14f722..36266d41a 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -36,7 +36,7 @@ option( EXIV2_ENABLE_BMFF "Build with BMFF support" + option( EXIV2_ENABLE_BROTLI "Use Brotli for JPEG XL compressed boxes (BMFF)" ON ) + option( EXIV2_ENABLE_VIDEO "Build with video support" ON ) + option( EXIV2_ENABLE_INIH "Use inih library" ON ) +- ++option( EXIV2_ENABLE_FILESYSTEM_ACCESS "Build with filesystem access" ON ) + option( EXIV2_BUILD_SAMPLES "Build sample applications" OFF ) + option( EXIV2_BUILD_EXIV2_COMMAND "Build exiv2 command-line executable" ON ) + option( EXIV2_BUILD_UNIT_TESTS "Build unit tests" OFF ) +@@ -106,6 +106,7 @@ if(EXIV2_BUILD_EXIV2_COMMAND) + + if( EXIV2_BUILD_SAMPLES ) + add_subdirectory( samples ) ++ set(EXIV2_ENABLE_FILESYSTEM_ACCESS ON) + get_directory_property(SAMPLES DIRECTORY samples DEFINITION APPLICATIONS) + + if (Python3_Interpreter_FOUND) +diff --git a/README.md b/README.md +index 63c9a1c4f..331065363 100644 +--- a/README.md ++++ b/README.md +@@ -254,6 +254,7 @@ option( EXIV2_ENABLE_PNG "Build with png support (requires libz)" + ... + option( EXIV2_ENABLE_BMFF "Build with BMFF support (brotli recommended)" ON ) + option( EXIV2_ENABLE_BROTLI "Use Brotli for JPEG XL compressed boxes (BMFF)" ON ) ++option( EXIV2_ENABLE_FILESYSTEM_ACCESS "Build with filesystem access" ON ) + 577 rmills@rmillsmm:~/gnu/github/exiv2/exiv2 $ + ``` + +diff --git a/cmake/config.h.cmake b/cmake/config.h.cmake +index a817d2d1b..f24cd4a63 100644 +--- a/cmake/config.h.cmake ++++ b/cmake/config.h.cmake +@@ -6,6 +6,9 @@ + // Define to 1 if you want to use libcurl in httpIO. + #cmakedefine EXV_USE_CURL + ++// Define to 1 if you want to enable filesystem access ++#cmakedefine EXV_ENABLE_FILESYSTEM ++ + // Define if you require webready support. + #cmakedefine EXV_ENABLE_WEBREADY + +diff --git a/cmake/findDependencies.cmake b/cmake/findDependencies.cmake +index 5ee481d1c..5159cb0b7 100644 +--- a/cmake/findDependencies.cmake ++++ b/cmake/findDependencies.cmake +@@ -35,7 +35,9 @@ if (NOT Python3_Interpreter_FOUND) + message(WARNING "Python3 was not found. Python tests under the 'tests' folder will not be executed") + endif() + +-find_package(Filesystem COMPONENTS Experimental Final REQUIRED) ++if(EXIV2_ENABLE_FILESYSTEM_ACCESS) ++ find_package(Filesystem COMPONENTS Experimental Final REQUIRED) ++endif() + + # don't use Frameworks on the Mac (#966) + if (APPLE) +diff --git a/cmake/generateConfigFile.cmake b/cmake/generateConfigFile.cmake +index 49c9a10f6..20e331c04 100644 +--- a/cmake/generateConfigFile.cmake ++++ b/cmake/generateConfigFile.cmake +@@ -6,10 +6,11 @@ include(CheckCXXSymbolExists) + if (${EXIV2_ENABLE_WEBREADY}) + set(EXV_USE_CURL ${EXIV2_ENABLE_CURL}) + endif() +-set(EXV_ENABLE_BMFF ${EXIV2_ENABLE_BMFF}) +-set(EXV_ENABLE_WEBREADY ${EXIV2_ENABLE_WEBREADY}) +-set(EXV_HAVE_LENSDATA ${EXIV2_ENABLE_LENSDATA}) +-set(EXV_ENABLE_INIH ${EXIV2_ENABLE_INIH}) ++set(EXV_ENABLE_BMFF ${EXIV2_ENABLE_BMFF}) ++set(EXV_ENABLE_WEBREADY ${EXIV2_ENABLE_WEBREADY}) ++set(EXV_HAVE_LENSDATA ${EXIV2_ENABLE_LENSDATA}) ++set(EXV_ENABLE_INIH ${EXIV2_ENABLE_INIH}) ++set(EXV_ENABLE_FILESYSTEM ${EXIV2_ENABLE_FILESYSTEM_ACCESS}) + + set(EXV_PACKAGE_NAME ${PROJECT_NAME}) + set(EXV_PACKAGE_VERSION ${PROJECT_VERSION}) +diff --git a/cmake/printSummary.cmake b/cmake/printSummary.cmake +index 854323ba0..06b2745a2 100644 +--- a/cmake/printSummary.cmake ++++ b/cmake/printSummary.cmake +@@ -69,4 +69,5 @@ OptionOutput( "Building unit tests: " EXIV2_BUILD_UNIT_TESTS + OptionOutput( "Building fuzz tests: " EXIV2_BUILD_FUZZ_TESTS ) + OptionOutput( "Building doc: " EXIV2_BUILD_DOC ) + OptionOutput( "Building with coverage flags: " BUILD_WITH_COVERAGE ) ++OptionOutput( "Building with filesystem access " EXIV2_ENABLE_FILESYSTEM_ACCESS ) + OptionOutput( "Using ccache: " BUILD_WITH_CCACHE ) +diff --git a/include/exiv2/basicio.hpp b/include/exiv2/basicio.hpp +index 179220464..71155136d 100644 +--- a/include/exiv2/basicio.hpp ++++ b/include/exiv2/basicio.hpp +@@ -276,6 +276,7 @@ class EXIV2API IoCloser { + IoCloser& operator=(const IoCloser&) = delete; + }; // class IoCloser + ++#ifdef EXV_ENABLE_FILESYSTEM + /*! + @brief Provides binary file IO by implementing the BasicIo + interface. +@@ -479,6 +480,7 @@ class EXIV2API FileIo : public BasicIo { + std::unique_ptr p_; + + }; // class FileIo ++#endif + + /*! + @brief Provides binary IO on blocks of memory by implementing the BasicIo +@@ -686,7 +688,7 @@ class EXIV2API XPathIo : public MemIo { + */ + void ReadDataUri(const std::string& path); + }; // class XPathIo +-#else ++#elif defined(EXV_ENABLE_FILESYSTEM) + class EXIV2API XPathIo : public FileIo { + public: + /*! +diff --git a/include/exiv2/exif.hpp b/include/exiv2/exif.hpp +index 8b1d3d6a4..1228b5dd4 100644 +--- a/include/exiv2/exif.hpp ++++ b/include/exiv2/exif.hpp +@@ -229,6 +229,7 @@ class EXIV2API ExifThumbC { + data buffer and %DataBuf ensures that it will be deleted. + */ + [[nodiscard]] DataBuf copy() const; ++#ifdef EXV_ENABLE_FILESYSTEM + /*! + @brief Write the thumbnail image to a file. + +@@ -240,6 +241,7 @@ class EXIV2API ExifThumbC { + @return The number of bytes written. + */ + [[nodiscard]] size_t writeFile(const std::string& path) const; ++#endif + /*! + @brief Return the MIME type of the thumbnail, either \c "image/tiff" + or \c "image/jpeg". +@@ -279,6 +281,7 @@ class EXIV2API ExifThumb : public ExifThumbC { + + //! @name Manipulators + //@{ ++#ifdef EXV_ENABLE_FILESYSTEM + /*! + @brief Set the Exif thumbnail to the JPEG image \em path. Set + XResolution, YResolution and ResolutionUnit to \em xres, +@@ -297,6 +300,7 @@ class EXIV2API ExifThumb : public ExifThumbC { + application that comes with OS X for one.) - David Harvey. + */ + void setJpegThumbnail(const std::string& path, URational xres, URational yres, uint16_t unit); ++#endif + /*! + @brief Set the Exif thumbnail to the JPEG image pointed to by \em buf, + and size \em size. Set XResolution, YResolution and +@@ -315,6 +319,7 @@ class EXIV2API ExifThumb : public ExifThumbC { + application that comes with OS X for one.) - David Harvey. + */ + void setJpegThumbnail(const byte* buf, size_t size, URational xres, URational yres, uint16_t unit); ++#ifdef EXV_ENABLE_FILESYSTEM + /*! + @brief Set the Exif thumbnail to the JPEG image \em path. + +@@ -329,6 +334,7 @@ class EXIV2API ExifThumb : public ExifThumbC { + @note Additional existing Exif thumbnail tags are not modified. + */ + void setJpegThumbnail(const std::string& path); ++#endif + /*! + @brief Set the Exif thumbnail to the JPEG image pointed to by \em buf, + and size \em size. +diff --git a/include/exiv2/preview.hpp b/include/exiv2/preview.hpp +index 295c35b5c..db069ec8c 100644 +--- a/include/exiv2/preview.hpp ++++ b/include/exiv2/preview.hpp +@@ -68,6 +68,7 @@ class EXIV2API PreviewImage { + @brief Return the size of the preview image in bytes. + */ + [[nodiscard]] uint32_t size() const; ++#ifdef EXV_ENABLE_FILESYSTEM + /*! + @brief Write the thumbnail image to a file. + +@@ -79,6 +80,7 @@ class EXIV2API PreviewImage { + @return The number of bytes written. + */ + [[nodiscard]] size_t writeFile(const std::string& path) const; ++#endif + /*! + @brief Return the MIME type of the preview image, usually either + \c "image/tiff" or \c "image/jpeg". +diff --git a/meson.build b/meson.build +index 5c205d845..1b141d5cf 100644 +--- a/meson.build ++++ b/meson.build +@@ -116,6 +116,7 @@ cdata.set('EXV_HAVE_LIBZ', zlib_dep.found()) + cdata.set('EXV_USE_CURL', curl_dep.found()) + cdata.set('EXV_ENABLE_NLS', intl_dep.found()) + cdata.set('EXV_ENABLE_WEBREADY', curl_dep.found()) ++cdata.set('EXV_ENABLE_FILESYSTEM', true) + + cfile = configure_file( + input: 'cmake/config.h.cmake', +diff --git a/src/basicio.cpp b/src/basicio.cpp +index 1986622b3..36509b16b 100644 +--- a/src/basicio.cpp ++++ b/src/basicio.cpp +@@ -36,6 +36,7 @@ + #include + #endif + ++#ifdef EXV_ENABLE_FILESYSTEM + #ifdef _WIN32 + using mode_t = unsigned short; + #include +@@ -49,19 +50,7 @@ namespace fs = std::filesystem; + #include + namespace fs = std::experimental::filesystem; + #endif +- +-// ***************************************************************************** +-// class member definitions +-namespace { +-/// @brief replace each substring of the subject that matches the given search string with the given replacement. +-void ReplaceStringInPlace(std::string& subject, std::string_view search, std::string_view replace) { +- auto pos = subject.find(search); +- while (pos != std::string::npos) { +- subject.replace(pos, search.length(), replace); +- pos += subject.find(search, pos + replace.length()); +- } +-} +-} // namespace ++#endif + + namespace Exiv2 { + void BasicIo::readOrThrow(byte* buf, size_t rcount, ErrorCode err) { +@@ -75,6 +64,7 @@ void BasicIo::seekOrThrow(int64_t offset, Position pos, ErrorCode err) { + Internal::enforce(r == 0, err); + } + ++#ifdef EXV_ENABLE_FILESYSTEM + //! Internal Pimpl structure of class FileIo. + class FileIo::Impl { + public: +@@ -570,6 +560,7 @@ const std::string& FileIo::path() const noexcept { + + void FileIo::populateFakeData() { + } ++#endif + + //! Internal Pimpl structure of class MemIo. + class MemIo::Impl final { +@@ -918,7 +909,7 @@ void XPathIo::ReadDataUri(const std::string& path) { + delete[] decodeData; + } + +-#else ++#elif defined(EXV_ENABLE_FILESYSTEM) + XPathIo::XPathIo(const std::string& orgPath) : FileIo(XPathIo::writeDataToFile(orgPath)), tempFilePath_(path()) { + } + +@@ -933,6 +924,16 @@ void XPathIo::transfer(BasicIo& src) { + if (isTemp_) { + // replace temp path to gent path. + auto currentPath = path(); ++ ++ // replace each substring of the subject that matches the given search string with the given replacement. ++ auto ReplaceStringInPlace = [](std::string& subject, std::string_view search, std::string_view replace) { ++ auto pos = subject.find(search); ++ while (pos != std::string::npos) { ++ subject.replace(pos, search.length(), replace); ++ pos += subject.find(search, pos + replace.length()); ++ } ++ }; ++ + ReplaceStringInPlace(currentPath, XPathIo::TEMP_FILE_EXT, XPathIo::GEN_FILE_EXT); + setPath(currentPath); + +@@ -1726,7 +1727,7 @@ CurlIo::CurlIo(const std::string& url, size_t blockSize) { + + // ************************************************************************* + // free functions +- ++#ifdef EXV_ENABLE_FILESYSTEM + DataBuf readFile(const std::string& path) { + FileIo file(path); + if (file.open("rb") != 0) { +@@ -1750,6 +1751,7 @@ size_t writeFile(const DataBuf& buf, const std::string& path) { + } + return file.write(buf.c_data(), buf.size()); + } ++#endif + + #ifdef EXV_USE_CURL + size_t curlWriter(char* data, size_t size, size_t nmemb, std::string* writerData) { +diff --git a/src/exif.cpp b/src/exif.cpp +index 4f61b074c..9c300e531 100644 +--- a/src/exif.cpp ++++ b/src/exif.cpp +@@ -375,6 +375,7 @@ DataBuf ExifThumbC::copy() const { + return thumbnail->copy(exifData_); + } + ++#ifdef EXV_ENABLE_FILESYSTEM + size_t ExifThumbC::writeFile(const std::string& path) const { + auto thumbnail = Thumbnail::create(exifData_); + if (!thumbnail) +@@ -387,6 +388,7 @@ size_t ExifThumbC::writeFile(const std::string& path) const { + + return Exiv2::writeFile(buf, name); + } ++#endif + + const char* ExifThumbC::mimeType() const { + auto thumbnail = Thumbnail::create(exifData_); +@@ -405,10 +407,12 @@ const char* ExifThumbC::extension() const { + ExifThumb::ExifThumb(ExifData& exifData) : ExifThumbC(exifData), exifData_(exifData) { + } + ++#ifdef EXV_ENABLE_FILESYSTEM + void ExifThumb::setJpegThumbnail(const std::string& path, URational xres, URational yres, uint16_t unit) { + DataBuf thumb = readFile(path); // may throw + setJpegThumbnail(thumb.c_data(), thumb.size(), xres, yres, unit); + } ++#endif + + void ExifThumb::setJpegThumbnail(const byte* buf, size_t size, URational xres, URational yres, uint16_t unit) { + setJpegThumbnail(buf, size); +@@ -417,10 +421,12 @@ void ExifThumb::setJpegThumbnail(const byte* buf, size_t size, URational xres, U + exifData_["Exif.Thumbnail.ResolutionUnit"] = unit; + } + ++#ifdef EXV_ENABLE_FILESYSTEM + void ExifThumb::setJpegThumbnail(const std::string& path) { + DataBuf thumb = readFile(path); // may throw + setJpegThumbnail(thumb.c_data(), thumb.size()); + } ++#endif + + void ExifThumb::setJpegThumbnail(const byte* buf, size_t size) { + exifData_["Exif.Thumbnail.Compression"] = static_cast(6); +diff --git a/src/futils.cpp b/src/futils.cpp +index c746838d5..0fd5024d4 100644 +--- a/src/futils.cpp ++++ b/src/futils.cpp +@@ -15,6 +15,7 @@ + #include + #include + ++#ifdef EXV_ENABLE_FILESYSTEM + #if __has_include() + #include + namespace fs = std::filesystem; +@@ -22,6 +23,7 @@ namespace fs = std::filesystem; + #include + namespace fs = std::experimental::filesystem; + #endif ++#endif + + #if defined(_WIN32) + // clang-format off +@@ -230,7 +232,11 @@ bool fileExists(const std::string& path) { + if (fileProtocol(path) != pFile) { + return true; + } ++#ifdef EXV_ENABLE_FILESYSTEM + return fs::exists(path); ++#else ++ return false; ++#endif + } + + std::string strError() { +@@ -340,6 +346,7 @@ Uri Uri::Parse(const std::string& uri) { + } + + std::string getProcessPath() { ++#ifdef EXV_ENABLE_FILESYSTEM + #if defined(__FreeBSD__) + std::string ret("unknown"); + unsigned int n; +@@ -381,5 +388,8 @@ std::string getProcessPath() { + return "unknown"; + } + #endif ++#else ++ return "unknown"; ++#endif + } + } // namespace Exiv2 +diff --git a/src/image.cpp b/src/image.cpp +index 05d8345f9..9bbe1f272 100644 +--- a/src/image.cpp ++++ b/src/image.cpp +@@ -116,11 +116,13 @@ constexpr Registry registry[] = { + #endif // EXV_ENABLE_BMFF + }; + ++#ifdef EXV_ENABLE_FILESYSTEM + std::string pathOfFileUrl(const std::string& url) { + std::string path = url.substr(7); + size_t found = path.find('/'); + return (found == std::string::npos) ? path : path.substr(found); + } ++#endif + + } // namespace + +@@ -748,9 +750,13 @@ bool ImageFactory::checkType(ImageType type, BasicIo& io, bool advance) { + return false; + } + +-ImageType ImageFactory::getType(const std::string& path) { ++ImageType ImageFactory::getType([[maybe_unused]] const std::string& path) { ++#ifdef EXV_ENABLE_FILESYSTEM + FileIo fileIo(path); + return getType(fileIo); ++#else ++ return ImageType::none; ++#endif + } + + ImageType ImageFactory::getType(const byte* data, size_t size) { +@@ -781,12 +787,16 @@ BasicIo::UniquePtr ImageFactory::createIo(const std::string& path, bool useCurl) + + if (fProt == pHttp) + return std::make_unique(path); // may throw ++#ifdef EXV_ENABLE_FILESYSTEM + if (fProt == pFileUri) + return std::make_unique(pathOfFileUrl(path)); + if (fProt == pStdin || fProt == pDataUri) + return std::make_unique(path); // may throw + + return std::make_unique(path); ++#else ++ return nullptr; ++#endif + + (void)(useCurl); + } // ImageFactory::createIo +@@ -817,6 +827,7 @@ Image::UniquePtr ImageFactory::open(BasicIo::UniquePtr io) { + return nullptr; + } + ++#ifdef EXV_ENABLE_FILESYSTEM + Image::UniquePtr ImageFactory::create(ImageType type, const std::string& path) { + auto fileIo = std::make_unique(path); + // Create or overwrite the file, then close it +@@ -831,6 +842,7 @@ Image::UniquePtr ImageFactory::create(ImageType type, const std::string& path) { + throw Error(ErrorCode::kerUnsupportedImageType, static_cast(type)); + return image; + } ++#endif + + Image::UniquePtr ImageFactory::create(ImageType type) { + auto image = create(type, std::make_unique()); +diff --git a/src/makernote_int.cpp b/src/makernote_int.cpp +index 0d2e0443f..0579d2596 100644 +--- a/src/makernote_int.cpp ++++ b/src/makernote_int.cpp +@@ -18,6 +18,7 @@ + #include + #include + ++#ifdef EXV_ENABLE_FILESYSTEM + #if __has_include() + #include + namespace fs = std::filesystem; +@@ -25,6 +26,7 @@ namespace fs = std::filesystem; + #include + namespace fs = std::experimental::filesystem; + #endif ++#endif + + #if !defined(_WIN32) + #include +@@ -62,6 +64,7 @@ namespace Exiv2::Internal { + // If not found in cwd, we return the default path + // which is the user profile path on win and the home dir on linux + std::string getExiv2ConfigPath() { ++#ifdef EXV_ENABLE_FILESYSTEM + #ifdef _WIN32 + std::string inifile("exiv2.ini"); + #else +@@ -83,13 +86,16 @@ std::string getExiv2ConfigPath() { + currentPath = std::string(pw ? pw->pw_dir : ""); + #endif + return (currentPath / inifile).string(); ++#else ++ return ""; ++#endif + } + + std::string readExiv2Config([[maybe_unused]] const std::string& section, [[maybe_unused]] const std::string& value, + const std::string& def) { + std::string result = def; + +-#ifdef EXV_ENABLE_INIH ++#if defined(EXV_ENABLE_INIH) && defined(EXV_ENABLE_FILESYSTEM) + INIReader reader(Exiv2::Internal::getExiv2ConfigPath()); + if (reader.ParseError() == 0) { + result = reader.Get(section, value, def); +diff --git a/src/preview.cpp b/src/preview.cpp +index 18fe6122b..993c3b749 100644 +--- a/src/preview.cpp ++++ b/src/preview.cpp +@@ -966,12 +966,14 @@ PreviewImage& PreviewImage::operator=(const PreviewImage& rhs) { + return *this; + } + ++#ifdef EXV_ENABLE_FILESYSTEM + size_t PreviewImage::writeFile(const std::string& path) const { + std::string name = path + extension(); + // Todo: Creating a DataBuf here unnecessarily copies the memory + DataBuf buf(pData(), size()); + return Exiv2::writeFile(buf, name); + } ++#endif + + DataBuf PreviewImage::copy() const { + return {pData(), size()}; +diff --git a/src/tiffcomposite_int.cpp b/src/tiffcomposite_int.cpp +index 33967b427..95ce450c7 100644 +--- a/src/tiffcomposite_int.cpp ++++ b/src/tiffcomposite_int.cpp +@@ -1095,7 +1095,7 @@ size_t TiffBinaryArray::doWrite(IoWrapper& ioWrapper, ByteOrder byteOrder, size_ + } + DataBuf buf = cryptFct(tag(), mio.mmap(), mio.size(), pRoot_); + if (!buf.empty()) { +- mio.seek(0, Exiv2::FileIo::beg); ++ mio.seek(0, Exiv2::BasicIo::beg); + mio.write(buf.c_data(), buf.size()); + } + } +-- +2.39.3 (Apple Git-146) + diff --git a/tools/depends/target/exiv2/0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch b/tools/depends/target/exiv2/0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch new file mode 100644 index 0000000000000..46cbb7079fa6c --- /dev/null +++ b/tools/depends/target/exiv2/0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch @@ -0,0 +1,169 @@ +From 0a626d3e54d5db1193e42b505db5cac9e65967af Mon Sep 17 00:00:00 2001 +From: Miguel Borges de Freitas <92enen@gmail.com> +Date: Fri, 24 Nov 2023 11:56:11 +0000 +Subject: [PATCH 2/2] Set conditional HTTP depending on EXIV2_ENABLE_WEBREADY + +--- + include/exiv2/exiv2.hpp | 2 ++ + include/meson.build | 5 ++++- + meson.build | 8 +++++++- + meson_options.txt | 5 +++++ + src/CMakeLists.txt | 6 ++++-- + src/basicio.cpp | 2 ++ + src/image.cpp | 4 +++- + 7 files changed, 27 insertions(+), 5 deletions(-) + +diff --git a/include/exiv2/exiv2.hpp b/include/exiv2/exiv2.hpp +index bae5614d2..35bdb1ab9 100644 +--- a/include/exiv2/exiv2.hpp ++++ b/include/exiv2/exiv2.hpp +@@ -19,7 +19,9 @@ + #include "exiv2/exif.hpp" + #include "exiv2/futils.hpp" + #include "exiv2/gifimage.hpp" ++#ifdef EXV_ENABLE_WEBREADY + #include "exiv2/http.hpp" ++#endif + #include "exiv2/image.hpp" + #include "exiv2/iptc.hpp" + #include "exiv2/jp2image.hpp" +diff --git a/include/meson.build b/include/meson.build +index 7bbb3800f..abcb4a4d6 100644 +--- a/include/meson.build ++++ b/include/meson.build +@@ -14,7 +14,6 @@ headers = files( + 'exiv2/exiv2.hpp', + 'exiv2/futils.hpp', + 'exiv2/gifimage.hpp', +- 'exiv2/http.hpp', + 'exiv2/image.hpp', + 'exiv2/image_types.hpp', + 'exiv2/iptc.hpp', +@@ -47,6 +46,10 @@ if get_option('video') + headers += files('exiv2/asfvideo.hpp', 'exiv2/matroskavideo.hpp', 'exiv2/quicktimevideo.hpp', 'exiv2/riffvideo.hpp') + endif + ++if get_option('webready') ++ headers += files('exiv2/http.hpp') ++endif ++ + if zlib_dep.found() + headers += files('exiv2/pngimage.hpp') + endif +diff --git a/meson.build b/meson.build +index 1b141d5cf..b69fada34 100644 +--- a/meson.build ++++ b/meson.build +@@ -67,7 +67,12 @@ if brotli_dep.found() + deps += brotli_dep + endif + +-curl_dep = dependency('libcurl', disabler: true, required: get_option('curl')) ++if get_option('webready') ++ curl_dep = dependency('libcurl', disabler: true, required: get_option('curl')) ++else ++ curl_dep = dependency('', disabler: true, required: false) ++endif ++ + if curl_dep.found() + deps += curl_dep + endif +@@ -113,6 +118,7 @@ cdata.set('EXV_HAVE_XMP_TOOLKIT', expat_dep.found()) + cdata.set('EXV_HAVE_BROTLI', brotli_dep.found()) + cdata.set('EXV_HAVE_ICONV', iconv_dep.found()) + cdata.set('EXV_HAVE_LIBZ', zlib_dep.found()) ++cdata.set('EXV_ENABLE_WEBREADY', get_option('webready')) + cdata.set('EXV_USE_CURL', curl_dep.found()) + cdata.set('EXV_ENABLE_NLS', intl_dep.found()) + cdata.set('EXV_ENABLE_WEBREADY', curl_dep.found()) +diff --git a/meson_options.txt b/meson_options.txt +index f80430a70..ca03706ea 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -44,3 +44,8 @@ option('xmp', type : 'feature', + option('unitTests', type : 'feature', + description : 'Build and run unit tests', + ) ++ ++option('webready', type : 'boolean', ++ value: true, ++ description : 'Build with support for webready', ++) +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index f1b27b861..348a0bfb1 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -48,7 +48,6 @@ set(PUBLIC_HEADERS + ../include/exiv2/exiv2.hpp + ../include/exiv2/futils.hpp + ../include/exiv2/gifimage.hpp +- ../include/exiv2/http.hpp + ../include/exiv2/image.hpp + ../include/exiv2/image_types.hpp + ../include/exiv2/iptc.hpp +@@ -92,7 +91,6 @@ add_library( exiv2lib + futils.cpp + fff.h + gifimage.cpp +- http.cpp + image.cpp + iptc.cpp + jp2image.cpp +@@ -129,6 +127,10 @@ generate_export_header(exiv2lib + + # Conditional addition of sources to library targets + # --------------------------------------------------------- ++if(EXIV2_ENABLE_WEBREADY) ++ set(PUBLIC_HEADERS ${PUBLIC_HEADERS} ../include/exiv2/http.hpp) ++ target_sources(exiv2lib PRIVATE http.cpp ../include/exiv2/http.hpp) ++endif() + + if( EXIV2_ENABLE_PNG ) + set(PUBLIC_HEADERS ${PUBLIC_HEADERS} ../include/exiv2/pngimage.hpp) +diff --git a/src/basicio.cpp b/src/basicio.cpp +index 36509b16b..5f3a9abb3 100644 +--- a/src/basicio.cpp ++++ b/src/basicio.cpp +@@ -1386,6 +1386,7 @@ void RemoteIo::populateFakeData() { + } + } + ++#ifdef EXV_ENABLE_WEBREADY + //! Internal Pimpl structure of class HttpIo. + class HttpIo::HttpImpl : public Impl { + public: +@@ -1525,6 +1526,7 @@ void HttpIo::HttpImpl::writeRemote(const byte* data, size_t size, size_t from, s + HttpIo::HttpIo(const std::string& url, size_t blockSize) { + p_ = std::make_unique(url, blockSize); + } ++#endif + + #ifdef EXV_USE_CURL + //! Internal Pimpl structure of class RemoteIo. +diff --git a/src/image.cpp b/src/image.cpp +index 9bbe1f272..e65ec7464 100644 +--- a/src/image.cpp ++++ b/src/image.cpp +@@ -777,7 +777,7 @@ ImageType ImageFactory::getType(BasicIo& io) { + } + + BasicIo::UniquePtr ImageFactory::createIo(const std::string& path, bool useCurl) { +- Protocol fProt = fileProtocol(path); ++ [[maybe_unused]] Protocol fProt = fileProtocol(path); + + #ifdef EXV_USE_CURL + if (useCurl && (fProt == pHttp || fProt == pHttps || fProt == pFtp)) { +@@ -785,8 +785,10 @@ BasicIo::UniquePtr ImageFactory::createIo(const std::string& path, bool useCurl) + } + #endif + ++#ifdef EXV_ENABLE_WEBREADY + if (fProt == pHttp) + return std::make_unique(path); // may throw ++#endif + #ifdef EXV_ENABLE_FILESYSTEM + if (fProt == pFileUri) + return std::make_unique(pathOfFileUrl(path)); +-- +2.39.3 (Apple Git-146) + diff --git a/tools/depends/target/exiv2/0003-UWP-Disable-getLoadedLibraries.patch b/tools/depends/target/exiv2/0003-UWP-Disable-getLoadedLibraries.patch new file mode 100644 index 0000000000000..41047f7056cc6 --- /dev/null +++ b/tools/depends/target/exiv2/0003-UWP-Disable-getLoadedLibraries.patch @@ -0,0 +1,45 @@ +From 8affb24ed2c704462cf49f8069954acdfe1f8f79 Mon Sep 17 00:00:00 2001 +From: Miguel Borges de Freitas <92enen@gmail.com> +Date: Mon, 17 Jun 2024 13:29:29 +0100 +Subject: [PATCH] [UWP] Disable getLoadedLibraries + +--- + src/version.cpp | 21 ++++++++++++--------- + 1 file changed, 12 insertions(+), 9 deletions(-) + +diff --git a/src/version.cpp b/src/version.cpp +index 7e8c0b98a..8dcc11ece 100644 +--- a/src/version.cpp ++++ b/src/version.cpp +@@ -122,16 +122,19 @@ static std::vector getLoadedLibraries() { + std::string path; + + #if defined(_WIN32) || defined(__CYGWIN__) +- // enumerate loaded libraries and determine path to executable +- HMODULE handles[200]; +- DWORD cbNeeded; +- if (EnumProcessModules(GetCurrentProcess(), handles, static_cast(std::size(handles)), &cbNeeded)) { +- char szFilename[_MAX_PATH]; +- for (DWORD h = 0; h < cbNeeded / sizeof(handles[0]); h++) { +- GetModuleFileNameA(handles[h], szFilename, static_cast(std::size(szFilename))); +- pushPath(szFilename, libs, paths); +- } ++ #include ++ #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_APP) ++ // enumerate loaded libraries and determine path to executable ++ HMODULE handles[200]; ++ DWORD cbNeeded; ++ if (EnumProcessModules(GetCurrentProcess(), handles, static_cast(std::size(handles)), &cbNeeded)) { ++ char szFilename[_MAX_PATH]; ++ for (DWORD h = 0; h < cbNeeded / sizeof(handles[0]); h++) { ++ GetModuleFileNameA(handles[h], szFilename, static_cast(std::size(szFilename))); ++ pushPath(szFilename, libs, paths); ++ } + } ++ #endif + #elif defined(__APPLE__) + // man 3 dyld + uint32_t count = _dyld_image_count(); +-- +2.39.3 (Apple Git-146) + diff --git a/tools/depends/target/exiv2/EXIV2-VERSION b/tools/depends/target/exiv2/EXIV2-VERSION new file mode 100644 index 0000000000000..c07af28a90f08 --- /dev/null +++ b/tools/depends/target/exiv2/EXIV2-VERSION @@ -0,0 +1,6 @@ +LIBNAME=exiv2 +VERSION=0.28.2 +ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz +SHA512=197cc607c0271b5731714713283756250031cef81ba7ed5d9c3e222b4c2397966cc2bbdbceaae706598329dde6f8a9729597d0ae4c36ac264c76546942e4e37b +BYPRODUCT=libexiv2.a +BYPRODUCT_WIN=exiv2.lib diff --git a/tools/depends/target/exiv2/Makefile b/tools/depends/target/exiv2/Makefile new file mode 100644 index 0000000000000..b26a4459a4a4b --- /dev/null +++ b/tools/depends/target/exiv2/Makefile @@ -0,0 +1,47 @@ +include ../../Makefile.include EXIV2-VERSION ../../download-files.include +DEPS = ../../Makefile.include Makefile EXIV2-VERSION ../../download-files.include \ + 0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch \ + 0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch \ + 0003-UWP-Disable-getLoadedLibraries.patch + +# configuration settings +CMAKE_OPTIONS=-DCMAKE_INSTALL_PREFIX=$(PREFIX) \ + -DBUILD_SHARED_LIBS=OFF \ + -DEXIV2_ENABLE_WEBREADY=OFF \ + -DEXIV2_ENABLE_XMP=OFF \ + -DEXIV2_ENABLE_CURL=OFF \ + -DEXIV2_ENABLE_NLS=OFF \ + -DEXIV2_BUILD_SAMPLES=OFF \ + -DEXIV2_BUILD_UNIT_TESTS=OFF \ + -DEXIV2_ENABLE_VIDEO=OFF \ + -DEXIV2_ENABLE_BMFF=OFF \ + -DEXIV2_ENABLE_BROTLI=OFF \ + -DEXIV2_ENABLE_INIH=OFF \ + -DEXIV2_ENABLE_FILESYSTEM_ACCESS=OFF \ + -DEXIV2_BUILD_EXIV2_COMMAND=OFF + +LIBDYLIB=$(PLATFORM)/build/$(BYPRODUCT) + +all: .installed-$(PLATFORM) + +$(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) + rm -rf $(PLATFORM); mkdir -p $(PLATFORM)/build + cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) + cd $(PLATFORM); patch -p1 -i ../0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch + cd $(PLATFORM); patch -p1 -i ../0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch + cd $(PLATFORM); patch -p1 -i ../0003-UWP-Disable-getLoadedLibraries.patch + cd $(PLATFORM)/build; $(CMAKE) $(CMAKE_OPTIONS) .. + +$(LIBDYLIB): $(PLATFORM) + $(MAKE) -C $(PLATFORM)/build + +.installed-$(PLATFORM): $(LIBDYLIB) + $(MAKE) -C $(PLATFORM)/build install + touch $@ + +clean: + $(MAKE) -C $(PLATFORM)/build clean + rm -f .installed-$(PLATFORM) + +distclean:: + rm -rf $(PLATFORM) .installed-$(PLATFORM) From c7284f5db080c97d8cfb73469b8e6711ddca11a0 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Fri, 17 Nov 2023 16:39:58 +1000 Subject: [PATCH 155/651] [cmake][modules] FindIconv utilise cmake system findmodule cmake has had a system provided find module for iconv since cmake 3.11 use it, and just alias to our core target naming for our purposes. Provides both our internal target name and the "standard" cmake target name to callers --- cmake/modules/FindIconv.cmake | 41 ++++++++++++++--------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/cmake/modules/FindIconv.cmake b/cmake/modules/FindIconv.cmake index 3acd96ea632c4..9f8ff74a42ac5 100644 --- a/cmake/modules/FindIconv.cmake +++ b/cmake/modules/FindIconv.cmake @@ -1,41 +1,32 @@ #.rst: -# FindICONV +# FindIconv # -------- # Finds the ICONV library # -# This will define the following target: +# This will define the following targets: # -# ${APP_NAME_LC}::ICONV - The ICONV library +# ${APP_NAME_LC}::Iconv - An alias of the Iconv::Iconv target +# Iconv::Iconv - The ICONV library if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) - find_path(ICONV_INCLUDE_DIR NAMES iconv.h - HINTS ${DEPENDS_PATH}/include) - find_library(ICONV_LIBRARY NAMES iconv libiconv c - HINTS ${DEPENDS_PATH}/lib) + # We do this dance to utilise cmake system FindIconv. Saves us dealing with it + set(_temp_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) + unset(CMAKE_MODULE_PATH) - set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARY}) - check_function_exists(iconv HAVE_ICONV_FUNCTION) - if(NOT HAVE_ICONV_FUNCTION) - check_function_exists(libiconv HAVE_LIBICONV_FUNCTION2) - set(HAVE_ICONV_FUNCTION ${HAVE_LIBICONV_FUNCTION2}) - unset(HAVE_LIBICONV_FUNCTION2) + if(Iconv_FIND_REQUIRED) + set(ICONV_REQUIRED "REQUIRED") endif() - unset(CMAKE_REQUIRED_LIBRARIES) - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(Iconv - REQUIRED_VARS ICONV_LIBRARY ICONV_INCLUDE_DIR HAVE_ICONV_FUNCTION) + find_package(Iconv ${ICONV_REQUIRED}) + + # Back to our normal module paths + set(CMAKE_MODULE_PATH ${_temp_CMAKE_MODULE_PATH}) if(ICONV_FOUND) - # Libc causes grief for linux, so search if found library is libc.* and only - # create imported TARGET if its not - if(NOT ${ICONV_LIBRARY} MATCHES ".*libc\..*") - add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) - set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES - IMPORTED_LOCATION "${ICONV_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${ICONV_INCLUDE_DIR}") - endif() + # We still want to Alias its "standard" target to our APP_NAME_LC based target + # for integration into our core dep packaging + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS Iconv::Iconv) else() if(Iconv_FIND_REQUIRED) message(FATAL_ERROR "Iconv libraries were not found.") From 197a3e6d2e80878f5f3f827b3a9073118b59bd51 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Thu, 16 Nov 2023 16:49:54 +0000 Subject: [PATCH 156/651] Pictures: Move to exiv2 for image metadata retrieval (exif and iptc) --- CMakeLists.txt | 1 + tools/depends/target/Makefile | 1 + xbmc/addons/ImageDecoder.cpp | 56 +- xbmc/pictures/CMakeLists.txt | 11 +- xbmc/pictures/Exif.h | 72 +++ xbmc/pictures/ExifParse.h | 37 -- xbmc/pictures/ImageMetadata.h | 18 + xbmc/pictures/ImageMetadataParser.cpp | 462 +++++++++++++++++ xbmc/pictures/ImageMetadataParser.h | 35 ++ xbmc/pictures/Iptc.h | 47 ++ xbmc/pictures/IptcParse.cpp | 212 -------- xbmc/pictures/IptcParse.h | 10 - xbmc/pictures/JpegParse.cpp | 316 ------------ xbmc/pictures/JpegParse.h | 63 --- xbmc/pictures/PictureInfoTag.cpp | 706 ++++++++++++-------------- xbmc/pictures/PictureInfoTag.h | 103 +--- xbmc/pictures/libexif.cpp | 35 -- xbmc/pictures/libexif.h | 138 ----- 18 files changed, 996 insertions(+), 1327 deletions(-) create mode 100644 xbmc/pictures/Exif.h delete mode 100644 xbmc/pictures/ExifParse.h create mode 100644 xbmc/pictures/ImageMetadata.h create mode 100644 xbmc/pictures/ImageMetadataParser.cpp create mode 100644 xbmc/pictures/ImageMetadataParser.h create mode 100644 xbmc/pictures/Iptc.h delete mode 100644 xbmc/pictures/IptcParse.cpp delete mode 100644 xbmc/pictures/IptcParse.h delete mode 100644 xbmc/pictures/JpegParse.cpp delete mode 100644 xbmc/pictures/JpegParse.h delete mode 100644 xbmc/pictures/libexif.cpp delete mode 100644 xbmc/pictures/libexif.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2883d76f83405..8746b432316f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,6 +210,7 @@ set(required_deps ASS>=0.15.0 Cdio CrossGUID Curl + Exiv2 FFMPEG FlatBuffers Fmt diff --git a/tools/depends/target/Makefile b/tools/depends/target/Makefile index a815853007e7d..35140b4a58515 100644 --- a/tools/depends/target/Makefile +++ b/tools/depends/target/Makefile @@ -9,6 +9,7 @@ DEPENDS = \ brotli \ bzip2 \ dav1d \ + exiv2 \ expat \ ffmpeg \ fontconfig \ diff --git a/xbmc/addons/ImageDecoder.cpp b/xbmc/addons/ImageDecoder.cpp index 48c3042b44ac4..20589eaa0da74 100644 --- a/xbmc/addons/ImageDecoder.cpp +++ b/xbmc/addons/ImageDecoder.cpp @@ -113,63 +113,63 @@ bool CImageDecoder::LoadInfoTag(const std::string& fileName, CPictureInfoTag* ta * @todo Rework @ref CPictureInfoTag to not limit on fixed structures ExifInfo & IPTCInfo. */ - tag->m_exifInfo.Width = ifcTag.width; - tag->m_exifInfo.Height = ifcTag.height; - tag->m_exifInfo.Distance = ifcTag.distance; - tag->m_exifInfo.Orientation = ifcTag.orientation; - tag->m_exifInfo.IsColor = ifcTag.color == ADDON_IMG_COLOR_COLORED ? 1 : 0; - tag->m_exifInfo.ApertureFNumber = ifcTag.aperture_f_number; - tag->m_exifInfo.FlashUsed = ifcTag.flash_used ? 1 : 0; - tag->m_exifInfo.LightSource = ifcTag.light_source; - tag->m_exifInfo.FocalLength = ifcTag.focal_length; - tag->m_exifInfo.FocalLength35mmEquiv = ifcTag.focal_length_in_35mm_format; - tag->m_exifInfo.MeteringMode = ifcTag.metering_mode; - tag->m_exifInfo.DigitalZoomRatio = ifcTag.digital_zoom_ratio; - tag->m_exifInfo.ExposureTime = ifcTag.exposure_time; - tag->m_exifInfo.ExposureBias = ifcTag.exposure_bias; - tag->m_exifInfo.ExposureProgram = ifcTag.exposure_program; - tag->m_exifInfo.ExposureMode = ifcTag.exposure_mode; - tag->m_exifInfo.ISOequivalent = static_cast(ifcTag.iso_speed); + tag->m_imageMetadata.exifInfo.Width = ifcTag.width; + tag->m_imageMetadata.exifInfo.Height = ifcTag.height; + tag->m_imageMetadata.exifInfo.Distance = ifcTag.distance; + tag->m_imageMetadata.exifInfo.Orientation = ifcTag.orientation; + tag->m_imageMetadata.exifInfo.IsColor = ifcTag.color == ADDON_IMG_COLOR_COLORED ? 1 : 0; + tag->m_imageMetadata.exifInfo.ApertureFNumber = ifcTag.aperture_f_number; + tag->m_imageMetadata.exifInfo.FlashUsed = ifcTag.flash_used ? 1 : 0; + tag->m_imageMetadata.exifInfo.LightSource = ifcTag.light_source; + tag->m_imageMetadata.exifInfo.FocalLength = ifcTag.focal_length; + tag->m_imageMetadata.exifInfo.FocalLength35mmEquiv = ifcTag.focal_length_in_35mm_format; + tag->m_imageMetadata.exifInfo.MeteringMode = ifcTag.metering_mode; + tag->m_imageMetadata.exifInfo.DigitalZoomRatio = ifcTag.digital_zoom_ratio; + tag->m_imageMetadata.exifInfo.ExposureTime = ifcTag.exposure_time; + tag->m_imageMetadata.exifInfo.ExposureBias = ifcTag.exposure_bias; + tag->m_imageMetadata.exifInfo.ExposureProgram = ifcTag.exposure_program; + tag->m_imageMetadata.exifInfo.ExposureMode = ifcTag.exposure_mode; + tag->m_imageMetadata.exifInfo.ISOequivalent = static_cast(ifcTag.iso_speed); CDateTime dt; dt.SetFromUTCDateTime(ifcTag.time_created); - tag->m_iptcInfo.TimeCreated = dt.GetAsLocalizedDateTime(); tag->m_dateTimeTaken = dt; - tag->m_exifInfo.GpsInfoPresent = ifcTag.gps_info_present; - if (tag->m_exifInfo.GpsInfoPresent) + tag->m_imageMetadata.iptcInfo.TimeCreated = dt.GetAsLocalizedDateTime(); + tag->m_imageMetadata.exifInfo.GpsInfoPresent = ifcTag.gps_info_present; + if (tag->m_imageMetadata.exifInfo.GpsInfoPresent) { - tag->m_exifInfo.GpsLat = + tag->m_imageMetadata.exifInfo.GpsLat = StringUtils::Format("{}{:.0f}°{:.0f}'{:.2f}\"", ifcTag.latitude_ref, ifcTag.latitude[0], ifcTag.latitude[1], ifcTag.latitude[2]); - tag->m_exifInfo.GpsLong = + tag->m_imageMetadata.exifInfo.GpsLong = StringUtils::Format("{}{:.0f}°{:.0f}'{:.2f}\"", ifcTag.longitude_ref, ifcTag.longitude[0], ifcTag.longitude[1], ifcTag.longitude[2]); - tag->m_exifInfo.GpsAlt = + tag->m_imageMetadata.exifInfo.GpsAlt = StringUtils::Format("{}{:.2f} m", ifcTag.altitude_ref ? '-' : '+', ifcTag.altitude); } if (ifcTag.camera_manufacturer) { - tag->m_exifInfo.CameraMake = ifcTag.camera_manufacturer; + tag->m_imageMetadata.exifInfo.CameraMake = ifcTag.camera_manufacturer; free(ifcTag.camera_manufacturer); } if (ifcTag.camera_model) { - tag->m_exifInfo.CameraModel = ifcTag.camera_model; + tag->m_imageMetadata.exifInfo.CameraModel = ifcTag.camera_model; free(ifcTag.camera_model); } if (ifcTag.author) { - tag->m_iptcInfo.Author = ifcTag.author; + tag->m_imageMetadata.iptcInfo.Author = ifcTag.author; free(ifcTag.author); } if (ifcTag.description) { - tag->m_exifInfo.Description = ifcTag.description; + tag->m_imageMetadata.exifInfo.Description = ifcTag.description; free(ifcTag.description); } if (ifcTag.copyright) { - tag->m_iptcInfo.CopyrightNotice = ifcTag.copyright; + tag->m_imageMetadata.iptcInfo.CopyrightNotice = ifcTag.copyright; free(ifcTag.copyright); } } diff --git a/xbmc/pictures/CMakeLists.txt b/xbmc/pictures/CMakeLists.txt index ee73acad6ccf4..874f453280b4e 100644 --- a/xbmc/pictures/CMakeLists.txt +++ b/xbmc/pictures/CMakeLists.txt @@ -1,11 +1,8 @@ -set(SOURCES ExifParse.cpp - GUIDialogPictureInfo.cpp +set(SOURCES GUIDialogPictureInfo.cpp GUIViewStatePictures.cpp GUIWindowPictures.cpp GUIWindowSlideShow.cpp - IptcParse.cpp - JpegParse.cpp - libexif.cpp + ImageMetadataParser.cpp Picture.cpp PictureFolderImageFileLoader.cpp PictureInfoLoader.cpp @@ -16,10 +13,14 @@ set(SOURCES ExifParse.cpp SlideShowPicture.cpp) set(HEADERS interfaces/ISlideShowDelegate.h + Exif.h GUIDialogPictureInfo.h GUIViewStatePictures.h GUIWindowPictures.h GUIWindowSlideShow.h + ImageMetadata.h + ImageMetadataParser.h + Iptc.h Picture.h PictureFolderImageFileLoader.h PictureInfoLoader.h diff --git a/xbmc/pictures/Exif.h b/xbmc/pictures/Exif.h new file mode 100644 index 0000000000000..bdc2255156e8f --- /dev/null +++ b/xbmc/pictures/Exif.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 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 +#include + +constexpr unsigned int MAX_DATE_COPIES = 10; +constexpr int EXIF_COMMENT_CHARSET_CONVERTED = -1; + +struct ExifInfo +{ + ExifInfo() = default; + ExifInfo(const ExifInfo&) = default; + ExifInfo(ExifInfo&&) = default; + + ExifInfo& operator=(const ExifInfo&) = default; + ExifInfo& operator=(ExifInfo&&) = default; + + std::string CameraMake; + std::string CameraModel; + std::string DateTime; + int Height{}; + int Width{}; + int Orientation{}; + int IsColor{}; + std::string Process{}; + int FlashUsed{}; + float FocalLength{}; + float ExposureTime{}; + float ApertureFNumber{}; + float Distance{}; + float CCDWidth{}; + float ExposureBias{}; + float DigitalZoomRatio{}; + int FocalLength35mmEquiv{}; + int Whitebalance{}; + int MeteringMode{}; + int ExposureProgram{}; + int ExposureMode{}; + int ISOequivalent{}; + int LightSource{}; + //! TODO: Remove me, not needed anymore (still exposed to addon interface) + int CommentsCharset{}; + //! TODO: Remove me, not needed anymore (still exposed to addon interface) + int XPCommentsCharset{}; + std::string Comments; + std::string FileComment; + std::string XPComment; + std::string Description; + + //! TODO: this is not used anywhere, just nuke it + unsigned ThumbnailOffset{}; + unsigned ThumbnailSize{}; + unsigned LargestExifOffset{}; + //! TODO: this is not used anywhere, just nuke it + char ThumbnailAtEnd{}; + int ThumbnailSizeOffset{}; + //! TODO: this is not used anywhere, just nuke it + std::vector DateTimeOffsets; + + int GpsInfoPresent{}; + std::string GpsLat; + std::string GpsLong; + std::string GpsAlt; +}; diff --git a/xbmc/pictures/ExifParse.h b/xbmc/pictures/ExifParse.h deleted file mode 100644 index 6da3bf26bbbe6..0000000000000 --- a/xbmc/pictures/ExifParse.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "libexif.h" - -class CExifParse -{ - public: - ~CExifParse(void) = default; - bool Process(const unsigned char* const Data, const unsigned short length, ExifInfo_t *info); - static int Get16(const void* const Short, const bool motorolaOrder=true); - static int Get32(const void* const Long, const bool motorolaOrder=true); - - private: - ExifInfo_t *m_ExifInfo = nullptr; - double m_FocalPlaneXRes = 0.0; - double m_FocalPlaneUnits = 0.0; - unsigned m_LargestExifOffset = 0; // Last exif data referenced (to check if thumbnail is at end) - int m_ExifImageWidth = 0; - bool m_MotorolaOrder = false; - bool m_DateFound = false; - -// void LocaliseDate (void); -// void GetExposureTime (const float exposureTime); - double ConvertAnyFormat(const void* const ValuePtr, int Format); - void ProcessDir(const unsigned char* const DirStart, - const unsigned char* const OffsetBase, - const unsigned ExifLength, int NestingLevel); - void ProcessGpsInfo(const unsigned char* const DirStart, - int ByteCountUnused, - const unsigned char* const OffsetBase, - unsigned ExifLength); - void GetLatLong(const unsigned int Format, - const unsigned char* ValuePtr, - const int ComponentSize, - char *latlongString); -}; - diff --git a/xbmc/pictures/ImageMetadata.h b/xbmc/pictures/ImageMetadata.h new file mode 100644 index 0000000000000..68d9578b46854 --- /dev/null +++ b/xbmc/pictures/ImageMetadata.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2023 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 "Exif.h" +#include "Iptc.h" + +struct ImageMetadata +{ + ExifInfo exifInfo; + IPTCInfo iptcInfo; +}; diff --git a/xbmc/pictures/ImageMetadataParser.cpp b/xbmc/pictures/ImageMetadataParser.cpp new file mode 100644 index 0000000000000..a4c4e904e3daf --- /dev/null +++ b/xbmc/pictures/ImageMetadataParser.cpp @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2023 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 "ImageMetadataParser.h" + +#include "filesystem/File.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include +#include +#include + +using namespace XFILE; + +namespace +{ +std::string GetGPSString(const Exiv2::Value& value) +{ + const Exiv2::Rational degress = value.toRational(0); + const Exiv2::Rational minutes = value.toRational(1); + Exiv2::Rational seconds = value.toRational(2); + + const int32_t dd = degress.first; + int32_t mm{0}; + float ss{0.0}; + if (minutes.second > 0) + { + const int32_t rem = minutes.first % minutes.second; + mm = minutes.first / minutes.second; + if ((seconds.first == 0) && (seconds.second == 1) && + (rem <= std::numeric_limits::max() / 60)) + { + seconds.first = 60 * rem; + seconds.second = minutes.second; + } + } + if (seconds.second > 0) + { + ss = static_cast(seconds.first) / seconds.second; + } + return StringUtils::Format("{}° {}' {:.3f}\"", dd, mm, ss); +} + +bool IsOnlySpaces(const std::string& str) +{ + return std::all_of(str.begin(), str.end(), [](unsigned char c) { return std::isspace(c); }); +} +} // namespace + +CImageMetadataParser::CImageMetadataParser() : m_imageMetadata(std::make_unique()) +{ +} + +std::unique_ptr CImageMetadataParser::ExtractMetadata(const std::string& picFileName) +{ + // read image file to a buffer so it can be fed to libexiv2 + CFile file; + std::vector outputBuffer; + ssize_t readbytes = file.LoadFile(picFileName, outputBuffer); + if (readbytes <= 0) + { + return nullptr; + } + + // read image metadata + auto image = Exiv2::ImageFactory::open(outputBuffer.data(), readbytes); + image->readMetadata(); + + CImageMetadataParser parser; + + // extract metadata + parser.ExtractCommonMetadata(image); + + parser.ExtractExif(image->exifData()); + parser.ExtractIPTC(image->iptcData()); + + return std::move(parser.m_imageMetadata); +} + +void CImageMetadataParser::ExtractCommonMetadata(std::unique_ptr& image) +{ + //! TODO: all these elements are generic should be moved out of the exif struct + m_imageMetadata->exifInfo.Height = image->pixelHeight(); + m_imageMetadata->exifInfo.Width = image->pixelWidth(); + m_imageMetadata->exifInfo.FileComment = image->comment(); + + if (image->imageType() == Exiv2::ImageType::jpeg) + { + auto jpegImage = dynamic_cast(image.get()); + m_imageMetadata->exifInfo.IsColor = jpegImage->numColorComponents() == 3; + m_imageMetadata->exifInfo.Process = jpegImage->encodingProcess(); + } +} + +void CImageMetadataParser::ExtractExif(Exiv2::ExifData& exifData) +{ + for (auto it = exifData.begin(); it != exifData.end(); ++it) + { + const std::string exifKey = it->key(); + if (exifKey == "Exif.Image.Make") + { + m_imageMetadata->exifInfo.CameraMake = it->value().toString(); + } + else if (exifKey == "Exif.Image.ImageDescription") + { + const std::string value = it->value().toString(); + if (!IsOnlySpaces(value)) + { + m_imageMetadata->exifInfo.Description = value; + } + } + else if (exifKey == "Exif.Image.Model") + { + m_imageMetadata->exifInfo.CameraModel = it->value().toString(); + } + else if (exifKey == "Exif.Image.Orientation") + { + const int orientationValue = it->value().toUint32(); + if (orientationValue < 0 || orientationValue > 8) + { + CLog::LogF(LOGWARNING, "Exif: Undefined rotation value {}", + m_imageMetadata->exifInfo.Orientation); + continue; + } + m_imageMetadata->exifInfo.Orientation = orientationValue; + } + else if ((exifKey == "Exif.Image.DateTime" || exifKey == "Exif.Photo.DateTimeDigitized")) + { + if (m_imageMetadata->exifInfo.DateTime.empty()) + m_imageMetadata->exifInfo.DateTime = it->value().toString(); + } + else if (exifKey == "Exif.Photo.ExposureTime") + { + m_imageMetadata->exifInfo.ExposureTime = it->value().toFloat(); + } + else if (exifKey == "Exif.Photo.FNumber") + { + m_imageMetadata->exifInfo.ApertureFNumber = it->value().toFloat(); + } + else if (exifKey == "Exif.Photo.ExposureProgram") + { + m_imageMetadata->exifInfo.ExposureProgram = it->value().toUint32(); + } + else if (exifKey == "Exif.Photo.ISOSpeedRatings") + { + m_imageMetadata->exifInfo.ISOequivalent = it->value().toUint32(); + } + else if (exifKey == "Exif.Photo.DateTimeOriginal") + { + m_imageMetadata->exifInfo.DateTime = it->value().toString(); + } + else if (exifKey == "Exif.Photo.ApertureValue") + { + m_imageMetadata->exifInfo.ApertureFNumber = + static_cast(it->value().toFloat()) * log(2.0) * 0.5; + } + else if (exifKey == "Exif.Photo.MaxApertureValue") + { + // More relevant info always comes earlier, so only use this field if we don't + // have appropriate aperture information yet. + if (m_imageMetadata->exifInfo.ApertureFNumber == 0) + m_imageMetadata->exifInfo.ApertureFNumber = + static_cast(it->value().toFloat()) * log(2.0) * 0.5; + } + else if (exifKey == "Exif.Photo.ShutterSpeedValue") + { + // More complicated way of expressing exposure time, so only use + // this value if we don't already have it from somewhere else. + if (m_imageMetadata->exifInfo.ExposureTime == 0) + { + m_imageMetadata->exifInfo.ExposureTime = + 1 / exp(static_cast(it->value().toFloat()) * log(2.0)); + } + } + else if (exifKey == "Exif.Photo.ExposureBiasValue") + { + m_imageMetadata->exifInfo.ExposureBias = it->value().toFloat(); + } + else if (exifKey == "Exif.Photo.MeteringMode") + { + m_imageMetadata->exifInfo.MeteringMode = it->value().toUint32(); + } + else if (exifKey == "Exif.Photo.Flash") + { + m_imageMetadata->exifInfo.FlashUsed = it->value().toUint32(); + } + else if (exifKey == "Exif.Photo.FocalLength") + { + m_imageMetadata->exifInfo.FocalLength = it->value().toFloat(); + } + else if (exifKey == "Exif.Photo.SubjectDistance") + { + m_imageMetadata->exifInfo.Distance = it->value().toFloat(); + } + else if (exifKey == "Exif.Image.XPComment") + { + m_imageMetadata->exifInfo.XPComment = it->value().toString(); + } + else if (exifKey == "Exif.Photo.UserComment") + { + m_imageMetadata->exifInfo.Comments = it->value().toString(); + } + else if (exifKey == "Exif.Photo.PixelXDimension" || exifKey == "Exif.Photo.PixelYDimension") + { + // Use largest of height and width to deal with images that have been + // rotated to portrait format. + { + const int value = static_cast(it->value().toInt64()); + if (m_imageWidth < value) + { + m_imageWidth = value; + } + } + } + else if (exifKey == "Exif.Photo.FocalPlaneXResolution") + { + m_focalPlaneXRes = it->value().toFloat(); + } + else if (exifKey == "Exif.Photo.FocalPlaneResolutionUnit") + { + const uint32_t value = it->value().toUint32(); + // see: https://exiftool.org/TagNames/EXIF.html + switch (value) + { + case 1: + m_focalPlaneUnits = 0; + break; // None + case 2: + m_focalPlaneUnits = 25.4; + break; // inch + case 3: + m_focalPlaneUnits = 10; + break; // centimeter + case 4: + m_focalPlaneUnits = 1; + break; // millimeter + case 5: + m_focalPlaneUnits = .001; + break; // micrometer + } + } + else if (exifKey == "Exif.Photo.ExposureMode") + { + m_imageMetadata->exifInfo.ExposureMode = it->value().toUint32(); + } + else if (exifKey == "Exif.Photo.WhiteBalance") + { + m_imageMetadata->exifInfo.Whitebalance = it->value().toUint32(); + } + else if (exifKey == "Exif.Photo.LightSource") + { + m_imageMetadata->exifInfo.LightSource = it->value().toUint32(); + } + else if (exifKey == "Exif.Photo.DigitalZoomRatio") + { + m_imageMetadata->exifInfo.DigitalZoomRatio = it->value().toFloat(); + } + else if (exifKey == "Exif.Photo.FocalLengthIn35mmFilm") + { + // The focal length equivalent 35 mm is a 2.2 tag (defined as of April 2002) + // if its present, use it to compute equivalent focal length instead of + // computing it from sensor geometry and actual focal length. + m_imageMetadata->exifInfo.FocalLength35mmEquiv = it->value().toUint32(); + } + else if (exifKey == "Exif.GPSInfo.GPSLatitudeRef") + { + if (m_imageMetadata->exifInfo.GpsLat.empty()) + { + m_imageMetadata->exifInfo.GpsLat = StringUtils::Format(" {}", it->value().toString()); + } + else + { + m_imageMetadata->exifInfo.GpsLat = + StringUtils::Format("{} {}", m_imageMetadata->exifInfo.GpsLat, it->value().toString()); + } + } + else if (exifKey == "Exif.GPSInfo.GPSLongitudeRef") + { + if (m_imageMetadata->exifInfo.GpsLong.empty()) + { + m_imageMetadata->exifInfo.GpsLong = StringUtils::Format(" {}", it->value().toString()); + } + else + { + m_imageMetadata->exifInfo.GpsLong = + StringUtils::Format("{} {}", m_imageMetadata->exifInfo.GpsLong, it->value().toString()); + } + } + else if (exifKey == "Exif.GPSInfo.GPSLatitude") + { + m_imageMetadata->exifInfo.GpsLat = + GetGPSString(it->value()) + m_imageMetadata->exifInfo.GpsLat; + } + else if (exifKey == "Exif.GPSInfo.GPSLongitude") + { + m_imageMetadata->exifInfo.GpsLong = + GetGPSString(it->value()) + m_imageMetadata->exifInfo.GpsLong; + } + else if (exifKey == "Exif.GPSInfo.GPSAltitude") + { + m_imageMetadata->exifInfo.GpsAlt += StringUtils::Format("{}m", it->value().toFloat()); + } + else if (exifKey == "Exif.GPSInfo.GPSAltitudeRef") + { + auto value = it->value().toUint32(); + if (value == 1) // below sea level + { + m_imageMetadata->exifInfo.GpsAlt = "-" + m_imageMetadata->exifInfo.GpsAlt; + } + } + } + + // Compute the CCD width, in millimeters. + if (m_focalPlaneXRes != 0) + { + // Note: With some cameras, its not possible to compute this correctly because + // they don't adjust the indicated focal plane resolution units when using less + // than maximum resolution, so the CCDWidth value comes out too small. + m_imageMetadata->exifInfo.CCDWidth = + static_cast(m_imageWidth * m_focalPlaneUnits / m_focalPlaneXRes); + } + + if (m_imageMetadata->exifInfo.FocalLength) + { + if (m_imageMetadata->exifInfo.FocalLength35mmEquiv == 0) + { + // Compute 35 mm equivalent focal length based on sensor geometry if we haven't + // already got it explicitly from a tag. + if (m_imageMetadata->exifInfo.CCDWidth != 0.0f) + { + m_imageMetadata->exifInfo.FocalLength35mmEquiv = static_cast( + (m_imageMetadata->exifInfo.FocalLength / m_imageMetadata->exifInfo.CCDWidth * 36 + + 0.5f)); + } + } + } +} + +void CImageMetadataParser::ExtractIPTC(Exiv2::IptcData& iptcData) +{ + for (auto it = iptcData.begin(); it != iptcData.end(); ++it) + { + const std::string iptcKey = it->key(); + if (iptcKey == "Iptc.Application2.RecordVersion") + { + m_imageMetadata->iptcInfo.RecordVersion = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.SuppCategory") + { + m_imageMetadata->iptcInfo.SupplementalCategories = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.Keywords") + { + if (m_imageMetadata->iptcInfo.Keywords.empty()) + { + m_imageMetadata->iptcInfo.Keywords = it->value().toString(); + } + else + { + m_imageMetadata->iptcInfo.Keywords += ", " + it->value().toString(); + } + } + else if (iptcKey == "Iptc.Application2.Caption") + { + m_imageMetadata->iptcInfo.Caption = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.Writer") + { + m_imageMetadata->iptcInfo.Author = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.Headline") + { + m_imageMetadata->iptcInfo.Headline = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.SpecialInstructions") + { + m_imageMetadata->iptcInfo.SpecialInstructions = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.Category") + { + m_imageMetadata->iptcInfo.Category = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.Byline") + { + m_imageMetadata->iptcInfo.Byline = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.BylineTitle") + { + m_imageMetadata->iptcInfo.BylineTitle = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.Credit") + { + m_imageMetadata->iptcInfo.Credit = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.Source") + { + m_imageMetadata->iptcInfo.Source = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.Copyright") + { + m_imageMetadata->iptcInfo.CopyrightNotice = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.ObjectName") + { + m_imageMetadata->iptcInfo.ObjectName = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.City") + { + m_imageMetadata->iptcInfo.City = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.ProvinceState") + { + m_imageMetadata->iptcInfo.State = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.CountryName") + { + m_imageMetadata->iptcInfo.Country = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.TransmissionReference") + { + m_imageMetadata->iptcInfo.TransmissionReference = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.Urgency") + { + m_imageMetadata->iptcInfo.Urgency = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.CountryCode") + { + m_imageMetadata->iptcInfo.CountryCode = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.ReferenceService") + { + m_imageMetadata->iptcInfo.ReferenceService = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.SubLocation") + { + m_imageMetadata->iptcInfo.SubLocation = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.ImageType") + { + m_imageMetadata->iptcInfo.ImageType = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.DateCreated") + { + m_imageMetadata->iptcInfo.Date = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.DateCreated") + { + m_imageMetadata->iptcInfo.Date = it->value().toString(); + } + else if (iptcKey == "Iptc.Application2.TimeCreated") + { + m_imageMetadata->iptcInfo.TimeCreated = it->value().toString(); + } + } +} diff --git a/xbmc/pictures/ImageMetadataParser.h b/xbmc/pictures/ImageMetadataParser.h new file mode 100644 index 0000000000000..513355cbb85ad --- /dev/null +++ b/xbmc/pictures/ImageMetadataParser.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 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 "ImageMetadata.h" + +#include +#include + +#include + +class CImageMetadataParser +{ +public: + ~CImageMetadataParser() = default; + + static std::unique_ptr ExtractMetadata(const std::string& picFileName); + +private: + CImageMetadataParser(); + void ExtractCommonMetadata(std::unique_ptr& image); + void ExtractExif(Exiv2::ExifData& exifData); + void ExtractIPTC(Exiv2::IptcData& iptcData); + + int m_imageWidth{0}; + float m_focalPlaneXRes{0.0}; + float m_focalPlaneUnits{0}; + std::unique_ptr m_imageMetadata; +}; diff --git a/xbmc/pictures/Iptc.h b/xbmc/pictures/Iptc.h new file mode 100644 index 0000000000000..71bb80a6c366f --- /dev/null +++ b/xbmc/pictures/Iptc.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 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 + +struct IPTCInfo +{ + IPTCInfo() = default; + IPTCInfo(const IPTCInfo&) = default; + IPTCInfo(IPTCInfo&&) = default; + + IPTCInfo& operator=(const IPTCInfo&) = default; + IPTCInfo& operator=(IPTCInfo&&) = default; + + std::string RecordVersion; + std::string SupplementalCategories; + std::string Keywords; + std::string Caption; + std::string Author; + std::string Headline; + std::string SpecialInstructions; + std::string Category; + std::string Byline; + std::string BylineTitle; + std::string Credit; + std::string Source; + std::string CopyrightNotice; + std::string ObjectName; + std::string City; + std::string State; + std::string Country; + std::string TransmissionReference; + std::string Date; + std::string Urgency; + std::string ReferenceService; + std::string CountryCode; + std::string TimeCreated; + std::string SubLocation; + std::string ImageType; +}; diff --git a/xbmc/pictures/IptcParse.cpp b/xbmc/pictures/IptcParse.cpp deleted file mode 100644 index dc66f2772d8c7..0000000000000 --- a/xbmc/pictures/IptcParse.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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. - */ - -//-------------------------------------------------------------------------- -// Module to pull IPTC information out of various types of digital images. -//-------------------------------------------------------------------------- - -//-------------------------------------------------------------------------- -// Process IPTC data. -//-------------------------------------------------------------------------- - -#include "IptcParse.h" - -#include "ExifParse.h" - -#ifdef TARGET_WINDOWS -#include -#else -#include -#endif - -#include - -#ifndef min -#define min(a,b) (a)>(b)?(b):(a) -#endif - -// Supported IPTC entry types -#define IPTC_RECORD_VERSION 0x00 -#define IPTC_SUPLEMENTAL_CATEGORIES 0x14 -#define IPTC_KEYWORDS 0x19 -#define IPTC_CAPTION 0x78 -#define IPTC_AUTHOR 0x7A -#define IPTC_HEADLINE 0x69 -#define IPTC_SPECIAL_INSTRUCTIONS 0x28 -#define IPTC_CATEGORY 0x0F -#define IPTC_BYLINE 0x50 -#define IPTC_BYLINE_TITLE 0x55 -#define IPTC_CREDIT 0x6E -#define IPTC_SOURCE 0x73 -#define IPTC_COPYRIGHT_NOTICE 0x74 -#define IPTC_OBJECT_NAME 0x05 -#define IPTC_CITY 0x5A -#define IPTC_STATE 0x5F -#define IPTC_COUNTRY 0x65 -#define IPTC_TRANSMISSION_REFERENCE 0x67 -#define IPTC_DATE 0x37 -#define IPTC_URGENCY 0x0A -#define IPTC_COUNTRY_CODE 0x64 -#define IPTC_REFERENCE_SERVICE 0x2D -#define IPTC_TIME_CREATED 0x3C -#define IPTC_SUB_LOCATION 0x5C -#define IPTC_IMAGE_TYPE 0x82 - - -//-------------------------------------------------------------------------- -// Process IPTC marker. Return FALSE if unable to process marker. -// -// IPTC block consists of: -// - Marker: 1 byte (0xED) -// - Block length: 2 bytes -// - IPTC Signature: 14 bytes ("Photoshop 3.0\0") -// - 8BIM Signature 4 bytes ("8BIM") -// - IPTC Block start 2 bytes (0x04, 0x04) -// - IPTC Header length 1 byte -// - IPTC header Header is padded to even length, counting the length byte -// - Length 4 bytes -// - IPTC Data which consists of a number of entries, each of which has the following format: -// - Signature 2 bytes (0x1C02) -// - Entry type 1 byte (for defined entry types, see #defines above) -// - entry length 2 bytes -// - entry data 'entry length' bytes -// -//-------------------------------------------------------------------------- -bool CIptcParse::Process (const unsigned char* const Data, const unsigned short itemlen, IPTCInfo_t *info) -{ - if (!info) return false; - - const char IptcSignature1[] = "Photoshop 3.0"; - const char IptcSignature2[] = "8BIM"; - const char IptcSignature3[] = {0x04, 0x04}; - - // Check IPTC signatures - const char* pos = (const char*)(Data + sizeof(short)); // position data pointer after length field - const char* maxpos = (const char*)(Data+itemlen); - unsigned char headerLen = 0; - unsigned char dataLen = 0; - memset(info, 0, sizeof(IPTCInfo_t)); - - if (itemlen < 25) return false; - - if (memcmp(pos, IptcSignature1, strlen(IptcSignature1)-1) != 0) return false; - pos += sizeof(IptcSignature1); // move data pointer to the next field - - if (memcmp(pos, IptcSignature2, strlen(IptcSignature2)-1) != 0) return false; - pos += sizeof(IptcSignature2)-1; // move data pointer to the next field - - while (memcmp(pos, IptcSignature3, sizeof(IptcSignature3)) != 0) { // loop on valid Photoshop blocks - - pos += sizeof(IptcSignature3); // move data pointer to the Header Length - // Skip header - headerLen = *pos; // get header length and move data pointer to the next field - pos += (headerLen & 0xfe) + 2; // move data pointer to the next field (Header is padded to even length, counting the length byte) - - pos += 3; // move data pointer to length, assume only one byte, TODO: use all 4 bytes - - dataLen = *pos++; - pos += dataLen; // skip data section - - if (memcmp(pos, IptcSignature2, sizeof(IptcSignature2) - 1) != 0) return false; - pos += sizeof(IptcSignature2) - 1; // move data pointer to the next field - } - - pos += sizeof(IptcSignature3); // move data pointer to the next field - if (pos >= maxpos) return false; - - // IPTC section found - - // Skip header - headerLen = *pos++; // get header length and move data pointer to the next field - pos += headerLen + 1 - (headerLen % 2); // move data pointer to the next field (Header is padded to even length, counting the length byte) - - if (pos + 4 >= maxpos) return false; - - pos += 4; // move data pointer to the next field - - // Now read IPTC data - while (pos < (const char*)(Data + itemlen-5)) - { - if (pos + 5 > maxpos) return false; - - short signature = (*pos << 8) + (*(pos+1)); - - pos += 2; - if (signature != 0x1C01 && signature != 0x1C02) - break; - - unsigned char type = *pos++; - unsigned short length = (*pos << 8) + (*(pos+1)); - pos += 2; // Skip tag length - - if (pos + length > maxpos) return false; - - // Process tag here - char *tag = NULL; - if (signature == 0x1C02) - { - switch (type) - { - case IPTC_RECORD_VERSION: tag = info->RecordVersion; break; - case IPTC_SUPLEMENTAL_CATEGORIES: tag = info->SupplementalCategories; break; - case IPTC_KEYWORDS: tag = info->Keywords; break; - case IPTC_CAPTION: tag = info->Caption; break; - case IPTC_AUTHOR: tag = info->Author; break; - case IPTC_HEADLINE: tag = info->Headline; break; - case IPTC_SPECIAL_INSTRUCTIONS: tag = info->SpecialInstructions; break; - case IPTC_CATEGORY: tag = info->Category; break; - case IPTC_BYLINE: tag = info->Byline; break; - case IPTC_BYLINE_TITLE: tag = info->BylineTitle; break; - case IPTC_CREDIT: tag = info->Credit; break; - case IPTC_SOURCE: tag = info->Source; break; - case IPTC_COPYRIGHT_NOTICE: tag = info->CopyrightNotice; break; - case IPTC_OBJECT_NAME: tag = info->ObjectName; break; - case IPTC_CITY: tag = info->City; break; - case IPTC_STATE: tag = info->State; break; - case IPTC_COUNTRY: tag = info->Country; break; - case IPTC_TRANSMISSION_REFERENCE: tag = info->TransmissionReference; break; - case IPTC_DATE: tag = info->Date; break; - case IPTC_URGENCY: tag = info->Urgency; break; - case IPTC_REFERENCE_SERVICE: tag = info->ReferenceService; break; - case IPTC_COUNTRY_CODE: tag = info->CountryCode; break; - case IPTC_TIME_CREATED: tag = info->TimeCreated; break; - case IPTC_SUB_LOCATION: tag = info->SubLocation; break; - case IPTC_IMAGE_TYPE: tag = info->ImageType; break; - default: - printf("IptcParse: Unrecognised IPTC tag: 0x%02x", type); - break; - } - } - - if (tag) - { - if (type != IPTC_KEYWORDS || *tag == 0) - { - strncpy(tag, pos, min(length, MAX_IPTC_STRING - 1)); - tag[min(length, MAX_IPTC_STRING - 1)] = 0; - } - else if (type == IPTC_KEYWORDS) - { - // there may be multiple keywords - lets join them - size_t maxLen = MAX_IPTC_STRING - strlen(tag); - if (maxLen > 2) - { - strcat(tag, ", "); - strncat(tag, pos, min(length, maxLen - 3)); - } - } -/* if (id == SLIDESHOW_IPTC_CAPTION) - { - CExifParse::FixComment(m_IptcInfo[id]); // Ensure comment is printable - }*/ - } - pos += length; - } - return true; -} - diff --git a/xbmc/pictures/IptcParse.h b/xbmc/pictures/IptcParse.h deleted file mode 100644 index ba001358bdb13..0000000000000 --- a/xbmc/pictures/IptcParse.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "libexif.h" - -class CIptcParse -{ - public: - static bool Process(const unsigned char* const Data, const unsigned short length, IPTCInfo_t *info); -}; - diff --git a/xbmc/pictures/JpegParse.cpp b/xbmc/pictures/JpegParse.cpp deleted file mode 100644 index 718ac5426a8fe..0000000000000 --- a/xbmc/pictures/JpegParse.cpp +++ /dev/null @@ -1,316 +0,0 @@ -/* - * 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. - */ - -//-------------------------------------------------------------------------- -// This module gathers information about a digital image file. This includes: -// - File name and path -// - File size -// - Resolution (if available) -// - IPTC information (if available) -// - EXIF information (if available) -// All gathered information is stored in a vector of 'description' and 'value' -// pairs (where both description and value fields are of CStdString types). -//-------------------------------------------------------------------------- - -#include "JpegParse.h" - -#include "filesystem/File.h" - -#ifdef TARGET_WINDOWS -#include -#else -#include -#include -typedef unsigned char BYTE; -#endif - -#ifndef min -#define min(a,b) (a)>(b)?(b):(a) -#endif - -using namespace XFILE; - -//-------------------------------------------------------------------------- -#define JPEG_PARSE_STRING_ID_BASE 21500 -enum { - ProcessUnknown = JPEG_PARSE_STRING_ID_BASE, - ProcessSof0, - ProcessSof1, - ProcessSof2, - ProcessSof3, - ProcessSof5, - ProcessSof6, - ProcessSof7, - ProcessSof9, - ProcessSof10, - ProcessSof11, - ProcessSof13, - ProcessSof14, - ProcessSof15, -}; - - - - -//-------------------------------------------------------------------------- -// Constructor -//-------------------------------------------------------------------------- -CJpegParse::CJpegParse(): - m_SectionBuffer(NULL) -{ - memset(&m_ExifInfo, 0, sizeof(m_ExifInfo)); - memset(&m_IPTCInfo, 0, sizeof(m_IPTCInfo)); -} - -//-------------------------------------------------------------------------- -// Process a SOFn marker. This is useful for the image dimensions -//-------------------------------------------------------------------------- -void CJpegParse::ProcessSOFn (void) -{ - m_ExifInfo.Height = CExifParse::Get16(m_SectionBuffer+3); - m_ExifInfo.Width = CExifParse::Get16(m_SectionBuffer+5); - - unsigned char num_components = m_SectionBuffer[7]; - if (num_components != 3) - { - m_ExifInfo.IsColor = 0; - } - else - { - m_ExifInfo.IsColor = 1; - } -} - - -//-------------------------------------------------------------------------- -// Read a section from a JPEG file. Note that this function allocates memory. -// It must be called in pair with ReleaseSection -//-------------------------------------------------------------------------- -bool CJpegParse::GetSection (CFile& infile, const unsigned short sectionLength) -{ - if (sectionLength < 2) - { - printf("JpgParse: invalid section length"); - return false; - } - - m_SectionBuffer = new unsigned char[sectionLength]; - if (m_SectionBuffer == NULL) - { - printf("JpgParse: could not allocate memory"); - return false; - } - // Store first two pre-read bytes. - m_SectionBuffer[0] = (unsigned char)(sectionLength >> 8); - m_SectionBuffer[1] = (unsigned char)(sectionLength & 0x00FF); - - unsigned int len = (unsigned int)sectionLength; - - size_t bytesRead = infile.Read(m_SectionBuffer+sizeof(sectionLength), len-sizeof(sectionLength)); - if (bytesRead != sectionLength-sizeof(sectionLength)) - { - printf("JpgParse: premature end of file?"); - ReleaseSection(); - return false; - } - return true; -} - -//-------------------------------------------------------------------------- -// Deallocate memory allocated in GetSection. This function must always -// be paired by a preceding GetSection call. -//-------------------------------------------------------------------------- -void CJpegParse::ReleaseSection (void) -{ - delete[] m_SectionBuffer; - m_SectionBuffer = NULL; -} - -//-------------------------------------------------------------------------- -// Parse the marker stream until SOS or EOI is seen; infile has already been -// successfully open -//-------------------------------------------------------------------------- -bool CJpegParse::ExtractInfo (CFile& infile) -{ - // Get file marker (two bytes - must be 0xFFD8 for JPEG files - BYTE a; - size_t bytesRead = infile.Read(&a, sizeof(BYTE)); - if ((bytesRead != sizeof(BYTE)) || (a != 0xFF)) - { - return false; - } - bytesRead = infile.Read(&a, sizeof(BYTE)); - if ((bytesRead != sizeof(BYTE)) || (a != M_SOI)) - { - return false; - } - - for(;;) - { - BYTE marker = 0; - for (a=0; a<7; a++) { - bytesRead = infile.Read(&marker, sizeof(BYTE)); - if (marker != 0xFF) - break; - - if (a >= 6) - { - printf("JpgParse: too many padding bytes"); - return false; - } - marker = 0; - } - - // Read the length of the section. - unsigned short itemlen = 0; - bytesRead = infile.Read(&itemlen, sizeof(itemlen)); - itemlen = CExifParse::Get16(&itemlen); - - if ((bytesRead != sizeof(itemlen)) || (itemlen < sizeof(itemlen))) - { - printf("JpgParse: invalid marker"); - return false; - } - - switch(marker) - { - case M_SOS: // stop before hitting compressed data - return true; - - case M_EOI: // in case it's a tables-only JPEG stream - printf("JpgParse: No image in jpeg!"); - return false; - break; - - case M_COM: // Comment section - GetSection(infile, itemlen); - if (m_SectionBuffer != NULL) - { - // CExifParse::FixComment(comment); // Ensure comment is printable - unsigned short length = min(itemlen - 2, MAX_COMMENT); - strncpy(m_ExifInfo.FileComment, (char *)&m_SectionBuffer[2], length); - m_ExifInfo.FileComment[length] = '\0'; - } - ReleaseSection(); - break; - - case M_SOF0: - case M_SOF1: - case M_SOF2: - case M_SOF3: - case M_SOF5: - case M_SOF6: - case M_SOF7: - case M_SOF9: - case M_SOF10: - case M_SOF11: - case M_SOF13: - case M_SOF14: - case M_SOF15: - GetSection(infile, itemlen); - if ((m_SectionBuffer != NULL) && (itemlen >= 7)) - { - ProcessSOFn(); - m_ExifInfo.Process = marker; - } - ReleaseSection(); - break; - - case M_IPTC: - GetSection(infile, itemlen); - if (m_SectionBuffer != NULL) - { - CIptcParse::Process(m_SectionBuffer, itemlen, &m_IPTCInfo); - } - ReleaseSection(); - break; - - case M_EXIF: - // Seen files from some 'U-lead' software with Vivitar scanner - // that uses marker 31 for non exif stuff. Thus make sure - // it says 'Exif' in the section before treating it as exif. - GetSection(infile, itemlen); - if (m_SectionBuffer != NULL) - { - CExifParse exif; - exif.Process(m_SectionBuffer, itemlen, &m_ExifInfo); - } - ReleaseSection(); - break; - - case M_JFIF: - // Regular jpegs always have this tag, exif images have the exif - // marker instead, although ACDsee will write images with both markers. - // this program will re-create this marker on absence of exif marker. - // hence no need to keep the copy from the file. - // fall through to default case - default: - // Skip any other sections. - GetSection(infile, itemlen); - ReleaseSection(); - break; - } - } - return true; -} - -//-------------------------------------------------------------------------- -// Process a file. Check if it is JPEG. Extract exif/iptc info if it is. -//-------------------------------------------------------------------------- -bool CJpegParse::Process (const char *picFileName) -{ - CFile file; - - if (!file.Open(picFileName)) - return false; - - // File exists and successfully opened. Start processing - // Gather all information about the file - -/* // Get file name... - CStdString tmp, urlFName, path; - CURL url(picFileName); - url.GetURLWithoutUserDetails(urlFName); - CUtil::Split(urlFName, path, tmp); - m_JpegInfo[SLIDESHOW_FILE_NAME] = tmp; - // ...then path... - m_JpegInfo[SLIDESHOW_FILE_PATH] = path; - - // ...then size... - __stat64 fileStat; - CFile::Stat(picFileName, &fileStat); - float fileSize = (float)fileStat.st_size; - tmp = ""; - if (fileSize > 1024) - { - fileSize /= 1024; - tmp = "KB"; - } - if (fileSize > 1024) - { - fileSize /= 1024; - tmp = "MB"; - } - if (fileSize > 1024) - { - fileSize /= 1024; - tmp = "GB"; - } - tmp.Format("%.2f %s", fileSize, tmp); - m_JpegInfo[SLIDESHOW_FILE_SIZE] = tmp; - - // ...then date and time... - CDateTime date((time_t)fileStat.st_mtime); - tmp.Format("%s %s", date.GetAsLocalizedDate(), date.GetAsLocalizedTime()); - m_JpegInfo[SLIDESHOW_FILE_DATE] = tmp;*/ - - bool result = ExtractInfo(file); - file.Close(); - return result; -} - diff --git a/xbmc/pictures/JpegParse.h b/xbmc/pictures/JpegParse.h deleted file mode 100644 index 11d2ca62f3755..0000000000000 --- a/xbmc/pictures/JpegParse.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include "ExifParse.h" -#include "IptcParse.h" - -#include - -//-------------------------------------------------------------------------- -// JPEG markers consist of one or more 0xFF bytes, followed by a marker -// code byte (which is not an FF). Here are the marker codes of interest -// in this application. -//-------------------------------------------------------------------------- - -#define M_SOF0 0xC0 // Start Of Frame N -#define M_SOF1 0xC1 // N indicates which compression process -#define M_SOF2 0xC2 // Only SOF0-SOF2 are now in common use -#define M_SOF3 0xC3 -#define M_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers -#define M_SOF6 0xC6 -#define M_SOF7 0xC7 -#define M_SOF9 0xC9 -#define M_SOF10 0xCA -#define M_SOF11 0xCB -#define M_SOF13 0xCD -#define M_SOF14 0xCE -#define M_SOF15 0xCF -#define M_SOI 0xD8 // Start Of Image (beginning of datastream) -#define M_EOI 0xD9 // End Of Image (end of datastream) -#define M_SOS 0xDA // Start Of Scan (begins compressed data) -#define M_JFIF 0xE0 // Jfif marker -#define M_EXIF 0xE1 // Exif marker -#define M_COM 0xFE // COMment -#define M_DQT 0xDB -#define M_DHT 0xC4 -#define M_DRI 0xDD -#define M_IPTC 0xED // IPTC marker - -namespace XFILE -{ - class CFile; -} - - -class CJpegParse -{ - public: - CJpegParse(); - ~CJpegParse(void) = default; - bool Process(const char *picFileName); - const ExifInfo_t* GetExifInfo() const { return &m_ExifInfo; } - const IPTCInfo_t* GetIptcInfo() const { return &m_IPTCInfo; } - - private: - bool ExtractInfo(XFILE::CFile& infile); - bool GetSection(XFILE::CFile& infile, const unsigned short sectionLength); - void ReleaseSection(void); - void ProcessSOFn(void); - - unsigned char* m_SectionBuffer; - ExifInfo_t m_ExifInfo; - IPTCInfo_t m_IPTCInfo; -}; - diff --git a/xbmc/pictures/PictureInfoTag.cpp b/xbmc/pictures/PictureInfoTag.cpp index 4aab3e0b190c2..6633d0fcb550e 100644 --- a/xbmc/pictures/PictureInfoTag.cpp +++ b/xbmc/pictures/PictureInfoTag.cpp @@ -8,6 +8,7 @@ #include "PictureInfoTag.h" +#include "ImageMetadataParser.h" #include "ServiceBroker.h" #include "addons/ExtsMimeSupportList.h" #include "addons/ImageDecoder.h" @@ -24,103 +25,9 @@ using namespace KODI::ADDONS; -CPictureInfoTag::ExifInfo::ExifInfo(const ExifInfo_t& other) - : CameraMake(other.CameraMake), - CameraModel(other.CameraModel), - DateTime(other.DateTime), - Height(other.Height), - Width(other.Width), - Orientation(other.Orientation), - IsColor(other.IsColor), - Process(other.Process), - FlashUsed(other.FlashUsed), - FocalLength(other.FocalLength), - ExposureTime(other.ExposureTime), - ApertureFNumber(other.ApertureFNumber), - Distance(other.Distance), - CCDWidth(other.CCDWidth), - ExposureBias(other.ExposureBias), - DigitalZoomRatio(other.DigitalZoomRatio), - FocalLength35mmEquiv(other.FocalLength35mmEquiv), - Whitebalance(other.Whitebalance), - MeteringMode(other.MeteringMode), - ExposureProgram(other.ExposureProgram), - ExposureMode(other.ExposureMode), - ISOequivalent(other.ISOequivalent), - LightSource(other.LightSource), - CommentsCharset(EXIF_COMMENT_CHARSET_CONVERTED), - XPCommentsCharset(EXIF_COMMENT_CHARSET_CONVERTED), - Comments(Convert(other.CommentsCharset, other.Comments)), - FileComment(Convert(EXIF_COMMENT_CHARSET_UNKNOWN, other.FileComment)), - XPComment(Convert(other.XPCommentsCharset, other.XPComment)), - Description(other.Description), - ThumbnailOffset(other.ThumbnailOffset), - ThumbnailSize(other.ThumbnailSize), - LargestExifOffset(other.LargestExifOffset), - ThumbnailAtEnd(other.ThumbnailAtEnd), - ThumbnailSizeOffset(other.ThumbnailSizeOffset), - DateTimeOffsets(other.DateTimeOffsets, other.DateTimeOffsets + other.numDateTimeTags), - GpsInfoPresent(other.GpsInfoPresent), - GpsLat(other.GpsLat), - GpsLong(other.GpsLong), - GpsAlt(other.GpsAlt) -{ -} - -std::string CPictureInfoTag::ExifInfo::Convert(int charset, const char* data) -{ - std::string value; - - // The charset used for the UserComment is stored in CommentsCharset: - // Ascii, Unicode (UCS2), JIS (X208-1990), Unknown (application specific) - if (charset == EXIF_COMMENT_CHARSET_UNICODE) - { - g_charsetConverter.ucs2ToUTF8(std::u16string(reinterpret_cast(data)), value); - } - else - { - // Ascii doesn't need to be converted (EXIF_COMMENT_CHARSET_ASCII) - // Unknown data can't be converted as it could be any codec (EXIF_COMMENT_CHARSET_UNKNOWN) - // JIS data can't be converted as CharsetConverter and iconv lacks support (EXIF_COMMENT_CHARSET_JIS) - g_charsetConverter.unknownToUTF8(data, value); - } - - return value; -} - -CPictureInfoTag::IPTCInfo::IPTCInfo(const IPTCInfo_t& other) - : RecordVersion(other.RecordVersion), - SupplementalCategories(other.SupplementalCategories), - Keywords(other.Keywords), - Caption(other.Caption), - Author(other.Author), - Headline(other.Headline), - SpecialInstructions(other.SpecialInstructions), - Category(other.Category), - Byline(other.Byline), - BylineTitle(other.BylineTitle), - Credit(other.Credit), - Source(other.Source), - CopyrightNotice(other.CopyrightNotice), - ObjectName(other.ObjectName), - City(other.City), - State(other.State), - Country(other.Country), - TransmissionReference(other.TransmissionReference), - Date(other.Date), - Urgency(other.Urgency), - ReferenceService(other.ReferenceService), - CountryCode(other.CountryCode), - TimeCreated(other.TimeCreated), - SubLocation(other.SubLocation), - ImageType(other.ImageType) -{ -} - void CPictureInfoTag::Reset() { - m_exifInfo = {}; - m_iptcInfo = {}; + m_imageMetadata = {}; m_isLoaded = false; m_isInfoSetExternally = false; m_dateTimeTaken.Reset(); @@ -155,13 +62,10 @@ bool CPictureInfoTag::Load(const std::string &path) // Load by Kodi's included own way if (!m_isLoaded) { - ExifInfo_t exifInfo; - IPTCInfo_t iptcInfo; - - if (process_jpeg(path.c_str(), &exifInfo, &iptcInfo)) + std::unique_ptr metadata = CImageMetadataParser::ExtractMetadata(path); + if (metadata) { - m_exifInfo = ExifInfo(exifInfo); - m_iptcInfo = IPTCInfo(iptcInfo); + m_imageMetadata = *(metadata.release()); m_isLoaded = true; } } @@ -177,223 +81,224 @@ void CPictureInfoTag::Archive(CArchive& ar) { ar << m_isLoaded; ar << m_isInfoSetExternally; - ar << m_exifInfo.ApertureFNumber; - ar << m_exifInfo.CameraMake; - ar << m_exifInfo.CameraModel; - ar << m_exifInfo.CCDWidth; - ar << m_exifInfo.Comments; - ar << m_exifInfo.Description; - ar << m_exifInfo.DateTime; + ar << m_imageMetadata.exifInfo.ApertureFNumber; + ar << m_imageMetadata.exifInfo.CameraMake; + ar << m_imageMetadata.exifInfo.CameraModel; + ar << m_imageMetadata.exifInfo.CCDWidth; + ar << m_imageMetadata.exifInfo.Comments; + ar << m_imageMetadata.exifInfo.Description; + ar << m_imageMetadata.exifInfo.DateTime; for (std::vector::size_type i = 0; i < MAX_DATE_COPIES; ++i) { - if (i < m_exifInfo.DateTimeOffsets.size()) - ar << m_exifInfo.DateTimeOffsets[i]; + if (i < m_imageMetadata.exifInfo.DateTimeOffsets.size()) + ar << m_imageMetadata.exifInfo.DateTimeOffsets[i]; else ar << static_cast(0); } - ar << m_exifInfo.DigitalZoomRatio; - ar << m_exifInfo.Distance; - ar << m_exifInfo.ExposureBias; - ar << m_exifInfo.ExposureMode; - ar << m_exifInfo.ExposureProgram; - ar << m_exifInfo.ExposureTime; - ar << m_exifInfo.FlashUsed; - ar << m_exifInfo.FocalLength; - ar << m_exifInfo.FocalLength35mmEquiv; - ar << m_exifInfo.GpsInfoPresent; - ar << m_exifInfo.GpsAlt; - ar << m_exifInfo.GpsLat; - ar << m_exifInfo.GpsLong; - ar << m_exifInfo.Height; - ar << m_exifInfo.IsColor; - ar << m_exifInfo.ISOequivalent; - ar << m_exifInfo.LargestExifOffset; - ar << m_exifInfo.LightSource; - ar << m_exifInfo.MeteringMode; - ar << static_cast(m_exifInfo.DateTimeOffsets.size()); - ar << m_exifInfo.Orientation; - ar << m_exifInfo.Process; - ar << m_exifInfo.ThumbnailAtEnd; - ar << m_exifInfo.ThumbnailOffset; - ar << m_exifInfo.ThumbnailSize; - ar << m_exifInfo.ThumbnailSizeOffset; - ar << m_exifInfo.Whitebalance; - ar << m_exifInfo.Width; + ar << m_imageMetadata.exifInfo.DigitalZoomRatio; + ar << m_imageMetadata.exifInfo.Distance; + ar << m_imageMetadata.exifInfo.ExposureBias; + ar << m_imageMetadata.exifInfo.ExposureMode; + ar << m_imageMetadata.exifInfo.ExposureProgram; + ar << m_imageMetadata.exifInfo.ExposureTime; + ar << m_imageMetadata.exifInfo.FlashUsed; + ar << m_imageMetadata.exifInfo.FocalLength; + ar << m_imageMetadata.exifInfo.FocalLength35mmEquiv; + ar << m_imageMetadata.exifInfo.GpsInfoPresent; + ar << m_imageMetadata.exifInfo.GpsAlt; + ar << m_imageMetadata.exifInfo.GpsLat; + ar << m_imageMetadata.exifInfo.GpsLong; + ar << m_imageMetadata.exifInfo.Height; + ar << m_imageMetadata.exifInfo.IsColor; + ar << m_imageMetadata.exifInfo.ISOequivalent; + ar << m_imageMetadata.exifInfo.LargestExifOffset; + ar << m_imageMetadata.exifInfo.LightSource; + ar << m_imageMetadata.exifInfo.MeteringMode; + ar << static_cast(m_imageMetadata.exifInfo.DateTimeOffsets.size()); + ar << m_imageMetadata.exifInfo.Orientation; + ar << m_imageMetadata.exifInfo.Process; + ar << m_imageMetadata.exifInfo.ThumbnailAtEnd; + ar << m_imageMetadata.exifInfo.ThumbnailOffset; + ar << m_imageMetadata.exifInfo.ThumbnailSize; + ar << m_imageMetadata.exifInfo.ThumbnailSizeOffset; + ar << m_imageMetadata.exifInfo.Whitebalance; + ar << m_imageMetadata.exifInfo.Width; ar << m_dateTimeTaken; - ar << m_iptcInfo.Author; - ar << m_iptcInfo.Byline; - ar << m_iptcInfo.BylineTitle; - ar << m_iptcInfo.Caption; - ar << m_iptcInfo.Category; - ar << m_iptcInfo.City; - ar << m_iptcInfo.Urgency; - ar << m_iptcInfo.CopyrightNotice; - ar << m_iptcInfo.Country; - ar << m_iptcInfo.CountryCode; - ar << m_iptcInfo.Credit; - ar << m_iptcInfo.Date; - ar << m_iptcInfo.Headline; - ar << m_iptcInfo.Keywords; - ar << m_iptcInfo.ObjectName; - ar << m_iptcInfo.ReferenceService; - ar << m_iptcInfo.Source; - ar << m_iptcInfo.SpecialInstructions; - ar << m_iptcInfo.State; - ar << m_iptcInfo.SupplementalCategories; - ar << m_iptcInfo.TransmissionReference; - ar << m_iptcInfo.TimeCreated; - ar << m_iptcInfo.SubLocation; - ar << m_iptcInfo.ImageType; + ar << m_imageMetadata.iptcInfo.Author; + ar << m_imageMetadata.iptcInfo.Byline; + ar << m_imageMetadata.iptcInfo.BylineTitle; + ar << m_imageMetadata.iptcInfo.Caption; + ar << m_imageMetadata.iptcInfo.Category; + ar << m_imageMetadata.iptcInfo.City; + ar << m_imageMetadata.iptcInfo.Urgency; + ar << m_imageMetadata.iptcInfo.CopyrightNotice; + ar << m_imageMetadata.iptcInfo.Country; + ar << m_imageMetadata.iptcInfo.CountryCode; + ar << m_imageMetadata.iptcInfo.Credit; + ar << m_imageMetadata.iptcInfo.Date; + ar << m_imageMetadata.iptcInfo.Headline; + ar << m_imageMetadata.iptcInfo.Keywords; + ar << m_imageMetadata.iptcInfo.ObjectName; + ar << m_imageMetadata.iptcInfo.ReferenceService; + ar << m_imageMetadata.iptcInfo.Source; + ar << m_imageMetadata.iptcInfo.SpecialInstructions; + ar << m_imageMetadata.iptcInfo.State; + ar << m_imageMetadata.iptcInfo.SupplementalCategories; + ar << m_imageMetadata.iptcInfo.TransmissionReference; + ar << m_imageMetadata.iptcInfo.TimeCreated; + ar << m_imageMetadata.iptcInfo.SubLocation; + ar << m_imageMetadata.iptcInfo.ImageType; } else { ar >> m_isLoaded; ar >> m_isInfoSetExternally; - ar >> m_exifInfo.ApertureFNumber; - ar >> m_exifInfo.CameraMake; - ar >> m_exifInfo.CameraModel; - ar >> m_exifInfo.CCDWidth; - ar >> m_exifInfo.Comments; - m_exifInfo.CommentsCharset = EXIF_COMMENT_CHARSET_CONVERTED; // Store and restore the comment charset converted - ar >> m_exifInfo.Description; - ar >> m_exifInfo.DateTime; - m_exifInfo.DateTimeOffsets.clear(); - m_exifInfo.DateTimeOffsets.reserve(MAX_DATE_COPIES); + ar >> m_imageMetadata.exifInfo.ApertureFNumber; + ar >> m_imageMetadata.exifInfo.CameraMake; + ar >> m_imageMetadata.exifInfo.CameraModel; + ar >> m_imageMetadata.exifInfo.CCDWidth; + ar >> m_imageMetadata.exifInfo.Comments; + m_imageMetadata.exifInfo.CommentsCharset = + EXIF_COMMENT_CHARSET_CONVERTED; // Store and restore the comment charset converted + ar >> m_imageMetadata.exifInfo.Description; + ar >> m_imageMetadata.exifInfo.DateTime; + m_imageMetadata.exifInfo.DateTimeOffsets.clear(); + m_imageMetadata.exifInfo.DateTimeOffsets.reserve(MAX_DATE_COPIES); for (std::vector::size_type i = 0; i < MAX_DATE_COPIES; ++i) { int dateTimeOffset; ar >> dateTimeOffset; - m_exifInfo.DateTimeOffsets.push_back(dateTimeOffset); + m_imageMetadata.exifInfo.DateTimeOffsets.push_back(dateTimeOffset); } - ar >> m_exifInfo.DigitalZoomRatio; - ar >> m_exifInfo.Distance; - ar >> m_exifInfo.ExposureBias; - ar >> m_exifInfo.ExposureMode; - ar >> m_exifInfo.ExposureProgram; - ar >> m_exifInfo.ExposureTime; - ar >> m_exifInfo.FlashUsed; - ar >> m_exifInfo.FocalLength; - ar >> m_exifInfo.FocalLength35mmEquiv; - ar >> m_exifInfo.GpsInfoPresent; - ar >> m_exifInfo.GpsAlt; - ar >> m_exifInfo.GpsLat; - ar >> m_exifInfo.GpsLong; - ar >> m_exifInfo.Height; - ar >> m_exifInfo.IsColor; - ar >> m_exifInfo.ISOequivalent; - ar >> m_exifInfo.LargestExifOffset; - ar >> m_exifInfo.LightSource; - ar >> m_exifInfo.MeteringMode; + ar >> m_imageMetadata.exifInfo.DigitalZoomRatio; + ar >> m_imageMetadata.exifInfo.Distance; + ar >> m_imageMetadata.exifInfo.ExposureBias; + ar >> m_imageMetadata.exifInfo.ExposureMode; + ar >> m_imageMetadata.exifInfo.ExposureProgram; + ar >> m_imageMetadata.exifInfo.ExposureTime; + ar >> m_imageMetadata.exifInfo.FlashUsed; + ar >> m_imageMetadata.exifInfo.FocalLength; + ar >> m_imageMetadata.exifInfo.FocalLength35mmEquiv; + ar >> m_imageMetadata.exifInfo.GpsInfoPresent; + ar >> m_imageMetadata.exifInfo.GpsAlt; + ar >> m_imageMetadata.exifInfo.GpsLat; + ar >> m_imageMetadata.exifInfo.GpsLong; + ar >> m_imageMetadata.exifInfo.Height; + ar >> m_imageMetadata.exifInfo.IsColor; + ar >> m_imageMetadata.exifInfo.ISOequivalent; + ar >> m_imageMetadata.exifInfo.LargestExifOffset; + ar >> m_imageMetadata.exifInfo.LightSource; + ar >> m_imageMetadata.exifInfo.MeteringMode; int numDateTimeTags; ar >> numDateTimeTags; - m_exifInfo.DateTimeOffsets.resize(numDateTimeTags); - ar >> m_exifInfo.Orientation; - ar >> m_exifInfo.Process; - ar >> m_exifInfo.ThumbnailAtEnd; - ar >> m_exifInfo.ThumbnailOffset; - ar >> m_exifInfo.ThumbnailSize; - ar >> m_exifInfo.ThumbnailSizeOffset; - ar >> m_exifInfo.Whitebalance; - ar >> m_exifInfo.Width; + m_imageMetadata.exifInfo.DateTimeOffsets.resize(numDateTimeTags); + ar >> m_imageMetadata.exifInfo.Orientation; + ar >> m_imageMetadata.exifInfo.Process; + ar >> m_imageMetadata.exifInfo.ThumbnailAtEnd; + ar >> m_imageMetadata.exifInfo.ThumbnailOffset; + ar >> m_imageMetadata.exifInfo.ThumbnailSize; + ar >> m_imageMetadata.exifInfo.ThumbnailSizeOffset; + ar >> m_imageMetadata.exifInfo.Whitebalance; + ar >> m_imageMetadata.exifInfo.Width; ar >> m_dateTimeTaken; - ar >> m_iptcInfo.Author; - ar >> m_iptcInfo.Byline; - ar >> m_iptcInfo.BylineTitle; - ar >> m_iptcInfo.Caption; - ar >> m_iptcInfo.Category; - ar >> m_iptcInfo.City; - ar >> m_iptcInfo.Urgency; - ar >> m_iptcInfo.CopyrightNotice; - ar >> m_iptcInfo.Country; - ar >> m_iptcInfo.CountryCode; - ar >> m_iptcInfo.Credit; - ar >> m_iptcInfo.Date; - ar >> m_iptcInfo.Headline; - ar >> m_iptcInfo.Keywords; - ar >> m_iptcInfo.ObjectName; - ar >> m_iptcInfo.ReferenceService; - ar >> m_iptcInfo.Source; - ar >> m_iptcInfo.SpecialInstructions; - ar >> m_iptcInfo.State; - ar >> m_iptcInfo.SupplementalCategories; - ar >> m_iptcInfo.TransmissionReference; - ar >> m_iptcInfo.TimeCreated; - ar >> m_iptcInfo.SubLocation; - ar >> m_iptcInfo.ImageType; + ar >> m_imageMetadata.iptcInfo.Author; + ar >> m_imageMetadata.iptcInfo.Byline; + ar >> m_imageMetadata.iptcInfo.BylineTitle; + ar >> m_imageMetadata.iptcInfo.Caption; + ar >> m_imageMetadata.iptcInfo.Category; + ar >> m_imageMetadata.iptcInfo.City; + ar >> m_imageMetadata.iptcInfo.Urgency; + ar >> m_imageMetadata.iptcInfo.CopyrightNotice; + ar >> m_imageMetadata.iptcInfo.Country; + ar >> m_imageMetadata.iptcInfo.CountryCode; + ar >> m_imageMetadata.iptcInfo.Credit; + ar >> m_imageMetadata.iptcInfo.Date; + ar >> m_imageMetadata.iptcInfo.Headline; + ar >> m_imageMetadata.iptcInfo.Keywords; + ar >> m_imageMetadata.iptcInfo.ObjectName; + ar >> m_imageMetadata.iptcInfo.ReferenceService; + ar >> m_imageMetadata.iptcInfo.Source; + ar >> m_imageMetadata.iptcInfo.SpecialInstructions; + ar >> m_imageMetadata.iptcInfo.State; + ar >> m_imageMetadata.iptcInfo.SupplementalCategories; + ar >> m_imageMetadata.iptcInfo.TransmissionReference; + ar >> m_imageMetadata.iptcInfo.TimeCreated; + ar >> m_imageMetadata.iptcInfo.SubLocation; + ar >> m_imageMetadata.iptcInfo.ImageType; } } void CPictureInfoTag::Serialize(CVariant& value) const { - value["aperturefnumber"] = m_exifInfo.ApertureFNumber; - value["cameramake"] = m_exifInfo.CameraMake; - value["cameramodel"] = m_exifInfo.CameraModel; - value["ccdwidth"] = m_exifInfo.CCDWidth; - value["comments"] = m_exifInfo.Comments; - value["description"] = m_exifInfo.Description; - value["datetime"] = m_exifInfo.DateTime; + value["aperturefnumber"] = m_imageMetadata.exifInfo.ApertureFNumber; + value["cameramake"] = m_imageMetadata.exifInfo.CameraMake; + value["cameramodel"] = m_imageMetadata.exifInfo.CameraModel; + value["ccdwidth"] = m_imageMetadata.exifInfo.CCDWidth; + value["comments"] = m_imageMetadata.exifInfo.Comments; + value["description"] = m_imageMetadata.exifInfo.Description; + value["datetime"] = m_imageMetadata.exifInfo.DateTime; for (std::vector::size_type i = 0; i < MAX_DATE_COPIES; ++i) { - if (i < m_exifInfo.DateTimeOffsets.size()) - value["datetimeoffsets"][static_cast(i)] = m_exifInfo.DateTimeOffsets[i]; + if (i < m_imageMetadata.exifInfo.DateTimeOffsets.size()) + value["datetimeoffsets"][static_cast(i)] = m_imageMetadata.exifInfo.DateTimeOffsets[i]; else value["datetimeoffsets"][static_cast(i)] = static_cast(0); } - value["digitalzoomratio"] = m_exifInfo.DigitalZoomRatio; - value["distance"] = m_exifInfo.Distance; - value["exposurebias"] = m_exifInfo.ExposureBias; - value["exposuremode"] = m_exifInfo.ExposureMode; - value["exposureprogram"] = m_exifInfo.ExposureProgram; - value["exposuretime"] = m_exifInfo.ExposureTime; - value["flashused"] = m_exifInfo.FlashUsed; - value["focallength"] = m_exifInfo.FocalLength; - value["focallength35mmequiv"] = m_exifInfo.FocalLength35mmEquiv; - value["gpsinfopresent"] = m_exifInfo.GpsInfoPresent; - value["gpsinfo"]["alt"] = m_exifInfo.GpsAlt; - value["gpsinfo"]["lat"] = m_exifInfo.GpsLat; - value["gpsinfo"]["long"] = m_exifInfo.GpsLong; - value["height"] = m_exifInfo.Height; - value["iscolor"] = m_exifInfo.IsColor; - value["isoequivalent"] = m_exifInfo.ISOequivalent; - value["largestexifoffset"] = m_exifInfo.LargestExifOffset; - value["lightsource"] = m_exifInfo.LightSource; - value["meteringmode"] = m_exifInfo.MeteringMode; - value["numdatetimetags"] = static_cast(m_exifInfo.DateTimeOffsets.size()); - value["orientation"] = m_exifInfo.Orientation; - value["process"] = m_exifInfo.Process; - value["thumbnailatend"] = m_exifInfo.ThumbnailAtEnd; - value["thumbnailoffset"] = m_exifInfo.ThumbnailOffset; - value["thumbnailsize"] = m_exifInfo.ThumbnailSize; - value["thumbnailsizeoffset"] = m_exifInfo.ThumbnailSizeOffset; - value["whitebalance"] = m_exifInfo.Whitebalance; - value["width"] = m_exifInfo.Width; + value["digitalzoomratio"] = m_imageMetadata.exifInfo.DigitalZoomRatio; + value["distance"] = m_imageMetadata.exifInfo.Distance; + value["exposurebias"] = m_imageMetadata.exifInfo.ExposureBias; + value["exposuremode"] = m_imageMetadata.exifInfo.ExposureMode; + value["exposureprogram"] = m_imageMetadata.exifInfo.ExposureProgram; + value["exposuretime"] = m_imageMetadata.exifInfo.ExposureTime; + value["flashused"] = m_imageMetadata.exifInfo.FlashUsed; + value["focallength"] = m_imageMetadata.exifInfo.FocalLength; + value["focallength35mmequiv"] = m_imageMetadata.exifInfo.FocalLength35mmEquiv; + value["gpsinfopresent"] = m_imageMetadata.exifInfo.GpsInfoPresent; + value["gpsinfo"]["alt"] = m_imageMetadata.exifInfo.GpsAlt; + value["gpsinfo"]["lat"] = m_imageMetadata.exifInfo.GpsLat; + value["gpsinfo"]["long"] = m_imageMetadata.exifInfo.GpsLong; + value["height"] = m_imageMetadata.exifInfo.Height; + value["iscolor"] = m_imageMetadata.exifInfo.IsColor; + value["isoequivalent"] = m_imageMetadata.exifInfo.ISOequivalent; + value["largestexifoffset"] = m_imageMetadata.exifInfo.LargestExifOffset; + value["lightsource"] = m_imageMetadata.exifInfo.LightSource; + value["meteringmode"] = m_imageMetadata.exifInfo.MeteringMode; + value["numdatetimetags"] = static_cast(m_imageMetadata.exifInfo.DateTimeOffsets.size()); + value["orientation"] = m_imageMetadata.exifInfo.Orientation; + value["process"] = m_imageMetadata.exifInfo.Process; + value["thumbnailatend"] = m_imageMetadata.exifInfo.ThumbnailAtEnd; + value["thumbnailoffset"] = m_imageMetadata.exifInfo.ThumbnailOffset; + value["thumbnailsize"] = m_imageMetadata.exifInfo.ThumbnailSize; + value["thumbnailsizeoffset"] = m_imageMetadata.exifInfo.ThumbnailSizeOffset; + value["whitebalance"] = m_imageMetadata.exifInfo.Whitebalance; + value["width"] = m_imageMetadata.exifInfo.Width; - value["author"] = m_iptcInfo.Author; - value["byline"] = m_iptcInfo.Byline; - value["bylinetitle"] = m_iptcInfo.BylineTitle; - value["caption"] = m_iptcInfo.Caption; - value["category"] = m_iptcInfo.Category; - value["city"] = m_iptcInfo.City; - value["urgency"] = m_iptcInfo.Urgency; - value["copyrightnotice"] = m_iptcInfo.CopyrightNotice; - value["country"] = m_iptcInfo.Country; - value["countrycode"] = m_iptcInfo.CountryCode; - value["credit"] = m_iptcInfo.Credit; - value["date"] = m_iptcInfo.Date; - value["headline"] = m_iptcInfo.Headline; - value["keywords"] = m_iptcInfo.Keywords; - value["objectname"] = m_iptcInfo.ObjectName; - value["referenceservice"] = m_iptcInfo.ReferenceService; - value["source"] = m_iptcInfo.Source; - value["specialinstructions"] = m_iptcInfo.SpecialInstructions; - value["state"] = m_iptcInfo.State; - value["supplementalcategories"] = m_iptcInfo.SupplementalCategories; - value["transmissionreference"] = m_iptcInfo.TransmissionReference; - value["timecreated"] = m_iptcInfo.TimeCreated; - value["sublocation"] = m_iptcInfo.SubLocation; - value["imagetype"] = m_iptcInfo.ImageType; + value["author"] = m_imageMetadata.iptcInfo.Author; + value["byline"] = m_imageMetadata.iptcInfo.Byline; + value["bylinetitle"] = m_imageMetadata.iptcInfo.BylineTitle; + value["caption"] = m_imageMetadata.iptcInfo.Caption; + value["category"] = m_imageMetadata.iptcInfo.Category; + value["city"] = m_imageMetadata.iptcInfo.City; + value["urgency"] = m_imageMetadata.iptcInfo.Urgency; + value["copyrightnotice"] = m_imageMetadata.iptcInfo.CopyrightNotice; + value["country"] = m_imageMetadata.iptcInfo.Country; + value["countrycode"] = m_imageMetadata.iptcInfo.CountryCode; + value["credit"] = m_imageMetadata.iptcInfo.Credit; + value["date"] = m_imageMetadata.iptcInfo.Date; + value["headline"] = m_imageMetadata.iptcInfo.Headline; + value["keywords"] = m_imageMetadata.iptcInfo.Keywords; + value["objectname"] = m_imageMetadata.iptcInfo.ObjectName; + value["referenceservice"] = m_imageMetadata.iptcInfo.ReferenceService; + value["source"] = m_imageMetadata.iptcInfo.Source; + value["specialinstructions"] = m_imageMetadata.iptcInfo.SpecialInstructions; + value["state"] = m_imageMetadata.iptcInfo.State; + value["supplementalcategories"] = m_imageMetadata.iptcInfo.SupplementalCategories; + value["transmissionreference"] = m_imageMetadata.iptcInfo.TransmissionReference; + value["timecreated"] = m_imageMetadata.iptcInfo.TimeCreated; + value["sublocation"] = m_imageMetadata.iptcInfo.SubLocation; + value["imagetype"] = m_imageMetadata.iptcInfo.ImageType; } void CPictureInfoTag::ToSortable(SortItem& sortable, Field field) const @@ -411,42 +316,26 @@ const std::string CPictureInfoTag::GetInfo(int info) const switch (info) { case SLIDESHOW_RESOLUTION: - value = StringUtils::Format("{} x {}", m_exifInfo.Width, m_exifInfo.Height); + value = StringUtils::Format("{} x {}", m_imageMetadata.exifInfo.Width, + m_imageMetadata.exifInfo.Height); break; case SLIDESHOW_COLOUR: - value = m_exifInfo.IsColor ? "Colour" : "Black and White"; + value = m_imageMetadata.exifInfo.IsColor ? "Colour" : "Black and White"; break; case SLIDESHOW_PROCESS: - switch (m_exifInfo.Process) - { - case M_SOF0: - // don't show it if its the plain old boring 'baseline' process, but do - // show it if its something else, like 'progressive' (used on web sometimes) - value = "Baseline"; - break; - case M_SOF1: value = "Extended sequential"; break; - case M_SOF2: value = "Progressive"; break; - case M_SOF3: value = "Lossless"; break; - case M_SOF5: value = "Differential sequential"; break; - case M_SOF6: value = "Differential progressive"; break; - case M_SOF7: value = "Differential lossless"; break; - case M_SOF9: value = "Extended sequential, arithmetic coding"; break; - case M_SOF10: value = "Progressive, arithmetic coding"; break; - case M_SOF11: value = "Lossless, arithmetic coding"; break; - case M_SOF13: value = "Differential sequential, arithmetic coding"; break; - case M_SOF14: value = "Differential progressive, arithmetic coding"; break; - case M_SOF15: value = "Differential lossless, arithmetic coding"; break; - default: value = "Unknown"; break; - } + { + auto process = m_imageMetadata.exifInfo.Process; + value = !process.empty() ? process : "Unknown"; break; + } case SLIDESHOW_COMMENT: - value = m_exifInfo.FileComment; + value = m_imageMetadata.exifInfo.FileComment; break; case SLIDESHOW_EXIF_COMMENT: - value = m_exifInfo.Comments; + value = m_imageMetadata.exifInfo.Comments; break; case SLIDESHOW_EXIF_XPCOMMENT: - value = m_exifInfo.XPComment; + value = m_imageMetadata.exifInfo.XPComment; break; case SLIDESHOW_EXIF_LONG_DATE_TIME: if (m_dateTimeTaken.IsValid()) @@ -465,22 +354,22 @@ const std::string CPictureInfoTag::GetInfo(int info) const value = m_dateTimeTaken.GetAsLocalizedDate(); break; case SLIDESHOW_EXIF_DESCRIPTION: - value = m_exifInfo.Description; + value = m_imageMetadata.exifInfo.Description; break; case SLIDESHOW_EXIF_CAMERA_MAKE: - value = m_exifInfo.CameraMake; + value = m_imageMetadata.exifInfo.CameraMake; break; case SLIDESHOW_EXIF_CAMERA_MODEL: - value = m_exifInfo.CameraModel; + value = m_imageMetadata.exifInfo.CameraModel; break; -// case SLIDESHOW_EXIF_SOFTWARE: -// value = m_exifInfo.Software; + // case SLIDESHOW_EXIF_SOFTWARE: + // value = m_imageMetadata.exifInfo.Software; case SLIDESHOW_EXIF_APERTURE: - if (m_exifInfo.ApertureFNumber) - value = StringUtils::Format("{:3.1f}", m_exifInfo.ApertureFNumber); + if (m_imageMetadata.exifInfo.ApertureFNumber) + value = StringUtils::Format("{:3.1f}", m_imageMetadata.exifInfo.ApertureFNumber); break; case SLIDESHOW_EXIF_ORIENTATION: - switch (m_exifInfo.Orientation) + switch (m_imageMetadata.exifInfo.Orientation) { case 1: value = "Top Left"; break; case 2: value = "Top Right"; break; @@ -493,21 +382,22 @@ const std::string CPictureInfoTag::GetInfo(int info) const } break; case SLIDESHOW_EXIF_FOCAL_LENGTH: - if (m_exifInfo.FocalLength) + if (m_imageMetadata.exifInfo.FocalLength) { - value = StringUtils::Format("{:4.2f}mm", m_exifInfo.FocalLength); - if (m_exifInfo.FocalLength35mmEquiv != 0) - value += StringUtils::Format(" (35mm Equivalent = {}mm)", m_exifInfo.FocalLength35mmEquiv); + value = StringUtils::Format("{:4.2f}mm", m_imageMetadata.exifInfo.FocalLength); + if (m_imageMetadata.exifInfo.FocalLength35mmEquiv != 0) + value += StringUtils::Format(" (35mm Equivalent = {}mm)", + m_imageMetadata.exifInfo.FocalLength35mmEquiv); } break; case SLIDESHOW_EXIF_FOCUS_DIST: - if (m_exifInfo.Distance < 0) + if (m_imageMetadata.exifInfo.Distance < 0) value = "Infinite"; - else if (m_exifInfo.Distance > 0) - value = StringUtils::Format("{:4.2f}m", m_exifInfo.Distance); + else if (m_imageMetadata.exifInfo.Distance > 0) + value = StringUtils::Format("{:4.2f}m", m_imageMetadata.exifInfo.Distance); break; case SLIDESHOW_EXIF_EXPOSURE: - switch (m_exifInfo.ExposureProgram) + switch (m_imageMetadata.exifInfo.ExposureProgram) { case 1: value = "Manual"; break; case 2: value = "Program (Auto)"; break; @@ -520,22 +410,23 @@ const std::string CPictureInfoTag::GetInfo(int info) const } break; case SLIDESHOW_EXIF_EXPOSURE_TIME: - if (m_exifInfo.ExposureTime) + if (m_imageMetadata.exifInfo.ExposureTime) { - if (m_exifInfo.ExposureTime < 0.010f) - value = StringUtils::Format("{:6.4f}s", m_exifInfo.ExposureTime); + if (m_imageMetadata.exifInfo.ExposureTime < 0.010f) + value = StringUtils::Format("{:6.4f}s", m_imageMetadata.exifInfo.ExposureTime); else - value = StringUtils::Format("{:5.3f}s", m_exifInfo.ExposureTime); - if (m_exifInfo.ExposureTime <= 0.5f) - value += StringUtils::Format(" (1/{})", static_cast(0.5f + 1 / m_exifInfo.ExposureTime)); + value = StringUtils::Format("{:5.3f}s", m_imageMetadata.exifInfo.ExposureTime); + if (m_imageMetadata.exifInfo.ExposureTime <= 0.5f) + value += StringUtils::Format( + " (1/{})", static_cast(0.5f + 1 / m_imageMetadata.exifInfo.ExposureTime)); } break; case SLIDESHOW_EXIF_EXPOSURE_BIAS: - if (m_exifInfo.ExposureBias != 0) - value = StringUtils::Format("{:4.2f} EV", m_exifInfo.ExposureBias); + if (m_imageMetadata.exifInfo.ExposureBias != 0) + value = StringUtils::Format("{:4.2f} EV", m_imageMetadata.exifInfo.ExposureBias); break; case SLIDESHOW_EXIF_EXPOSURE_MODE: - switch (m_exifInfo.ExposureMode) + switch (m_imageMetadata.exifInfo.ExposureMode) { case 0: value = "Automatic"; break; case 1: value = "Manual"; break; @@ -543,12 +434,12 @@ const std::string CPictureInfoTag::GetInfo(int info) const } break; case SLIDESHOW_EXIF_FLASH_USED: - if (m_exifInfo.FlashUsed >= 0) + if (m_imageMetadata.exifInfo.FlashUsed >= 0) { - if (m_exifInfo.FlashUsed & 1) + if (m_imageMetadata.exifInfo.FlashUsed & 1) { value = "Yes"; - switch (m_exifInfo.FlashUsed) + switch (m_imageMetadata.exifInfo.FlashUsed) { case 0x5: value = "Yes (Strobe light not detected)"; break; case 0x7: value = "Yes (Strobe light detected)"; break; @@ -570,13 +461,13 @@ const std::string CPictureInfoTag::GetInfo(int info) const } } else - value = m_exifInfo.FlashUsed == 0x18 ? "No (Auto)" : "No"; + value = m_imageMetadata.exifInfo.FlashUsed == 0x18 ? "No (Auto)" : "No"; } break; case SLIDESHOW_EXIF_WHITE_BALANCE: - return m_exifInfo.Whitebalance ? "Manual" : "Auto"; + return m_imageMetadata.exifInfo.Whitebalance ? "Manual" : "Auto"; case SLIDESHOW_EXIF_LIGHT_SOURCE: - switch (m_exifInfo.LightSource) + switch (m_imageMetadata.exifInfo.LightSource) { case 1: value = "Daylight"; break; case 2: value = "Fluorescent"; break; @@ -590,7 +481,7 @@ const std::string CPictureInfoTag::GetInfo(int info) const } break; case SLIDESHOW_EXIF_METERING_MODE: - switch (m_exifInfo.MeteringMode) + switch (m_imageMetadata.exifInfo.MeteringMode) { case 2: value = "Center weight"; break; case 3: value = "Spot"; break; @@ -598,50 +489,98 @@ const std::string CPictureInfoTag::GetInfo(int info) const } break; case SLIDESHOW_EXIF_ISO_EQUIV: - if (m_exifInfo.ISOequivalent) - value = StringUtils::Format("{:2}", m_exifInfo.ISOequivalent); + if (m_imageMetadata.exifInfo.ISOequivalent) + value = StringUtils::Format("{:2}", m_imageMetadata.exifInfo.ISOequivalent); break; case SLIDESHOW_EXIF_DIGITAL_ZOOM: - if (m_exifInfo.DigitalZoomRatio) - value = StringUtils::Format("{:1.3f}x", m_exifInfo.DigitalZoomRatio); + if (m_imageMetadata.exifInfo.DigitalZoomRatio) + value = StringUtils::Format("{:1.3f}x", m_imageMetadata.exifInfo.DigitalZoomRatio); break; case SLIDESHOW_EXIF_CCD_WIDTH: - if (m_exifInfo.CCDWidth) - value = StringUtils::Format("{:4.2f}mm", m_exifInfo.CCDWidth); + if (m_imageMetadata.exifInfo.CCDWidth) + value = StringUtils::Format("{:4.2f}mm", m_imageMetadata.exifInfo.CCDWidth); break; case SLIDESHOW_EXIF_GPS_LATITUDE: - value = m_exifInfo.GpsLat; + value = m_imageMetadata.exifInfo.GpsLat; break; case SLIDESHOW_EXIF_GPS_LONGITUDE: - value = m_exifInfo.GpsLong; + value = m_imageMetadata.exifInfo.GpsLong; break; case SLIDESHOW_EXIF_GPS_ALTITUDE: - value = m_exifInfo.GpsAlt; - break; - case SLIDESHOW_IPTC_SUP_CATEGORIES: value = m_iptcInfo.SupplementalCategories; break; - case SLIDESHOW_IPTC_KEYWORDS: value = m_iptcInfo.Keywords; break; - case SLIDESHOW_IPTC_CAPTION: value = m_iptcInfo.Caption; break; - case SLIDESHOW_IPTC_AUTHOR: value = m_iptcInfo.Author; break; - case SLIDESHOW_IPTC_HEADLINE: value = m_iptcInfo.Headline; break; - case SLIDESHOW_IPTC_SPEC_INSTR: value = m_iptcInfo.SpecialInstructions; break; - case SLIDESHOW_IPTC_CATEGORY: value = m_iptcInfo.Category; break; - case SLIDESHOW_IPTC_BYLINE: value = m_iptcInfo.Byline; break; - case SLIDESHOW_IPTC_BYLINE_TITLE: value = m_iptcInfo.BylineTitle; break; - case SLIDESHOW_IPTC_CREDIT: value = m_iptcInfo.Credit; break; - case SLIDESHOW_IPTC_SOURCE: value = m_iptcInfo.Source; break; - case SLIDESHOW_IPTC_COPYRIGHT_NOTICE: value = m_iptcInfo.CopyrightNotice; break; - case SLIDESHOW_IPTC_OBJECT_NAME: value = m_iptcInfo.ObjectName; break; - case SLIDESHOW_IPTC_CITY: value = m_iptcInfo.City; break; - case SLIDESHOW_IPTC_STATE: value = m_iptcInfo.State; break; - case SLIDESHOW_IPTC_COUNTRY: value = m_iptcInfo.Country; break; - case SLIDESHOW_IPTC_TX_REFERENCE: value = m_iptcInfo.TransmissionReference; break; - case SLIDESHOW_IPTC_DATE: value = m_iptcInfo.Date; break; - case SLIDESHOW_IPTC_URGENCY: value = m_iptcInfo.Urgency; break; - case SLIDESHOW_IPTC_COUNTRY_CODE: value = m_iptcInfo.CountryCode; break; - case SLIDESHOW_IPTC_REF_SERVICE: value = m_iptcInfo.ReferenceService; break; - case SLIDESHOW_IPTC_TIMECREATED: value = m_iptcInfo.TimeCreated; break; - case SLIDESHOW_IPTC_SUBLOCATION: value = m_iptcInfo.SubLocation; break; - case SLIDESHOW_IPTC_IMAGETYPE: value = m_iptcInfo.ImageType; break; + value = m_imageMetadata.exifInfo.GpsAlt; + break; + case SLIDESHOW_IPTC_SUP_CATEGORIES: + value = m_imageMetadata.iptcInfo.SupplementalCategories; + break; + case SLIDESHOW_IPTC_KEYWORDS: + value = m_imageMetadata.iptcInfo.Keywords; + break; + case SLIDESHOW_IPTC_CAPTION: + value = m_imageMetadata.iptcInfo.Caption; + break; + case SLIDESHOW_IPTC_AUTHOR: + value = m_imageMetadata.iptcInfo.Author; + break; + case SLIDESHOW_IPTC_HEADLINE: + value = m_imageMetadata.iptcInfo.Headline; + break; + case SLIDESHOW_IPTC_SPEC_INSTR: + value = m_imageMetadata.iptcInfo.SpecialInstructions; + break; + case SLIDESHOW_IPTC_CATEGORY: + value = m_imageMetadata.iptcInfo.Category; + break; + case SLIDESHOW_IPTC_BYLINE: + value = m_imageMetadata.iptcInfo.Byline; + break; + case SLIDESHOW_IPTC_BYLINE_TITLE: + value = m_imageMetadata.iptcInfo.BylineTitle; + break; + case SLIDESHOW_IPTC_CREDIT: + value = m_imageMetadata.iptcInfo.Credit; + break; + case SLIDESHOW_IPTC_SOURCE: + value = m_imageMetadata.iptcInfo.Source; + break; + case SLIDESHOW_IPTC_COPYRIGHT_NOTICE: + value = m_imageMetadata.iptcInfo.CopyrightNotice; + break; + case SLIDESHOW_IPTC_OBJECT_NAME: + value = m_imageMetadata.iptcInfo.ObjectName; + break; + case SLIDESHOW_IPTC_CITY: + value = m_imageMetadata.iptcInfo.City; + break; + case SLIDESHOW_IPTC_STATE: + value = m_imageMetadata.iptcInfo.State; + break; + case SLIDESHOW_IPTC_COUNTRY: + value = m_imageMetadata.iptcInfo.Country; + break; + case SLIDESHOW_IPTC_TX_REFERENCE: + value = m_imageMetadata.iptcInfo.TransmissionReference; + break; + case SLIDESHOW_IPTC_DATE: + value = m_imageMetadata.iptcInfo.Date; + break; + case SLIDESHOW_IPTC_URGENCY: + value = m_imageMetadata.iptcInfo.Urgency; + break; + case SLIDESHOW_IPTC_COUNTRY_CODE: + value = m_imageMetadata.iptcInfo.CountryCode; + break; + case SLIDESHOW_IPTC_REF_SERVICE: + value = m_imageMetadata.iptcInfo.ReferenceService; + break; + case SLIDESHOW_IPTC_TIMECREATED: + value = m_imageMetadata.iptcInfo.TimeCreated; + break; + case SLIDESHOW_IPTC_SUBLOCATION: + value = m_imageMetadata.iptcInfo.SubLocation; + break; + case SLIDESHOW_IPTC_IMAGETYPE: + value = m_imageMetadata.iptcInfo.ImageType; + break; default: break; } @@ -725,15 +664,16 @@ void CPictureInfoTag::SetInfo(const std::string &key, const std::string& value) StringUtils::Tokenize(value, dimension, ","); if (dimension.size() == 2) { - m_exifInfo.Width = atoi(dimension[0].c_str()); - m_exifInfo.Height = atoi(dimension[1].c_str()); - m_isInfoSetExternally = true; // Set the internal state to show metadata has been set by call to SetInfo + m_imageMetadata.exifInfo.Width = std::atoi(dimension[0].c_str()); + m_imageMetadata.exifInfo.Height = std::atoi(dimension[1].c_str()); + m_isInfoSetExternally = + true; // Set the internal state to show metadata has been set by call to SetInfo } break; } case SLIDESHOW_EXIF_DATE_TIME: { - m_exifInfo.DateTime = value; + m_imageMetadata.exifInfo.DateTime = value; m_isInfoSetExternally = true; // Set the internal state to show metadata has been set by call to SetInfo ConvertDateTime(); break; @@ -750,7 +690,7 @@ const CDateTime& CPictureInfoTag::GetDateTimeTaken() const void CPictureInfoTag::ConvertDateTime() { - const std::string& dateTime = m_exifInfo.DateTime; + const std::string& dateTime = m_imageMetadata.exifInfo.DateTime; if (dateTime.length() >= 19 && dateTime[0] != ' ') { int year = atoi(dateTime.substr(0, 4).c_str()); diff --git a/xbmc/pictures/PictureInfoTag.h b/xbmc/pictures/PictureInfoTag.h index eff58fbc61744..bf74be2b8b0eb 100644 --- a/xbmc/pictures/PictureInfoTag.h +++ b/xbmc/pictures/PictureInfoTag.h @@ -8,8 +8,8 @@ #pragma once +#include "ImageMetadata.h" #include "XBDateTime.h" -#include "libexif.h" #include "utils/IArchivable.h" #include "utils/ISerializable.h" #include "utils/ISortable.h" @@ -31,102 +31,6 @@ class CPictureInfoTag : public IArchivable, public ISerializable, public ISortab { friend class KODI::ADDONS::CImageDecoder; - // Mimic structs from libexif.h but with C++ types instead of arrays - struct ExifInfo - { - ExifInfo() = default; - ExifInfo(const ExifInfo&) = default; - ExifInfo(ExifInfo&&) = default; - ExifInfo(const ExifInfo_t& other); - - ExifInfo& operator=(const ExifInfo&) = default; - ExifInfo& operator=(ExifInfo&&) = default; - - std::string CameraMake; - std::string CameraModel; - std::string DateTime; - int Height{}; - int Width{}; - int Orientation{}; - int IsColor{}; - int Process{}; - int FlashUsed{}; - float FocalLength{}; - float ExposureTime{}; - float ApertureFNumber{}; - float Distance{}; - float CCDWidth{}; - float ExposureBias{}; - float DigitalZoomRatio{}; - int FocalLength35mmEquiv{}; - int Whitebalance{}; - int MeteringMode{}; - int ExposureProgram{}; - int ExposureMode{}; - int ISOequivalent{}; - int LightSource{}; - int CommentsCharset{}; - int XPCommentsCharset{}; - std::string Comments; - std::string FileComment; - std::string XPComment; - std::string Description; - - unsigned ThumbnailOffset{}; - unsigned ThumbnailSize{}; - unsigned LargestExifOffset{}; - - char ThumbnailAtEnd{}; - int ThumbnailSizeOffset{}; - - std::vector DateTimeOffsets; - - int GpsInfoPresent{}; - std::string GpsLat; - std::string GpsLong; - std::string GpsAlt; - - private: - static std::string Convert(int charset, const char* data); - }; - - struct IPTCInfo - { - IPTCInfo() = default; - IPTCInfo(const IPTCInfo&) = default; - IPTCInfo(IPTCInfo&&) = default; - IPTCInfo(const IPTCInfo_t& other); - - IPTCInfo& operator=(const IPTCInfo&) = default; - IPTCInfo& operator=(IPTCInfo&&) = default; - - std::string RecordVersion; - std::string SupplementalCategories; - std::string Keywords; - std::string Caption; - std::string Author; - std::string Headline; - std::string SpecialInstructions; - std::string Category; - std::string Byline; - std::string BylineTitle; - std::string Credit; - std::string Source; - std::string CopyrightNotice; - std::string ObjectName; - std::string City; - std::string State; - std::string Country; - std::string TransmissionReference; - std::string Date; - std::string Urgency; - std::string ReferenceService; - std::string CountryCode; - std::string TimeCreated; - std::string SubLocation; - std::string ImageType; - }; - public: CPictureInfoTag() { Reset(); } virtual ~CPictureInfoTag() = default; @@ -145,14 +49,13 @@ class CPictureInfoTag : public IArchivable, public ISerializable, public ISortab * GetDateTimeTaken() -- Returns the EXIF DateTimeOriginal for current picture * * The exif library returns DateTimeOriginal if available else the other - * DateTime tags. See libexif CExifParse::ProcessDir for details. + * DateTime tags. */ const CDateTime& GetDateTimeTaken() const; private: static int TranslateString(const std::string &info); - ExifInfo m_exifInfo; - IPTCInfo m_iptcInfo; + ImageMetadata m_imageMetadata; bool m_isLoaded; // Set to true if metadata has been loaded from the picture file successfully bool m_isInfoSetExternally; // Set to true if metadata has been set by an external call to SetInfo CDateTime m_dateTimeTaken; diff --git a/xbmc/pictures/libexif.cpp b/xbmc/pictures/libexif.cpp deleted file mode 100644 index 3ee75b975f033..0000000000000 --- a/xbmc/pictures/libexif.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// libexif.cpp : Defines the entry point for the console application. -// - -#include "libexif.h" - -#include "JpegParse.h" - -#ifdef TARGET_WINDOWS -#include -#else -#include -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -bool process_jpeg(const char *filename, ExifInfo_t *exifInfo, IPTCInfo_t *iptcInfo) -{ - if (!exifInfo || !iptcInfo) return false; - CJpegParse jpeg; - memset(exifInfo, 0, sizeof(ExifInfo_t)); - memset(iptcInfo, 0, sizeof(IPTCInfo_t)); - if (jpeg.Process(filename)) - { - memcpy(exifInfo, jpeg.GetExifInfo(), sizeof(ExifInfo_t)); - memcpy(iptcInfo, jpeg.GetIptcInfo(), sizeof(IPTCInfo_t)); - return true; - } - return false; -} -#ifdef __cplusplus -} -#endif diff --git a/xbmc/pictures/libexif.h b/xbmc/pictures/libexif.h deleted file mode 100644 index 567255ece658c..0000000000000 --- a/xbmc/pictures/libexif.h +++ /dev/null @@ -1,138 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef _DLL -#ifdef WIN32 -#define EXIF_EXPORT __declspec(dllexport) -#else -#define EXIF_EXPORT -#endif -#else -#define EXIF_EXPORT -#endif - -//-------------------------------------------------------------------------- -// JPEG markers consist of one or more 0xFF bytes, followed by a marker -// code byte (which is not an FF). Here are the marker codes of interest -// in this application. -//-------------------------------------------------------------------------- - -#define M_SOF0 0xC0 // Start Of Frame N -#define M_SOF1 0xC1 // N indicates which compression process -#define M_SOF2 0xC2 // Only SOF0-SOF2 are now in common use -#define M_SOF3 0xC3 -#define M_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers -#define M_SOF6 0xC6 -#define M_SOF7 0xC7 -#define M_SOF9 0xC9 -#define M_SOF10 0xCA -#define M_SOF11 0xCB -#define M_SOF13 0xCD -#define M_SOF14 0xCE -#define M_SOF15 0xCF -#define M_SOI 0xD8 // Start Of Image (beginning of datastream) -#define M_EOI 0xD9 // End Of Image (end of datastream) -#define M_SOS 0xDA // Start Of Scan (begins compressed data) -#define M_JFIF 0xE0 // Jfif marker -#define M_EXIF 0xE1 // Exif marker -#define M_COM 0xFE // COMment -#define M_DQT 0xDB -#define M_DHT 0xC4 -#define M_DRI 0xDD -#define M_IPTC 0xED // IPTC marker - -#define MAX_IPTC_STRING 256 - -typedef struct { - char RecordVersion[MAX_IPTC_STRING]; - char SupplementalCategories[MAX_IPTC_STRING]; - char Keywords[MAX_IPTC_STRING]; - char Caption[MAX_IPTC_STRING]; - char Author[MAX_IPTC_STRING]; - char Headline[MAX_IPTC_STRING]; - char SpecialInstructions[MAX_IPTC_STRING]; - char Category[MAX_IPTC_STRING]; - char Byline[MAX_IPTC_STRING]; - char BylineTitle[MAX_IPTC_STRING]; - char Credit[MAX_IPTC_STRING]; - char Source[MAX_IPTC_STRING]; - char CopyrightNotice[MAX_IPTC_STRING]; - char ObjectName[MAX_IPTC_STRING]; - char City[MAX_IPTC_STRING]; - char State[MAX_IPTC_STRING]; - char Country[MAX_IPTC_STRING]; - char TransmissionReference[MAX_IPTC_STRING]; - char Date[MAX_IPTC_STRING]; - char Urgency[MAX_IPTC_STRING]; - char ReferenceService[MAX_IPTC_STRING]; - char CountryCode[MAX_IPTC_STRING]; - char TimeCreated[MAX_IPTC_STRING]; - char SubLocation[MAX_IPTC_STRING]; - char ImageType[MAX_IPTC_STRING]; -} IPTCInfo_t; - -#define EXIF_COMMENT_CHARSET_CONVERTED -1 // Comments contains converted data -#define EXIF_COMMENT_CHARSET_UNKNOWN 0 // Exif: Unknown -#define EXIF_COMMENT_CHARSET_ASCII 2 // Exif: Ascii -#define EXIF_COMMENT_CHARSET_UNICODE 3 // Exif: Unicode (UTF-16) -#define EXIF_COMMENT_CHARSET_JIS 4 // Exif: JIS X208-1990 - -#define MAX_COMMENT 2000 -#define MAX_DATE_COPIES 10 - -typedef struct { - char CameraMake [33]; - char CameraModel [41]; - char DateTime [21]; - int Height, Width; - int Orientation; - int IsColor; - int Process; - int FlashUsed; - float FocalLength; - float ExposureTime; - float ApertureFNumber; - float Distance; - float CCDWidth; - float ExposureBias; - float DigitalZoomRatio; - int FocalLength35mmEquiv; // Exif 2.2 tag - usually not present. - int Whitebalance; - int MeteringMode; - int ExposureProgram; - int ExposureMode; - int ISOequivalent; - int LightSource; - int CommentsCharset; // EXIF_COMMENT_CHARSET_* - int XPCommentsCharset; - char Comments[MAX_COMMENT + 1]; // +1 for null termination - char FileComment[MAX_COMMENT + 1]; - char XPComment[MAX_COMMENT + 1]; - char Description[MAX_COMMENT + 1]; - - unsigned ThumbnailOffset; // Exif offset to thumbnail - unsigned ThumbnailSize; // Size of thumbnail. - unsigned LargestExifOffset; // Last exif data referenced (to check if thumbnail is at end) - - char ThumbnailAtEnd; // Exif header ends with the thumbnail - // (we can only modify the thumbnail if its at the end) - int ThumbnailSizeOffset; - - int DateTimeOffsets[MAX_DATE_COPIES]; - int numDateTimeTags; - - int GpsInfoPresent; - char GpsLat[31]; - char GpsLong[31]; - char GpsAlt[20]; -} ExifInfo_t; - -EXIF_EXPORT bool process_jpeg(const char *filename, ExifInfo_t *exifInfo, IPTCInfo_t *iptcInfo); - -#ifdef __cplusplus -} -#endif - From 1e9bd9735736d969f3972704e1937ad36eb55fc0 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Mon, 13 May 2024 11:30:03 +0100 Subject: [PATCH 157/651] Pictures: Remove unused members of exif --- xbmc/addons/ImageDecoder.cpp | 6 ----- xbmc/pictures/Exif.h | 15 ----------- xbmc/pictures/PictureInfoTag.cpp | 44 -------------------------------- 3 files changed, 65 deletions(-) diff --git a/xbmc/addons/ImageDecoder.cpp b/xbmc/addons/ImageDecoder.cpp index 20589eaa0da74..8cdd04384da39 100644 --- a/xbmc/addons/ImageDecoder.cpp +++ b/xbmc/addons/ImageDecoder.cpp @@ -79,12 +79,6 @@ bool CImageDecoder::LoadInfoTag(const std::string& fileName, CPictureInfoTag* ta * - std::string Comments; * - std::string FileComment; * - std::string XPComment; - * - unsigned ThumbnailOffset{}; - * - unsigned ThumbnailSize{}; - * - unsigned LargestExifOffset{}; - * - char ThumbnailAtEnd{}; - * - int ThumbnailSizeOffset{}; - * - std::vector DateTimeOffsets; * * struct IPTCInfo: * - std::string RecordVersion; diff --git a/xbmc/pictures/Exif.h b/xbmc/pictures/Exif.h index bdc2255156e8f..1c043cff5cae2 100644 --- a/xbmc/pictures/Exif.h +++ b/xbmc/pictures/Exif.h @@ -12,7 +12,6 @@ #include constexpr unsigned int MAX_DATE_COPIES = 10; -constexpr int EXIF_COMMENT_CHARSET_CONVERTED = -1; struct ExifInfo { @@ -46,25 +45,11 @@ struct ExifInfo int ExposureMode{}; int ISOequivalent{}; int LightSource{}; - //! TODO: Remove me, not needed anymore (still exposed to addon interface) - int CommentsCharset{}; - //! TODO: Remove me, not needed anymore (still exposed to addon interface) - int XPCommentsCharset{}; std::string Comments; std::string FileComment; std::string XPComment; std::string Description; - //! TODO: this is not used anywhere, just nuke it - unsigned ThumbnailOffset{}; - unsigned ThumbnailSize{}; - unsigned LargestExifOffset{}; - //! TODO: this is not used anywhere, just nuke it - char ThumbnailAtEnd{}; - int ThumbnailSizeOffset{}; - //! TODO: this is not used anywhere, just nuke it - std::vector DateTimeOffsets; - int GpsInfoPresent{}; std::string GpsLat; std::string GpsLong; diff --git a/xbmc/pictures/PictureInfoTag.cpp b/xbmc/pictures/PictureInfoTag.cpp index 6633d0fcb550e..4dda0b5903151 100644 --- a/xbmc/pictures/PictureInfoTag.cpp +++ b/xbmc/pictures/PictureInfoTag.cpp @@ -88,13 +88,6 @@ void CPictureInfoTag::Archive(CArchive& ar) ar << m_imageMetadata.exifInfo.Comments; ar << m_imageMetadata.exifInfo.Description; ar << m_imageMetadata.exifInfo.DateTime; - for (std::vector::size_type i = 0; i < MAX_DATE_COPIES; ++i) - { - if (i < m_imageMetadata.exifInfo.DateTimeOffsets.size()) - ar << m_imageMetadata.exifInfo.DateTimeOffsets[i]; - else - ar << static_cast(0); - } ar << m_imageMetadata.exifInfo.DigitalZoomRatio; ar << m_imageMetadata.exifInfo.Distance; ar << m_imageMetadata.exifInfo.ExposureBias; @@ -111,16 +104,10 @@ void CPictureInfoTag::Archive(CArchive& ar) ar << m_imageMetadata.exifInfo.Height; ar << m_imageMetadata.exifInfo.IsColor; ar << m_imageMetadata.exifInfo.ISOequivalent; - ar << m_imageMetadata.exifInfo.LargestExifOffset; ar << m_imageMetadata.exifInfo.LightSource; ar << m_imageMetadata.exifInfo.MeteringMode; - ar << static_cast(m_imageMetadata.exifInfo.DateTimeOffsets.size()); ar << m_imageMetadata.exifInfo.Orientation; ar << m_imageMetadata.exifInfo.Process; - ar << m_imageMetadata.exifInfo.ThumbnailAtEnd; - ar << m_imageMetadata.exifInfo.ThumbnailOffset; - ar << m_imageMetadata.exifInfo.ThumbnailSize; - ar << m_imageMetadata.exifInfo.ThumbnailSizeOffset; ar << m_imageMetadata.exifInfo.Whitebalance; ar << m_imageMetadata.exifInfo.Width; ar << m_dateTimeTaken; @@ -159,18 +146,8 @@ void CPictureInfoTag::Archive(CArchive& ar) ar >> m_imageMetadata.exifInfo.CameraModel; ar >> m_imageMetadata.exifInfo.CCDWidth; ar >> m_imageMetadata.exifInfo.Comments; - m_imageMetadata.exifInfo.CommentsCharset = - EXIF_COMMENT_CHARSET_CONVERTED; // Store and restore the comment charset converted ar >> m_imageMetadata.exifInfo.Description; ar >> m_imageMetadata.exifInfo.DateTime; - m_imageMetadata.exifInfo.DateTimeOffsets.clear(); - m_imageMetadata.exifInfo.DateTimeOffsets.reserve(MAX_DATE_COPIES); - for (std::vector::size_type i = 0; i < MAX_DATE_COPIES; ++i) - { - int dateTimeOffset; - ar >> dateTimeOffset; - m_imageMetadata.exifInfo.DateTimeOffsets.push_back(dateTimeOffset); - } ar >> m_imageMetadata.exifInfo.DigitalZoomRatio; ar >> m_imageMetadata.exifInfo.Distance; ar >> m_imageMetadata.exifInfo.ExposureBias; @@ -187,18 +164,10 @@ void CPictureInfoTag::Archive(CArchive& ar) ar >> m_imageMetadata.exifInfo.Height; ar >> m_imageMetadata.exifInfo.IsColor; ar >> m_imageMetadata.exifInfo.ISOequivalent; - ar >> m_imageMetadata.exifInfo.LargestExifOffset; ar >> m_imageMetadata.exifInfo.LightSource; ar >> m_imageMetadata.exifInfo.MeteringMode; - int numDateTimeTags; - ar >> numDateTimeTags; - m_imageMetadata.exifInfo.DateTimeOffsets.resize(numDateTimeTags); ar >> m_imageMetadata.exifInfo.Orientation; ar >> m_imageMetadata.exifInfo.Process; - ar >> m_imageMetadata.exifInfo.ThumbnailAtEnd; - ar >> m_imageMetadata.exifInfo.ThumbnailOffset; - ar >> m_imageMetadata.exifInfo.ThumbnailSize; - ar >> m_imageMetadata.exifInfo.ThumbnailSizeOffset; ar >> m_imageMetadata.exifInfo.Whitebalance; ar >> m_imageMetadata.exifInfo.Width; ar >> m_dateTimeTaken; @@ -239,13 +208,6 @@ void CPictureInfoTag::Serialize(CVariant& value) const value["comments"] = m_imageMetadata.exifInfo.Comments; value["description"] = m_imageMetadata.exifInfo.Description; value["datetime"] = m_imageMetadata.exifInfo.DateTime; - for (std::vector::size_type i = 0; i < MAX_DATE_COPIES; ++i) - { - if (i < m_imageMetadata.exifInfo.DateTimeOffsets.size()) - value["datetimeoffsets"][static_cast(i)] = m_imageMetadata.exifInfo.DateTimeOffsets[i]; - else - value["datetimeoffsets"][static_cast(i)] = static_cast(0); - } value["digitalzoomratio"] = m_imageMetadata.exifInfo.DigitalZoomRatio; value["distance"] = m_imageMetadata.exifInfo.Distance; value["exposurebias"] = m_imageMetadata.exifInfo.ExposureBias; @@ -262,16 +224,10 @@ void CPictureInfoTag::Serialize(CVariant& value) const value["height"] = m_imageMetadata.exifInfo.Height; value["iscolor"] = m_imageMetadata.exifInfo.IsColor; value["isoequivalent"] = m_imageMetadata.exifInfo.ISOequivalent; - value["largestexifoffset"] = m_imageMetadata.exifInfo.LargestExifOffset; value["lightsource"] = m_imageMetadata.exifInfo.LightSource; value["meteringmode"] = m_imageMetadata.exifInfo.MeteringMode; - value["numdatetimetags"] = static_cast(m_imageMetadata.exifInfo.DateTimeOffsets.size()); value["orientation"] = m_imageMetadata.exifInfo.Orientation; value["process"] = m_imageMetadata.exifInfo.Process; - value["thumbnailatend"] = m_imageMetadata.exifInfo.ThumbnailAtEnd; - value["thumbnailoffset"] = m_imageMetadata.exifInfo.ThumbnailOffset; - value["thumbnailsize"] = m_imageMetadata.exifInfo.ThumbnailSize; - value["thumbnailsizeoffset"] = m_imageMetadata.exifInfo.ThumbnailSizeOffset; value["whitebalance"] = m_imageMetadata.exifInfo.Whitebalance; value["width"] = m_imageMetadata.exifInfo.Width; From 0c1d7e813518b2c3262d63b67dbeb5d24a9504ee Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Mon, 13 May 2024 16:01:11 +0100 Subject: [PATCH 158/651] Pictures: move common metadata out of the exif struct --- xbmc/addons/ImageDecoder.cpp | 6 ++--- xbmc/pictures/Exif.h | 5 ---- xbmc/pictures/ImageMetadata.h | 6 +++++ xbmc/pictures/ImageMetadataParser.cpp | 10 ++++---- xbmc/pictures/PictureInfoTag.cpp | 37 +++++++++++++-------------- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/xbmc/addons/ImageDecoder.cpp b/xbmc/addons/ImageDecoder.cpp index 8cdd04384da39..4c99ab59841da 100644 --- a/xbmc/addons/ImageDecoder.cpp +++ b/xbmc/addons/ImageDecoder.cpp @@ -107,11 +107,11 @@ bool CImageDecoder::LoadInfoTag(const std::string& fileName, CPictureInfoTag* ta * @todo Rework @ref CPictureInfoTag to not limit on fixed structures ExifInfo & IPTCInfo. */ - tag->m_imageMetadata.exifInfo.Width = ifcTag.width; - tag->m_imageMetadata.exifInfo.Height = ifcTag.height; + tag->m_imageMetadata.width = ifcTag.width; + tag->m_imageMetadata.height = ifcTag.height; tag->m_imageMetadata.exifInfo.Distance = ifcTag.distance; tag->m_imageMetadata.exifInfo.Orientation = ifcTag.orientation; - tag->m_imageMetadata.exifInfo.IsColor = ifcTag.color == ADDON_IMG_COLOR_COLORED ? 1 : 0; + tag->m_imageMetadata.isColor = ifcTag.color == ADDON_IMG_COLOR_COLORED ? 1 : 0; tag->m_imageMetadata.exifInfo.ApertureFNumber = ifcTag.aperture_f_number; tag->m_imageMetadata.exifInfo.FlashUsed = ifcTag.flash_used ? 1 : 0; tag->m_imageMetadata.exifInfo.LightSource = ifcTag.light_source; diff --git a/xbmc/pictures/Exif.h b/xbmc/pictures/Exif.h index 1c043cff5cae2..b276c6057ff30 100644 --- a/xbmc/pictures/Exif.h +++ b/xbmc/pictures/Exif.h @@ -25,11 +25,7 @@ struct ExifInfo std::string CameraMake; std::string CameraModel; std::string DateTime; - int Height{}; - int Width{}; int Orientation{}; - int IsColor{}; - std::string Process{}; int FlashUsed{}; float FocalLength{}; float ExposureTime{}; @@ -46,7 +42,6 @@ struct ExifInfo int ISOequivalent{}; int LightSource{}; std::string Comments; - std::string FileComment; std::string XPComment; std::string Description; diff --git a/xbmc/pictures/ImageMetadata.h b/xbmc/pictures/ImageMetadata.h index 68d9578b46854..e61a933731675 100644 --- a/xbmc/pictures/ImageMetadata.h +++ b/xbmc/pictures/ImageMetadata.h @@ -13,6 +13,12 @@ struct ImageMetadata { + int height{}; + int width{}; + bool isColor{}; + std::string encodingProcess{}; + std::string fileComment{}; + ExifInfo exifInfo; IPTCInfo iptcInfo; }; diff --git a/xbmc/pictures/ImageMetadataParser.cpp b/xbmc/pictures/ImageMetadataParser.cpp index a4c4e904e3daf..485fd8d97426c 100644 --- a/xbmc/pictures/ImageMetadataParser.cpp +++ b/xbmc/pictures/ImageMetadataParser.cpp @@ -86,15 +86,15 @@ std::unique_ptr CImageMetadataParser::ExtractMetadata(const std:: void CImageMetadataParser::ExtractCommonMetadata(std::unique_ptr& image) { //! TODO: all these elements are generic should be moved out of the exif struct - m_imageMetadata->exifInfo.Height = image->pixelHeight(); - m_imageMetadata->exifInfo.Width = image->pixelWidth(); - m_imageMetadata->exifInfo.FileComment = image->comment(); + m_imageMetadata->height = image->pixelHeight(); + m_imageMetadata->width = image->pixelWidth(); + m_imageMetadata->fileComment = image->comment(); if (image->imageType() == Exiv2::ImageType::jpeg) { auto jpegImage = dynamic_cast(image.get()); - m_imageMetadata->exifInfo.IsColor = jpegImage->numColorComponents() == 3; - m_imageMetadata->exifInfo.Process = jpegImage->encodingProcess(); + m_imageMetadata->isColor = jpegImage->numColorComponents() == 3; + m_imageMetadata->encodingProcess = jpegImage->encodingProcess(); } } diff --git a/xbmc/pictures/PictureInfoTag.cpp b/xbmc/pictures/PictureInfoTag.cpp index 4dda0b5903151..4ad8b1f6cb77d 100644 --- a/xbmc/pictures/PictureInfoTag.cpp +++ b/xbmc/pictures/PictureInfoTag.cpp @@ -101,15 +101,15 @@ void CPictureInfoTag::Archive(CArchive& ar) ar << m_imageMetadata.exifInfo.GpsAlt; ar << m_imageMetadata.exifInfo.GpsLat; ar << m_imageMetadata.exifInfo.GpsLong; - ar << m_imageMetadata.exifInfo.Height; - ar << m_imageMetadata.exifInfo.IsColor; + ar << m_imageMetadata.height; + ar << m_imageMetadata.isColor; ar << m_imageMetadata.exifInfo.ISOequivalent; ar << m_imageMetadata.exifInfo.LightSource; ar << m_imageMetadata.exifInfo.MeteringMode; ar << m_imageMetadata.exifInfo.Orientation; - ar << m_imageMetadata.exifInfo.Process; + ar << m_imageMetadata.encodingProcess; ar << m_imageMetadata.exifInfo.Whitebalance; - ar << m_imageMetadata.exifInfo.Width; + ar << m_imageMetadata.width; ar << m_dateTimeTaken; ar << m_imageMetadata.iptcInfo.Author; @@ -161,15 +161,15 @@ void CPictureInfoTag::Archive(CArchive& ar) ar >> m_imageMetadata.exifInfo.GpsAlt; ar >> m_imageMetadata.exifInfo.GpsLat; ar >> m_imageMetadata.exifInfo.GpsLong; - ar >> m_imageMetadata.exifInfo.Height; - ar >> m_imageMetadata.exifInfo.IsColor; + ar >> m_imageMetadata.height; + ar >> m_imageMetadata.isColor; ar >> m_imageMetadata.exifInfo.ISOequivalent; ar >> m_imageMetadata.exifInfo.LightSource; ar >> m_imageMetadata.exifInfo.MeteringMode; ar >> m_imageMetadata.exifInfo.Orientation; - ar >> m_imageMetadata.exifInfo.Process; + ar >> m_imageMetadata.encodingProcess; ar >> m_imageMetadata.exifInfo.Whitebalance; - ar >> m_imageMetadata.exifInfo.Width; + ar >> m_imageMetadata.width; ar >> m_dateTimeTaken; ar >> m_imageMetadata.iptcInfo.Author; @@ -221,15 +221,15 @@ void CPictureInfoTag::Serialize(CVariant& value) const value["gpsinfo"]["alt"] = m_imageMetadata.exifInfo.GpsAlt; value["gpsinfo"]["lat"] = m_imageMetadata.exifInfo.GpsLat; value["gpsinfo"]["long"] = m_imageMetadata.exifInfo.GpsLong; - value["height"] = m_imageMetadata.exifInfo.Height; - value["iscolor"] = m_imageMetadata.exifInfo.IsColor; + value["height"] = m_imageMetadata.height; + value["iscolor"] = m_imageMetadata.isColor; value["isoequivalent"] = m_imageMetadata.exifInfo.ISOequivalent; value["lightsource"] = m_imageMetadata.exifInfo.LightSource; value["meteringmode"] = m_imageMetadata.exifInfo.MeteringMode; value["orientation"] = m_imageMetadata.exifInfo.Orientation; - value["process"] = m_imageMetadata.exifInfo.Process; + value["process"] = m_imageMetadata.encodingProcess; value["whitebalance"] = m_imageMetadata.exifInfo.Whitebalance; - value["width"] = m_imageMetadata.exifInfo.Width; + value["width"] = m_imageMetadata.width; value["author"] = m_imageMetadata.iptcInfo.Author; value["byline"] = m_imageMetadata.iptcInfo.Byline; @@ -272,20 +272,19 @@ const std::string CPictureInfoTag::GetInfo(int info) const switch (info) { case SLIDESHOW_RESOLUTION: - value = StringUtils::Format("{} x {}", m_imageMetadata.exifInfo.Width, - m_imageMetadata.exifInfo.Height); + value = StringUtils::Format("{} x {}", m_imageMetadata.width, m_imageMetadata.height); break; case SLIDESHOW_COLOUR: - value = m_imageMetadata.exifInfo.IsColor ? "Colour" : "Black and White"; + value = m_imageMetadata.isColor ? "Colour" : "Black and White"; break; case SLIDESHOW_PROCESS: { - auto process = m_imageMetadata.exifInfo.Process; + auto process = m_imageMetadata.encodingProcess; value = !process.empty() ? process : "Unknown"; break; } case SLIDESHOW_COMMENT: - value = m_imageMetadata.exifInfo.FileComment; + value = m_imageMetadata.fileComment; break; case SLIDESHOW_EXIF_COMMENT: value = m_imageMetadata.exifInfo.Comments; @@ -620,8 +619,8 @@ void CPictureInfoTag::SetInfo(const std::string &key, const std::string& value) StringUtils::Tokenize(value, dimension, ","); if (dimension.size() == 2) { - m_imageMetadata.exifInfo.Width = std::atoi(dimension[0].c_str()); - m_imageMetadata.exifInfo.Height = std::atoi(dimension[1].c_str()); + m_imageMetadata.width = std::atoi(dimension[0].c_str()); + m_imageMetadata.height = std::atoi(dimension[1].c_str()); m_isInfoSetExternally = true; // Set the internal state to show metadata has been set by call to SetInfo } From 430afbfaad3f136fb44742a27c9ccc468f28a2f2 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Mon, 13 May 2024 16:50:06 +0100 Subject: [PATCH 159/651] [Pictures] Move metadata extraction to metadata folder --- cmake/treedata/common/subdirs.txt | 1 + xbmc/pictures/CMakeLists.txt | 5 ----- xbmc/pictures/PictureInfoTag.cpp | 2 +- xbmc/pictures/PictureInfoTag.h | 2 +- xbmc/pictures/metadata/CMakeLists.txt | 8 ++++++++ xbmc/pictures/{ => metadata}/Exif.h | 0 xbmc/pictures/{ => metadata}/ImageMetadata.h | 0 xbmc/pictures/{ => metadata}/ImageMetadataParser.cpp | 0 xbmc/pictures/{ => metadata}/ImageMetadataParser.h | 0 xbmc/pictures/{ => metadata}/Iptc.h | 0 10 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 xbmc/pictures/metadata/CMakeLists.txt rename xbmc/pictures/{ => metadata}/Exif.h (100%) rename xbmc/pictures/{ => metadata}/ImageMetadata.h (100%) rename xbmc/pictures/{ => metadata}/ImageMetadataParser.cpp (100%) rename xbmc/pictures/{ => metadata}/ImageMetadataParser.h (100%) rename xbmc/pictures/{ => metadata}/Iptc.h (100%) diff --git a/cmake/treedata/common/subdirs.txt b/cmake/treedata/common/subdirs.txt index 8ead4a28218b3..00bd9396e5467 100644 --- a/cmake/treedata/common/subdirs.txt +++ b/cmake/treedata/common/subdirs.txt @@ -21,6 +21,7 @@ xbmc/imagefiles imagefiles xbmc/messaging messaging xbmc/messaging/helpers messagingHelpers xbmc/pictures pictures +xbmc/pictures/metadata pictures_metadata xbmc/platform platform xbmc/playlists playlists xbmc/powermanagement powermanagement diff --git a/xbmc/pictures/CMakeLists.txt b/xbmc/pictures/CMakeLists.txt index 874f453280b4e..4caa57e05fa78 100644 --- a/xbmc/pictures/CMakeLists.txt +++ b/xbmc/pictures/CMakeLists.txt @@ -2,7 +2,6 @@ set(SOURCES GUIDialogPictureInfo.cpp GUIViewStatePictures.cpp GUIWindowPictures.cpp GUIWindowSlideShow.cpp - ImageMetadataParser.cpp Picture.cpp PictureFolderImageFileLoader.cpp PictureInfoLoader.cpp @@ -13,14 +12,10 @@ set(SOURCES GUIDialogPictureInfo.cpp SlideShowPicture.cpp) set(HEADERS interfaces/ISlideShowDelegate.h - Exif.h GUIDialogPictureInfo.h GUIViewStatePictures.h GUIWindowPictures.h GUIWindowSlideShow.h - ImageMetadata.h - ImageMetadataParser.h - Iptc.h Picture.h PictureFolderImageFileLoader.h PictureInfoLoader.h diff --git a/xbmc/pictures/PictureInfoTag.cpp b/xbmc/pictures/PictureInfoTag.cpp index 4ad8b1f6cb77d..a894cae41f17b 100644 --- a/xbmc/pictures/PictureInfoTag.cpp +++ b/xbmc/pictures/PictureInfoTag.cpp @@ -8,12 +8,12 @@ #include "PictureInfoTag.h" -#include "ImageMetadataParser.h" #include "ServiceBroker.h" #include "addons/ExtsMimeSupportList.h" #include "addons/ImageDecoder.h" #include "addons/addoninfo/AddonType.h" #include "guilib/guiinfo/GUIInfoLabels.h" +#include "pictures/metadata/ImageMetadataParser.h" #include "utils/Archive.h" #include "utils/CharsetConverter.h" #include "utils/StringUtils.h" diff --git a/xbmc/pictures/PictureInfoTag.h b/xbmc/pictures/PictureInfoTag.h index bf74be2b8b0eb..796a741017fc1 100644 --- a/xbmc/pictures/PictureInfoTag.h +++ b/xbmc/pictures/PictureInfoTag.h @@ -8,8 +8,8 @@ #pragma once -#include "ImageMetadata.h" #include "XBDateTime.h" +#include "pictures/metadata/ImageMetadata.h" #include "utils/IArchivable.h" #include "utils/ISerializable.h" #include "utils/ISortable.h" diff --git a/xbmc/pictures/metadata/CMakeLists.txt b/xbmc/pictures/metadata/CMakeLists.txt new file mode 100644 index 0000000000000..0096ee879027e --- /dev/null +++ b/xbmc/pictures/metadata/CMakeLists.txt @@ -0,0 +1,8 @@ +set(SOURCES ImageMetadataParser.cpp) + +set(HEADERS Exif.h + ImageMetadata.h + ImageMetadataParser.h + Iptc.h) + +core_add_library(pictures_metadata) diff --git a/xbmc/pictures/Exif.h b/xbmc/pictures/metadata/Exif.h similarity index 100% rename from xbmc/pictures/Exif.h rename to xbmc/pictures/metadata/Exif.h diff --git a/xbmc/pictures/ImageMetadata.h b/xbmc/pictures/metadata/ImageMetadata.h similarity index 100% rename from xbmc/pictures/ImageMetadata.h rename to xbmc/pictures/metadata/ImageMetadata.h diff --git a/xbmc/pictures/ImageMetadataParser.cpp b/xbmc/pictures/metadata/ImageMetadataParser.cpp similarity index 100% rename from xbmc/pictures/ImageMetadataParser.cpp rename to xbmc/pictures/metadata/ImageMetadataParser.cpp diff --git a/xbmc/pictures/ImageMetadataParser.h b/xbmc/pictures/metadata/ImageMetadataParser.h similarity index 100% rename from xbmc/pictures/ImageMetadataParser.h rename to xbmc/pictures/metadata/ImageMetadataParser.h diff --git a/xbmc/pictures/Iptc.h b/xbmc/pictures/metadata/Iptc.h similarity index 100% rename from xbmc/pictures/Iptc.h rename to xbmc/pictures/metadata/Iptc.h From 5a1495b9e431f11c51800ed5770b6f0cb286bc56 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sun, 19 Nov 2023 20:56:37 +0000 Subject: [PATCH 160/651] Pictures: Add unit tests for metadata extraction --- cmake/installdata/test-reference-data.txt | 2 + cmake/treedata/common/tests.txt | 1 + xbmc/pictures/metadata/test/CMakeLists.txt | 3 + .../metadata/test/TestMetadataExtraction.cpp | 80 ++++++++++++++++++ .../metadata/test/testdata/exifgps.jpg | Bin 0 -> 33550 bytes xbmc/pictures/metadata/test/testdata/iptc.jpg | Bin 0 -> 6026 bytes 6 files changed, 86 insertions(+) create mode 100644 xbmc/pictures/metadata/test/CMakeLists.txt create mode 100644 xbmc/pictures/metadata/test/TestMetadataExtraction.cpp create mode 100644 xbmc/pictures/metadata/test/testdata/exifgps.jpg create mode 100644 xbmc/pictures/metadata/test/testdata/iptc.jpg diff --git a/cmake/installdata/test-reference-data.txt b/cmake/installdata/test-reference-data.txt index b2fafd5914b6b..f7833bbad5c08 100644 --- a/cmake/installdata/test-reference-data.txt +++ b/cmake/installdata/test-reference-data.txt @@ -25,6 +25,8 @@ xbmc/filesystem/test/refRARstored.rar xbmc/network/test/data/test-ranges.txt xbmc/network/test/data/test.html xbmc/network/test/data/test.png +xbmc/pictures/metadata/test/testdata/exifgps.jpg +xbmc/pictures/metadata/test/testdata/iptc.jpg xbmc/playlists/test/test.asx xbmc/playlists/test/test.b4s xbmc/playlists/test/test.pxml diff --git a/cmake/treedata/common/tests.txt b/cmake/treedata/common/tests.txt index 3141b916cdcef..d7068b066d0fc 100644 --- a/cmake/treedata/common/tests.txt +++ b/cmake/treedata/common/tests.txt @@ -12,6 +12,7 @@ xbmc/interfaces/python/test test/python xbmc/music/test test/music xbmc/music/tags/test test/music_tags xbmc/network/test test/network +xbmc/pictures/metadata/test test/pictures/metatada xbmc/playlists/test test/playlists xbmc/pvr/channels/test test/pvrchannels xbmc/settings/test test/settings diff --git a/xbmc/pictures/metadata/test/CMakeLists.txt b/xbmc/pictures/metadata/test/CMakeLists.txt new file mode 100644 index 0000000000000..0ca068eab2402 --- /dev/null +++ b/xbmc/pictures/metadata/test/CMakeLists.txt @@ -0,0 +1,3 @@ +set(SOURCES TestMetadataExtraction.cpp) + +core_add_test_library(pictures_metadata_test) diff --git a/xbmc/pictures/metadata/test/TestMetadataExtraction.cpp b/xbmc/pictures/metadata/test/TestMetadataExtraction.cpp new file mode 100644 index 0000000000000..7b08118dfd7cf --- /dev/null +++ b/xbmc/pictures/metadata/test/TestMetadataExtraction.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 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 "pictures/metadata/ImageMetadataParser.h" +#include "test/TestUtils.h" + +#include + +#include + +class TestMetadataExtraction : public ::testing::Test +{ +protected: + TestMetadataExtraction() = default; +}; + +TEST_F(TestMetadataExtraction, TestGPSImage) +{ + auto path = XBMC_REF_FILE_PATH("xbmc/pictures/metadata/test/testdata/exifgps.jpg"); + std::unique_ptr metadata = CImageMetadataParser::ExtractMetadata(path); + // Extract GPS tags + EXPECT_EQ(metadata->exifInfo.GpsLat, "49° 5' 47.469\" N"); + EXPECT_EQ(metadata->exifInfo.GpsLong, "9° 23' 14.630\" E"); + EXPECT_EQ(metadata->exifInfo.GpsAlt, "333.45m"); + // Check other exif tags this image also includes + EXPECT_EQ(metadata->exifInfo.CameraMake, "SONY"); + EXPECT_EQ(metadata->exifInfo.CameraModel, "SLT-A77V"); + EXPECT_EQ(metadata->exifInfo.DateTime, "2020:04:04 13:32:09"); + EXPECT_EQ(metadata->exifInfo.Orientation, 1); + EXPECT_EQ(metadata->exifInfo.FlashUsed, 16); + EXPECT_EQ(metadata->exifInfo.FocalLength, 20); + EXPECT_EQ(static_cast(metadata->exifInfo.ExposureTime * 1000), 2); + EXPECT_EQ(metadata->exifInfo.ApertureFNumber, 8); + EXPECT_EQ(metadata->exifInfo.MeteringMode, 5); + EXPECT_EQ(metadata->exifInfo.ExposureProgram, 2); + EXPECT_EQ(metadata->exifInfo.Whitebalance, 0); + EXPECT_EQ(metadata->exifInfo.ISOequivalent, 100); + // Check the IPTC tags of this image + EXPECT_EQ(metadata->iptcInfo.Keywords, "blauer Himmel, Ausblick, Parkplatz, Löwenstein, Auto"); + // Generic metadata information + EXPECT_EQ(metadata->width, 1); + EXPECT_EQ(metadata->height, 1); + EXPECT_TRUE(metadata->fileComment.empty()); + // format specific (but common) metadata + EXPECT_TRUE(metadata->isColor); + EXPECT_EQ(metadata->encodingProcess, "Baseline DCT, Huffman coding"); +} + +TEST_F(TestMetadataExtraction, TestIPTC) +{ + auto path = XBMC_REF_FILE_PATH("xbmc/pictures/metadata/test/testdata/iptc.jpg"); + std::unique_ptr metadata = CImageMetadataParser::ExtractMetadata(path); + // Check the IPTC tags of this image + EXPECT_TRUE(metadata->iptcInfo.Keywords.empty()); + EXPECT_EQ(metadata->iptcInfo.RecordVersion, "4"); + EXPECT_EQ( + metadata->iptcInfo.Caption, + "The railways of the S45 line are running very close to a small street with parking cars"); + EXPECT_EQ(metadata->iptcInfo.Headline, "The railway and the cars"); + EXPECT_EQ(metadata->iptcInfo.SpecialInstructions, + "This photo is for metadata testing purposes only"); + EXPECT_EQ(metadata->iptcInfo.Byline, "Jane Photosty"); + EXPECT_EQ(metadata->iptcInfo.Credit, "IPTC/Jane Photosty"); + EXPECT_EQ(metadata->iptcInfo.CopyrightNotice, + "\xa9 Copyright 2020 IPTC (Test Images) - www.iptc.org"); + EXPECT_EQ(metadata->iptcInfo.Date, "2020-01-08"); + EXPECT_EQ(metadata->iptcInfo.TimeCreated, "13:30:01+01:00"); + // Generic metadata information + EXPECT_EQ(metadata->width, 1); + EXPECT_EQ(metadata->height, 1); + EXPECT_TRUE(metadata->fileComment.empty()); + // format specific (but common) metadata + EXPECT_TRUE(metadata->isColor); + EXPECT_EQ(metadata->encodingProcess, "Baseline DCT, Huffman coding"); +} diff --git a/xbmc/pictures/metadata/test/testdata/exifgps.jpg b/xbmc/pictures/metadata/test/testdata/exifgps.jpg new file mode 100644 index 0000000000000000000000000000000000000000..12f850c664b240104b044640b472c769177f8a4e GIT binary patch literal 33550 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

L@Nj&qxD7osN3b+;Kb^a@{MCV1?nZF)~Oe*4Ee zWQ|kl?$urqT4C#nge(`j!ai#c|g@nS`a}9^4^061}6iwU8d0RlhTAFTH*O zgTA%`Udo2OGv&76H-L{&3LW8^h?<4=R@lUfcAgr(Gy>e5Fs4XH8Niv(HX9yNo% ziX9pbKRCym^wxZl;G8X8RBRNHVrKvI^`fi_!v?>Ys^KRX^rqDUm*(N<%9x7L4PKJ& z;|2rluGjK7YLVawgT`>DfN8lcJ$B(5Mek4*TKSEdQtRe+-bbOlXY))8o0AWvh(AIF zrtMmK?bFznBe@St9kVQ&pF>3t#q{xc8Z8ptslI+D=JddmpNQRGHGLP&`#E!hd`&Gq zHs|Cjo$6YZ>e6tij_WphuHKny<~dWLqO>wNIKa%nz>hcDeYyWK&(R+Kz6N3L0S53t zvKb(Ai?9H94K~TJy)vGMp7K#dD>%O+=8VQ-u9oj|d+R z)8$$glQhl4%))#Fd_9BRHN$*Y`|-@exLQ)?WPA z7{v?s3w941<;Th7_e=jr*a5IC|lL@F@I!Q(fvmwS78&vf_w79E!vhxxesN(=7C`%Uy&-X1~zJpUEJ z(*JGnTPn!JxAToOp8))y5&kCS>Y%Q%zc=A(Z?BcXLH^K~xFI_$;p@5F+dUx2ziS%r zHQt_~qkKbF2YX9jOEc<>_?y(rJ-($22nkv(t;li@1JBi-zMg);xJF~X<>U$fmEpJ8 zzmY6{)4>)C*~}rx8&bRDCgN(1(b8-etC@#;u&0+lyyJ1ThOQ2tIOW^(P_N*LT|&Y& zC-ZpTUVfg-xkCd({DUVNwtG0*e9z!u&!9hxJm!1k-~1PjIE_I^p5dK2{vn_;tjrl*8 z`z&{80dGIgzuW@B{(mj+PK5w1#X6#;iIv-I`fMj>&mbNswCz-`meFWK{P+FcEcbw^ z?!h52IJDbOe19_;`e1O#a?d%Q(096|=nUs-nHU?7HyUSPG|sTgYdh#X-;&vXXV)3d z)f#I$a@^<%rens8ga4tcYYcxsukT5xbxGR!f~#dZ(#UwU(O6>>qw$7@)9}FBF}Rt{ zb@$@gdGmrhMr$X}zz%A)_oVioXYiXoWp03nV0hU+x&!gOa~O20{U0NB(O-jan)%0Q zpkfAHl;@8TI<)|@3ij{vT3V|1!W;acYQG(u&FtKH!81L>L;Zu6!_?JL6)ShI@0I#b zt9pCk4gOg@R|lD`^!5yL$D@gd`)a3$jr)&V^iNvxPh0R$ z8tykGmGG5Kw2I_b|X!v22yw*>x{z~2)1TLOPe;BN{1KPiFl;FhN! zAX=dSU4a4j6fh>n4&U1?xn-~imc=r%a^1m-8(;E0*l@{%Eq6~asKyScUOg3h;Rnp5 zJ!!Y@GBVv2q_^xjinp*^$+dg>FT?WK8uqK}}?F7-poa%v@TPg;o*cXsxh+1Z)d!46*y|3Wt& zKde7QilU%L@Ik}Bm8S=7pZl!s2Y9<^pnG4|_cIv;O(}1twvf?iI}8Vc2g6I#4{?Wb zViQgMqKajj~~sjC-4@3R*2~Z$syE=o_1<1GN5_coauc zIj}qEqeh$JkT`CS3^4I*%%N|QkZqZ9YBDA2blMIGI~gIx!dA|dF}^*pht7c?AT%Y}7e zSUSP1LtGa#9#YD2&*5zxknmJ+Mw%j&LYd)f<>ps?r-{)1KKcxdm_u1*14a<~2A&h; zW3(bn3(t?rCzJP=$`(pXI&dmqNd67z|Cah-M28vQekbX-f&K~}bVVx>(BK(TNBN&H zJ$kvg-O=#%&6PIifvM{rD-zp3p+(E@=zS9CrJ|vu%Kd&otWz-T32ixERsH9upH=MG zRrUDJzOf(d(zf&HETl(gPaD?RskLLP_iOn|-|Tk+;f=W8aXn`CtEvPy;a!k*6-yR= zT2-Y>n5NjAhVQ$$R4U%RtpZ4BH_gMnha(8+Rn?KM!gqSx!gKV}&d!c*l=X610}hQ1 zJWl$W$8G^a9!rcp%B&Dv&G9`fFv*GEdYpkJN;<${ zT(K?F3Mt`!D7q2+5q&N@QO|X1iEXLqLL^WnzwC@ARP-d80Jo>AP6a%0c|w5D+H{H6 zs;Yg>rcKgyxmvp-r<c55e{)cK4JtDrC7hK*mWOfx9c;X<5 zFqsU?V2NC0LNj+mYCF7kK1){gb4aZX2dd*rs8` zQQ{MlP>(3FMFx%gMLF!*H?*kSUJ>*{&3WVOM;IKmUC(md zxw0gjlr|?&COK%1k|K1YMatAH9q~;G?{Wr8j3d6}G5MW6CIK{0!n4CC z=R8{%)g{wiX$2^=G{31ivaZv_h!#MJr;qk+EI{B~&gA16!2S;T0pkBq6rhPDalXEZ z&WO?BKAB@aW3Yq<4Lb`TV-KJbjX*KPEDc{!yNDY;hWrEtEo4q!=b(v z-b8ULBoOpAJ{d#>GcbqrdDnqcq&O#nC#Ud5YFDNM$Mocg!)YSQnTxj58rSR^)cM*- zDR#l$UI0CJ%Oc&Okb{y=pA?3XH^^Lgg+Gx?U!ed2y%e&LftTi5nrk5oEX2I@uBEva zvcN*jOYd5mYat6P#Qgtw*Yrw{dT@`|gA2RipJ*4Q6h(YTExnQ!?OG<2Uddj|X2p}c zzIr{E%jdG${CYmWx&|$KedE3LwGHtuK*V%03%+Z)Y))`2)`q{JLJn>?m9$bose+O& zD8paTO?l@j?mx#ZC#W~XZEd0f^+R!mo@nWeU@pv1;fnnaw3b#tO1gmVqr&&MhQI#{ DA#r}` literal 0 HcmV?d00001 From 6a6f589c02b3c4bbeff9f82ae212fdd8d37a84f7 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Tue, 21 Nov 2023 16:22:30 +0000 Subject: [PATCH 161/651] [GUIDialogPictureInfo] Use unique_ptr for exif info --- xbmc/pictures/GUIDialogPictureInfo.cpp | 11 +++-------- xbmc/pictures/GUIDialogPictureInfo.h | 6 ++++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/xbmc/pictures/GUIDialogPictureInfo.cpp b/xbmc/pictures/GUIDialogPictureInfo.cpp index 2a4b8cc665afc..908dfed8ecba7 100644 --- a/xbmc/pictures/GUIDialogPictureInfo.cpp +++ b/xbmc/pictures/GUIDialogPictureInfo.cpp @@ -24,17 +24,12 @@ #define SLIDESHOW_STRING_BASE 21800 - SLIDESHOW_LABELS_START CGUIDialogPictureInfo::CGUIDialogPictureInfo(void) - : CGUIDialog(WINDOW_DIALOG_PICTURE_INFO, "DialogPictureInfo.xml") + : CGUIDialog(WINDOW_DIALOG_PICTURE_INFO, "DialogPictureInfo.xml"), + m_pictureInfo{std::make_unique()} { - m_pictureInfo = new CFileItemList; m_loadType = KEEP_IN_MEMORY; } -CGUIDialogPictureInfo::~CGUIDialogPictureInfo(void) -{ - delete m_pictureInfo; -} - void CGUIDialogPictureInfo::SetPicture(CFileItem *item) { CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPicturesInfoProvider().SetCurrentSlide(item); @@ -102,7 +97,7 @@ void CGUIDialogPictureInfo::UpdatePictureInfo() m_pictureInfo->Add(item); } } - CGUIMessage msg(GUI_MSG_LABEL_BIND, GetID(), CONTROL_PICTURE_INFO, 0, 0, m_pictureInfo); + CGUIMessage msg(GUI_MSG_LABEL_BIND, GetID(), CONTROL_PICTURE_INFO, 0, 0, m_pictureInfo.get()); OnMessage(msg); } diff --git a/xbmc/pictures/GUIDialogPictureInfo.h b/xbmc/pictures/GUIDialogPictureInfo.h index 20e5b3b726d21..8ae7d6e03a523 100644 --- a/xbmc/pictures/GUIDialogPictureInfo.h +++ b/xbmc/pictures/GUIDialogPictureInfo.h @@ -10,6 +10,8 @@ #include "guilib/GUIDialog.h" +#include + class CFileItemList; class CGUIDialogPictureInfo : @@ -17,7 +19,7 @@ class CGUIDialogPictureInfo : { public: CGUIDialogPictureInfo(void); - ~CGUIDialogPictureInfo(void) override; + ~CGUIDialogPictureInfo(void) = default; void SetPicture(CFileItem *item); void FrameMove() override; @@ -27,6 +29,6 @@ class CGUIDialogPictureInfo : bool OnAction(const CAction& action) override; void UpdatePictureInfo(); - CFileItemList* m_pictureInfo; + std::unique_ptr m_pictureInfo; std::string m_currentPicture; }; From 882e7b0ce0f7eecc3ce75a0d5924975e1b58322d Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Tue, 21 Nov 2023 17:15:00 +0000 Subject: [PATCH 162/651] [CGUIDialogPictureInfo] No more translation magic calculations --- .../resources/strings.po | 99 ++++++++++++++++++- addons/skin.estuary/xml/MyPics.xml | 2 +- xbmc/pictures/GUIDialogPictureInfo.cpp | 76 ++++++++++++-- 3 files changed, 163 insertions(+), 14 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index c0cf1830b71d9..c81cd35e55148 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -4284,6 +4284,7 @@ msgid "Video" msgstr "" #: xbmc/dialogs/GUIDialogMediaSource.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#1213" msgid "Pictures" msgstr "" @@ -7286,6 +7287,8 @@ msgstr "" #: system/settings/settings.xml #: xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.cpp +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#13419" msgid "Software" msgstr "" @@ -14951,145 +14954,198 @@ msgstr "" #empty strings from id 21603 to 21799 +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21800" msgid "File name" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21801" msgid "File path" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21802" msgid "File size" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21803" msgid "File date / time" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21804" msgid "Slide index" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21805" msgid "Resolution" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21806" msgid "Comment" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21807" msgid "Colour / B&W" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21808" msgid "JPEG process" msgstr "" #empty strings from id 21809 to 21819 +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21820" msgid "Date / Time" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21821" msgid "Description" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21822" msgid "Camera make" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp #: addons/skin.estuary/xml/Variables.xml +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21823" msgid "Camera model" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21824" msgid "EXIF comment" msgstr "" -msgctxt "#21825" -msgid "Firmware" -msgstr "" +#empty string with id 21825 +#: xbmc/pictures/GUIDialogPictureInfo.cpp #: addons/skin.estuary/xml/Variables.xml +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21826" msgid "Aperture" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp #: addons/skin.estuary/xml/Variables.xml +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21827" msgid "Focal length" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21828" msgid "Focus distance" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21829" msgid "Exposure" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp #: addons/skin.estuary/xml/Variables.xml +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21830" msgid "Exposure time" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21831" msgid "Exposure bias" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21832" msgid "Exposure mode" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21833" msgid "Flash used" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21834" msgid "White-balance" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21835" msgid "Light source" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21836" msgid "Metering mode" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21837" msgid "ISO" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21838" msgid "Digital zoom" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21839" msgid "CCD width" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21840" msgid "GPS latitude" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21841" msgid "GPS longitude" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21842" msgid "GPS altitude" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp #: xbmc/video/dialogs/GUIDialogVideoSettings.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21843" msgid "Orientation" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21844" msgid "XP comment" msgstr "" @@ -15102,100 +15158,137 @@ msgstr "" #empty strings from id 21846 to 21856 +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21857" msgid "Sub-location" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21858" msgid "Image type" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21859" msgid "Time created" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21860" msgid "Supplemental categories" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21861" msgid "Keywords" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21862" msgid "Caption" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21863" msgid "Author" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21864" msgid "Headline" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21865" msgid "Special instructions" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21866" msgid "Category" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21867" msgid "Byline" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21868" msgid "Byline title" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21869" msgid "Credit" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21870" msgid "Source" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21871" msgid "Copyright notice" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21872" msgid "Object name" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21873" msgid "City" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21874" msgid "State" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp #: addons/skin.estuary/xml/DialogVideoInfo.xml +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21875" msgid "Country" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21876" msgid "Original TX reference" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp #: xbmc/playlist/SmartPlayList.cpp msgctxt "#21877" msgid "Date created" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21878" msgid "Urgency" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp +#: addons/skin.estuary/xml/MyPics.xml msgctxt "#21879" msgid "Country code" msgstr "" +#: xbmc/pictures/GUIDialogPictureInfo.cpp msgctxt "#21880" msgid "Reference service" msgstr "" diff --git a/addons/skin.estuary/xml/MyPics.xml b/addons/skin.estuary/xml/MyPics.xml index ba8ef9b7b79d4..a64d5f02e4dc6 100644 --- a/addons/skin.estuary/xml/MyPics.xml +++ b/addons/skin.estuary/xml/MyPics.xml @@ -69,7 +69,7 @@ - + diff --git a/xbmc/pictures/GUIDialogPictureInfo.cpp b/xbmc/pictures/GUIDialogPictureInfo.cpp index 908dfed8ecba7..edc5e1ab93851 100644 --- a/xbmc/pictures/GUIDialogPictureInfo.cpp +++ b/xbmc/pictures/GUIDialogPictureInfo.cpp @@ -19,9 +19,70 @@ #include "input/actions/Action.h" #include "input/actions/ActionIDs.h" -#define CONTROL_PICTURE_INFO 5 +#include -#define SLIDESHOW_STRING_BASE 21800 - SLIDESHOW_LABELS_START +namespace +{ +constexpr unsigned int CONTROL_PICTURE_INFO = 5; + +constexpr std::array slideShowInfoTranslationList{std::pair{SLIDESHOW_FILE_NAME, 21800}, + std::pair{SLIDESHOW_FILE_PATH, 21801}, + std::pair{SLIDESHOW_FILE_SIZE, 21802}, + std::pair{SLIDESHOW_FILE_DATE, 21803}, + std::pair{SLIDESHOW_COLOUR, 21807}, + std::pair{SLIDESHOW_PROCESS, 21808}, + std::pair{SLIDESHOW_INDEX, 21804}, + std::pair{SLIDESHOW_RESOLUTION, 21805}, + std::pair{SLIDESHOW_COMMENT, 21806}, + std::pair{SLIDESHOW_EXIF_DATE_TIME, 21820}, + std::pair{SLIDESHOW_EXIF_DESCRIPTION, 21821}, + std::pair{SLIDESHOW_EXIF_CAMERA_MAKE, 21822}, + std::pair{SLIDESHOW_EXIF_CAMERA_MODEL, 21823}, + std::pair{SLIDESHOW_EXIF_COMMENT, 21824}, + std::pair{SLIDESHOW_EXIF_APERTURE, 21826}, + std::pair{SLIDESHOW_EXIF_FOCAL_LENGTH, 21827}, + std::pair{SLIDESHOW_EXIF_FOCUS_DIST, 21828}, + std::pair{SLIDESHOW_EXIF_EXPOSURE, 21829}, + std::pair{SLIDESHOW_EXIF_EXPOSURE_TIME, 21830}, + std::pair{SLIDESHOW_EXIF_EXPOSURE_BIAS, 21831}, + std::pair{SLIDESHOW_EXIF_EXPOSURE_MODE, 21832}, + std::pair{SLIDESHOW_EXIF_FLASH_USED, 21833}, + std::pair{SLIDESHOW_EXIF_WHITE_BALANCE, 21834}, + std::pair{SLIDESHOW_EXIF_LIGHT_SOURCE, 21835}, + std::pair{SLIDESHOW_EXIF_METERING_MODE, 21836}, + std::pair{SLIDESHOW_EXIF_ISO_EQUIV, 21837}, + std::pair{SLIDESHOW_EXIF_DIGITAL_ZOOM, 21838}, + std::pair{SLIDESHOW_EXIF_CCD_WIDTH, 21839}, + std::pair{SLIDESHOW_EXIF_GPS_LATITUDE, 21840}, + std::pair{SLIDESHOW_EXIF_GPS_LONGITUDE, 21841}, + std::pair{SLIDESHOW_EXIF_GPS_ALTITUDE, 21842}, + std::pair{SLIDESHOW_EXIF_ORIENTATION, 21843}, + std::pair{SLIDESHOW_EXIF_XPCOMMENT, 21844}, + std::pair{SLIDESHOW_IPTC_SUBLOCATION, 21857}, + std::pair{SLIDESHOW_IPTC_IMAGETYPE, 21858}, + std::pair{SLIDESHOW_IPTC_TIMECREATED, 21859}, + std::pair{SLIDESHOW_IPTC_SUP_CATEGORIES, 21860}, + std::pair{SLIDESHOW_IPTC_KEYWORDS, 21861}, + std::pair{SLIDESHOW_IPTC_CAPTION, 21862}, + std::pair{SLIDESHOW_IPTC_AUTHOR, 21863}, + std::pair{SLIDESHOW_IPTC_HEADLINE, 21864}, + std::pair{SLIDESHOW_IPTC_SPEC_INSTR, 21865}, + std::pair{SLIDESHOW_IPTC_CATEGORY, 21866}, + std::pair{SLIDESHOW_IPTC_BYLINE, 21867}, + std::pair{SLIDESHOW_IPTC_BYLINE_TITLE, 21868}, + std::pair{SLIDESHOW_IPTC_CREDIT, 21869}, + std::pair{SLIDESHOW_IPTC_SOURCE, 21870}, + std::pair{SLIDESHOW_IPTC_COPYRIGHT_NOTICE, 21871}, + std::pair{SLIDESHOW_IPTC_OBJECT_NAME, 21872}, + std::pair{SLIDESHOW_IPTC_CITY, 21873}, + std::pair{SLIDESHOW_IPTC_STATE, 21874}, + std::pair{SLIDESHOW_IPTC_COUNTRY, 21875}, + std::pair{SLIDESHOW_IPTC_TX_REFERENCE, 21876}, + std::pair{SLIDESHOW_IPTC_DATE, 21877}, + std::pair{SLIDESHOW_IPTC_URGENCY, 21878}, + std::pair{SLIDESHOW_IPTC_COUNTRY_CODE, 21879}, + std::pair{SLIDESHOW_IPTC_REF_SERVICE, 21880}}; +} // namespace CGUIDialogPictureInfo::CGUIDialogPictureInfo(void) : CGUIDialog(WINDOW_DIALOG_PICTURE_INFO, "DialogPictureInfo.xml"), @@ -81,18 +142,13 @@ void CGUIDialogPictureInfo::UpdatePictureInfo() CGUIMessage msgReset(GUI_MSG_LABEL_RESET, GetID(), CONTROL_PICTURE_INFO); OnMessage(msgReset); m_pictureInfo->Clear(); - for (int info = SLIDESHOW_LABELS_START; info <= SLIDESHOW_LABELS_END; ++info) + for (const auto& [info, code] : slideShowInfoTranslationList) { - // we only want to add SLIDESHOW_EXIF_DATE_TIME - // so we skip the other date formats - if (info == SLIDESHOW_EXIF_DATE || info == SLIDESHOW_EXIF_LONG_DATE || info == SLIDESHOW_EXIF_LONG_DATE_TIME ) - continue; - - std::string picInfo = + const std::string picInfo = CServiceBroker::GetGUI()->GetInfoManager().GetLabel(info, INFO::DEFAULT_CONTEXT); if (!picInfo.empty()) { - CFileItemPtr item(new CFileItem(g_localizeStrings.Get(SLIDESHOW_STRING_BASE + info))); + auto item{std::make_shared(g_localizeStrings.Get(code))}; item->SetLabel2(picInfo); m_pictureInfo->Add(item); } From 0a52bf6f85cb3e97ccff165bb07b312eaa921e21 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sun, 16 Jun 2024 16:16:56 +0100 Subject: [PATCH 163/651] [Pictures] Remove broken Slideshow.EXIFSoftware tag - did nothing previously --- xbmc/GUIInfoManager.cpp | 137 +++++++++++++--------------- xbmc/guilib/guiinfo/GUIInfoLabels.h | 2 +- xbmc/pictures/PictureInfoTag.cpp | 6 +- 3 files changed, 68 insertions(+), 77 deletions(-) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 10826139e68ad..549ea2abc931e 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -9267,14 +9267,6 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// @note This is the value of the EXIF ImageDescription tag (hex code 0x010E). ///

/// } -/// \table_row3{ `Slideshow.EXIFSoftware`, -/// \anchor Slideshow_EXIFSoftware -/// _string_, -/// @return The name and version of the firmware used by the camera that took -/// the current picture. -/// @note This is the value of the EXIF Software tag (hex code 0x0131). -///

-/// } /// \table_row3{ `Slideshow.EXIFTime`, /// \anchor Slideshow_EXIFTime /// _string_, @@ -9652,70 +9644,70 @@ const infomap rds[] = {{ "hasrds", RDS_HAS_RDS }, /// \table_end /// /// ----------------------------------------------------------------------------- -const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED }, - { "isactive", SLIDESHOW_ISACTIVE }, - { "isvideo", SLIDESHOW_ISVIDEO }, - { "israndom", SLIDESHOW_ISRANDOM }, - { "filename", SLIDESHOW_FILE_NAME }, - { "path", SLIDESHOW_FILE_PATH }, - { "filesize", SLIDESHOW_FILE_SIZE }, - { "filedate", SLIDESHOW_FILE_DATE }, - { "slideindex", SLIDESHOW_INDEX }, - { "resolution", SLIDESHOW_RESOLUTION }, - { "slidecomment", SLIDESHOW_COMMENT }, - { "colour", SLIDESHOW_COLOUR }, - { "process", SLIDESHOW_PROCESS }, - { "exiftime", SLIDESHOW_EXIF_DATE_TIME }, - { "exifdate", SLIDESHOW_EXIF_DATE }, - { "longexiftime", SLIDESHOW_EXIF_LONG_DATE_TIME }, - { "longexifdate", SLIDESHOW_EXIF_LONG_DATE }, - { "exifdescription", SLIDESHOW_EXIF_DESCRIPTION }, - { "cameramake", SLIDESHOW_EXIF_CAMERA_MAKE }, - { "cameramodel", SLIDESHOW_EXIF_CAMERA_MODEL }, - { "exifcomment", SLIDESHOW_EXIF_COMMENT }, - { "exifsoftware", SLIDESHOW_EXIF_SOFTWARE }, - { "aperture", SLIDESHOW_EXIF_APERTURE }, - { "focallength", SLIDESHOW_EXIF_FOCAL_LENGTH }, - { "focusdistance", SLIDESHOW_EXIF_FOCUS_DIST }, - { "exposure", SLIDESHOW_EXIF_EXPOSURE }, - { "exposuretime", SLIDESHOW_EXIF_EXPOSURE_TIME }, - { "exposurebias", SLIDESHOW_EXIF_EXPOSURE_BIAS }, - { "exposuremode", SLIDESHOW_EXIF_EXPOSURE_MODE }, - { "flashused", SLIDESHOW_EXIF_FLASH_USED }, - { "whitebalance", SLIDESHOW_EXIF_WHITE_BALANCE }, - { "lightsource", SLIDESHOW_EXIF_LIGHT_SOURCE }, - { "meteringmode", SLIDESHOW_EXIF_METERING_MODE }, - { "isoequivalence", SLIDESHOW_EXIF_ISO_EQUIV }, - { "digitalzoom", SLIDESHOW_EXIF_DIGITAL_ZOOM }, - { "ccdwidth", SLIDESHOW_EXIF_CCD_WIDTH }, - { "orientation", SLIDESHOW_EXIF_ORIENTATION }, - { "supplementalcategories", SLIDESHOW_IPTC_SUP_CATEGORIES }, - { "keywords", SLIDESHOW_IPTC_KEYWORDS }, - { "caption", SLIDESHOW_IPTC_CAPTION }, - { "author", SLIDESHOW_IPTC_AUTHOR }, - { "headline", SLIDESHOW_IPTC_HEADLINE }, - { "specialinstructions", SLIDESHOW_IPTC_SPEC_INSTR }, - { "category", SLIDESHOW_IPTC_CATEGORY }, - { "byline", SLIDESHOW_IPTC_BYLINE }, - { "bylinetitle", SLIDESHOW_IPTC_BYLINE_TITLE }, - { "credit", SLIDESHOW_IPTC_CREDIT }, - { "source", SLIDESHOW_IPTC_SOURCE }, - { "copyrightnotice", SLIDESHOW_IPTC_COPYRIGHT_NOTICE }, - { "objectname", SLIDESHOW_IPTC_OBJECT_NAME }, - { "city", SLIDESHOW_IPTC_CITY }, - { "state", SLIDESHOW_IPTC_STATE }, - { "country", SLIDESHOW_IPTC_COUNTRY }, - { "transmissionreference", SLIDESHOW_IPTC_TX_REFERENCE }, - { "iptcdate", SLIDESHOW_IPTC_DATE }, - { "urgency", SLIDESHOW_IPTC_URGENCY }, - { "countrycode", SLIDESHOW_IPTC_COUNTRY_CODE }, - { "referenceservice", SLIDESHOW_IPTC_REF_SERVICE }, - { "latitude", SLIDESHOW_EXIF_GPS_LATITUDE }, - { "longitude", SLIDESHOW_EXIF_GPS_LONGITUDE }, - { "altitude", SLIDESHOW_EXIF_GPS_ALTITUDE }, - { "timecreated", SLIDESHOW_IPTC_TIMECREATED }, - { "sublocation", SLIDESHOW_IPTC_SUBLOCATION }, - { "imagetype", SLIDESHOW_IPTC_IMAGETYPE }, +const infomap slideshow[] = { + {"ispaused", SLIDESHOW_ISPAUSED}, + {"isactive", SLIDESHOW_ISACTIVE}, + {"isvideo", SLIDESHOW_ISVIDEO}, + {"israndom", SLIDESHOW_ISRANDOM}, + {"filename", SLIDESHOW_FILE_NAME}, + {"path", SLIDESHOW_FILE_PATH}, + {"filesize", SLIDESHOW_FILE_SIZE}, + {"filedate", SLIDESHOW_FILE_DATE}, + {"slideindex", SLIDESHOW_INDEX}, + {"resolution", SLIDESHOW_RESOLUTION}, + {"slidecomment", SLIDESHOW_COMMENT}, + {"colour", SLIDESHOW_COLOUR}, + {"process", SLIDESHOW_PROCESS}, + {"exiftime", SLIDESHOW_EXIF_DATE_TIME}, + {"exifdate", SLIDESHOW_EXIF_DATE}, + {"longexiftime", SLIDESHOW_EXIF_LONG_DATE_TIME}, + {"longexifdate", SLIDESHOW_EXIF_LONG_DATE}, + {"exifdescription", SLIDESHOW_EXIF_DESCRIPTION}, + {"cameramake", SLIDESHOW_EXIF_CAMERA_MAKE}, + {"cameramodel", SLIDESHOW_EXIF_CAMERA_MODEL}, + {"exifcomment", SLIDESHOW_EXIF_COMMENT}, + {"aperture", SLIDESHOW_EXIF_APERTURE}, + {"focallength", SLIDESHOW_EXIF_FOCAL_LENGTH}, + {"focusdistance", SLIDESHOW_EXIF_FOCUS_DIST}, + {"exposure", SLIDESHOW_EXIF_EXPOSURE}, + {"exposuretime", SLIDESHOW_EXIF_EXPOSURE_TIME}, + {"exposurebias", SLIDESHOW_EXIF_EXPOSURE_BIAS}, + {"exposuremode", SLIDESHOW_EXIF_EXPOSURE_MODE}, + {"flashused", SLIDESHOW_EXIF_FLASH_USED}, + {"whitebalance", SLIDESHOW_EXIF_WHITE_BALANCE}, + {"lightsource", SLIDESHOW_EXIF_LIGHT_SOURCE}, + {"meteringmode", SLIDESHOW_EXIF_METERING_MODE}, + {"isoequivalence", SLIDESHOW_EXIF_ISO_EQUIV}, + {"digitalzoom", SLIDESHOW_EXIF_DIGITAL_ZOOM}, + {"ccdwidth", SLIDESHOW_EXIF_CCD_WIDTH}, + {"orientation", SLIDESHOW_EXIF_ORIENTATION}, + {"supplementalcategories", SLIDESHOW_IPTC_SUP_CATEGORIES}, + {"keywords", SLIDESHOW_IPTC_KEYWORDS}, + {"caption", SLIDESHOW_IPTC_CAPTION}, + {"author", SLIDESHOW_IPTC_AUTHOR}, + {"headline", SLIDESHOW_IPTC_HEADLINE}, + {"specialinstructions", SLIDESHOW_IPTC_SPEC_INSTR}, + {"category", SLIDESHOW_IPTC_CATEGORY}, + {"byline", SLIDESHOW_IPTC_BYLINE}, + {"bylinetitle", SLIDESHOW_IPTC_BYLINE_TITLE}, + {"credit", SLIDESHOW_IPTC_CREDIT}, + {"source", SLIDESHOW_IPTC_SOURCE}, + {"copyrightnotice", SLIDESHOW_IPTC_COPYRIGHT_NOTICE}, + {"objectname", SLIDESHOW_IPTC_OBJECT_NAME}, + {"city", SLIDESHOW_IPTC_CITY}, + {"state", SLIDESHOW_IPTC_STATE}, + {"country", SLIDESHOW_IPTC_COUNTRY}, + {"transmissionreference", SLIDESHOW_IPTC_TX_REFERENCE}, + {"iptcdate", SLIDESHOW_IPTC_DATE}, + {"urgency", SLIDESHOW_IPTC_URGENCY}, + {"countrycode", SLIDESHOW_IPTC_COUNTRY_CODE}, + {"referenceservice", SLIDESHOW_IPTC_REF_SERVICE}, + {"latitude", SLIDESHOW_EXIF_GPS_LATITUDE}, + {"longitude", SLIDESHOW_EXIF_GPS_LONGITUDE}, + {"altitude", SLIDESHOW_EXIF_GPS_ALTITUDE}, + {"timecreated", SLIDESHOW_IPTC_TIMECREATED}, + {"sublocation", SLIDESHOW_IPTC_SUBLOCATION}, + {"imagetype", SLIDESHOW_IPTC_IMAGETYPE}, }; /// \page modules__infolabels_boolean_conditions @@ -9895,6 +9887,7 @@ const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED /// \subsection modules_rm_infolabels_booleans_v22 Kodi v22 /// @skinning_v22 **[Removed Infolabels]** The following infolabels have been removed: /// - `Player.Cutlist` - Please use \link Player_Editlist `Player.EditList`\endlink for the EDL list and \link Player_Cuts `Player.Cuts`\endlink for the cut markers +/// - `Slideshow.EXIFSoftware`- This infolabel was broken (did nothing) in previous versions. It might be re-added later. /// ///


/// \subsection modules_rm_infolabels_booleans_v21 Kodi v21 (Omega) diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index b926d860e9b79..f86aad749833d 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -517,7 +517,7 @@ #define SLIDESHOW_EXIF_CAMERA_MAKE (SLIDESHOW_LABELS_START + 22) #define SLIDESHOW_EXIF_CAMERA_MODEL (SLIDESHOW_LABELS_START + 23) #define SLIDESHOW_EXIF_COMMENT (SLIDESHOW_LABELS_START + 24) -#define SLIDESHOW_EXIF_SOFTWARE (SLIDESHOW_LABELS_START + 25) +//empty label (SLIDESHOW_LABELS_START + 25) #define SLIDESHOW_EXIF_APERTURE (SLIDESHOW_LABELS_START + 26) #define SLIDESHOW_EXIF_FOCAL_LENGTH (SLIDESHOW_LABELS_START + 27) #define SLIDESHOW_EXIF_FOCUS_DIST (SLIDESHOW_LABELS_START + 28) diff --git a/xbmc/pictures/PictureInfoTag.cpp b/xbmc/pictures/PictureInfoTag.cpp index a894cae41f17b..3e590d498b06b 100644 --- a/xbmc/pictures/PictureInfoTag.cpp +++ b/xbmc/pictures/PictureInfoTag.cpp @@ -317,8 +317,6 @@ const std::string CPictureInfoTag::GetInfo(int info) const case SLIDESHOW_EXIF_CAMERA_MODEL: value = m_imageMetadata.exifInfo.CameraModel; break; - // case SLIDESHOW_EXIF_SOFTWARE: - // value = m_imageMetadata.exifInfo.Software; case SLIDESHOW_EXIF_APERTURE: if (m_imageMetadata.exifInfo.ApertureFNumber) value = StringUtils::Format("{:3.1f}", m_imageMetadata.exifInfo.ApertureFNumber); @@ -560,8 +558,8 @@ int CPictureInfoTag::TranslateString(const std::string &info) else if (StringUtils::EqualsNoCase(info, "exifdescription")) return SLIDESHOW_EXIF_DESCRIPTION; else if (StringUtils::EqualsNoCase(info, "cameramake")) return SLIDESHOW_EXIF_CAMERA_MAKE; else if (StringUtils::EqualsNoCase(info, "cameramodel")) return SLIDESHOW_EXIF_CAMERA_MODEL; - else if (StringUtils::EqualsNoCase(info, "exifcomment")) return SLIDESHOW_EXIF_COMMENT; - else if (StringUtils::EqualsNoCase(info, "exifsoftware")) return SLIDESHOW_EXIF_SOFTWARE; + else if (StringUtils::EqualsNoCase(info, "exifcomment")) + return SLIDESHOW_EXIF_COMMENT; else if (StringUtils::EqualsNoCase(info, "aperture")) return SLIDESHOW_EXIF_APERTURE; else if (StringUtils::EqualsNoCase(info, "focallength")) return SLIDESHOW_EXIF_FOCAL_LENGTH; else if (StringUtils::EqualsNoCase(info, "focusdistance")) return SLIDESHOW_EXIF_FOCUS_DIST; From a9ba56bb1df27cd26efcb6cd86b02e38bf94d3d4 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sun, 16 Jun 2024 16:47:20 +0100 Subject: [PATCH 164/651] [Docs] Add exiv2 to dependencies in build guides --- docs/README.Fedora.md | 2 +- docs/README.FreeBSD.md | 2 +- docs/README.Linux.md | 2 +- docs/README.Ubuntu.md | 2 +- docs/README.openSUSE.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/README.Fedora.md b/docs/README.Fedora.md index 5f6bf877eb490..3054a88f08e51 100644 --- a/docs/README.Fedora.md +++ b/docs/README.Fedora.md @@ -71,7 +71,7 @@ If you get a `package not found` type of message with the below command, remove Install build dependencies: ``` -sudo dnf install alsa-lib-devel autoconf automake avahi-compat-libdns_sd-devel avahi-devel bluez-libs-devel bzip2-devel cmake curl dbus-devel flatbuffers flatbuffers-devel fmt-devel fontconfig-devel freetype-devel fribidi-devel fstrcmp-devel gawk gcc gcc-c++ gettext gettext-devel giflib-devel gperf gtest-devel java-11-openjdk-headless jre lcms2-devel libao-devel libass-devel libbluray-devel libcap-devel libcdio-devel libcec-devel libcurl-devel libidn2-devel libjpeg-turbo-devel libmicrohttpd-devel libmpc-devel libnfs-devel libplist-devel libpng12-devel libsmbclient-devel libtool libtool-ltdl-devel libudev-devel libunistring libunistring-devel libusb-devel libuuid-devel libva-devel libvdpau-devel libxkbcommon-devel libxml2-devel libXmu-devel libXrandr-devel libxslt-devel libXt-devel lirc-devel lzo-devel make mariadb-devel mesa-libEGL-devel mesa-libGL-devel mesa-libGLU-devel mesa-libGLw-devel mesa-libOSMesa-devel nasm openssl-devel openssl-libs patch pcre2-devel pulseaudio-libs-devel python3-devel python3-pillow rapidjson-devel shairplay-devel spdlog-devel sqlite-devel swig taglib-devel tinyxml-devel tinyxml2-devel trousers-devel uuid-devel zlib-devel +sudo dnf install alsa-lib-devel autoconf automake avahi-compat-libdns_sd-devel avahi-devel bluez-libs-devel bzip2-devel cmake curl dbus-devel exiv2-devel flatbuffers flatbuffers-devel fmt-devel fontconfig-devel freetype-devel fribidi-devel fstrcmp-devel gawk gcc gcc-c++ gettext gettext-devel giflib-devel gperf gtest-devel java-11-openjdk-headless jre lcms2-devel libao-devel libass-devel libbluray-devel libcap-devel libcdio-devel libcec-devel libcurl-devel libidn2-devel libjpeg-turbo-devel libmicrohttpd-devel libmpc-devel libnfs-devel libplist-devel libpng12-devel libsmbclient-devel libtool libtool-ltdl-devel libudev-devel libunistring libunistring-devel libusb-devel libuuid-devel libva-devel libvdpau-devel libxkbcommon-devel libxml2-devel libXmu-devel libXrandr-devel libxslt-devel libXt-devel lirc-devel lzo-devel make mariadb-devel mesa-libEGL-devel mesa-libGL-devel mesa-libGLU-devel mesa-libGLw-devel mesa-libOSMesa-devel nasm openssl-devel openssl-libs patch pcre2-devel pulseaudio-libs-devel python3-devel python3-pillow rapidjson-devel shairplay-devel spdlog-devel sqlite-devel swig taglib-devel tinyxml-devel tinyxml2-devel trousers-devel uuid-devel zlib-devel ``` > [!WARNING] diff --git a/docs/README.FreeBSD.md b/docs/README.FreeBSD.md index 0d36409914172..0c8e83b4f5ff3 100644 --- a/docs/README.FreeBSD.md +++ b/docs/README.FreeBSD.md @@ -82,7 +82,7 @@ If you get a `package not found` type of message with the below command, remove Install build dependencies: ``` -sudo pkg install autoconf automake avahi-app binutils cmake curl dbus doxygen e2fsprogs-libuuid enca encodings evdev-proto ffmpeg flac flatbuffers font-util fontconfig freetype2 fribidi fstrcmp gawk gettext-tools giflib git glew gmake gmp gnutls googletest gperf gstreamer1-vaapi jpeg-turbo libaacs libass libbdplus libbluray libcapn libcdio libcec libdisplay-info libedit libfmt libgcrypt libgpg-error libidn libinotify libinput libmicrohttpd libnfs libogg libplist librtmp libtool libudev-devd libva libvdpau libvorbis libxkbcommon libxslt lirc lzo2 m4 mariadb-connector-c-3.3.8_1 mesa-libs nasm openjdk21 p8-platform pcre2 pkgconf python3 rapidjson shairplay sndio spdlog sqlite3 swig taglib tiff tinyxml tinyxml2 wayland-protocols waylandpp xf86-input-keyboard xf86-input-mouse xorg-server xrandr zip +sudo pkg install autoconf automake avahi-app binutils cmake curl dbus doxygen e2fsprogs-libuuid enca encodings evdev-proto exiv2 ffmpeg flac flatbuffers font-util fontconfig freetype2 fribidi fstrcmp gawk gettext-tools giflib git glew gmake gmp gnutls googletest gperf gstreamer1-vaapi jpeg-turbo libaacs libass libbdplus libbluray libcapn libcdio libcec libdisplay-info libedit libfmt libgcrypt libgpg-error libidn libinotify libinput libmicrohttpd libnfs libogg libplist librtmp libtool libudev-devd libva libvdpau libvorbis libxkbcommon libxslt lirc lzo2 m4 mariadb-connector-c-3.3.8_1 mesa-libs nasm openjdk21 p8-platform pcre2 pkgconf python3 rapidjson shairplay sndio spdlog sqlite3 swig taglib tiff tinyxml tinyxml2 wayland-protocols waylandpp xf86-input-keyboard xf86-input-mouse xorg-server xrandr zip ``` > [!WARNING] diff --git a/docs/README.Linux.md b/docs/README.Linux.md index 45d4d4928d3e6..3d556df73a3bd 100644 --- a/docs/README.Linux.md +++ b/docs/README.Linux.md @@ -80,7 +80,7 @@ The following is the list of packages that are used to build Kodi on Debian/Ubun > [!NOTE] > Kodi requires a compiler with C++17 support, i.e. gcc >= 7 or clang >= 5 -* autoconf, automake, autopoint, gettext, autotools-dev, cmake, curl, default-jre | openjdk-6-jre | openjdk-7-jre, gawk, gcc (>= 7) | gcc-7, g++ (>= 7) | g++-7, cpp (>= 7) | cpp-7, flatbuffers, gdc, gperf, libasound2-dev | libasound-dev, libass-dev (>= 0.9.8), libavahi-client-dev, libavahi-common-dev, libbluetooth-dev, libbluray-dev, libbz2-dev, libcdio-dev, libcec4-dev | libcec-dev, libp8-platform-dev, libcrossguid-dev, libcurl4-openssl-dev | libcurl4-gnutls-dev | libcurl-dev, libcwiid-dev, libdbus-1-dev, libegl1-mesa-dev, libenca-dev, libflac-dev, libfontconfig-dev, libfmt3-dev | libfmt-dev, libfreetype6-dev, libfribidi-dev, libfstrcmp-dev, libgcrypt-dev, libgif-dev (>= 5.0.5), libgles2-mesa-dev [armel] | libgl1-mesa-dev | libgl-dev, libglew-dev, libglu1-mesa-dev | libglu-dev, libgnutls-dev | libgnutls28-dev, libgpg-error-dev, libgtest-dev, libiso9660-dev, libjpeg-dev, liblcms2-dev, liblirc-dev, libltdl-dev, liblzo2-dev, libmicrohttpd-dev, libmysqlclient-dev, libnfs-dev, libogg-dev, libomxil-bellagio-dev [armel], libpcre2-dev, libplist-dev, libpng12-dev | libpng-dev, libpulse-dev, libshairplay-dev, libsmbclient-dev, libspdlog-dev, libsqlite3-dev, libssl-dev, libtag1-dev (>= 1.8) | libtag1x8, libtiff5-dev | libtiff-dev | libtiff4-dev, libtinyxml-dev, libtinyxml2-dev, libtool, libudev-dev, libunistring-dev, libva-dev, libvdpau-dev, libvorbis-dev, libxkbcommon-dev, libxmu-dev, libxrandr-dev, libxslt1-dev | libxslt-dev, libxt-dev, waylandpp-dev | netcat, wayland-protocols | wipe, lsb-release, meson (>= 0.47.0), nasm (>= 2.14), ninja-build, python3-dev, python3-pil | python-imaging, python-support | python3-minimal, rapidjson-dev, swig, unzip, uuid-dev, zip, zlib1g-dev +* autoconf, automake, autopoint, gettext, autotools-dev, cmake, curl, default-jre | openjdk-6-jre | openjdk-7-jre, gawk, gcc (>= 7) | gcc-7, g++ (>= 7) | g++-7, cpp (>= 7) | cpp-7, flatbuffers, gdc, gperf, libasound2-dev | libasound-dev, libass-dev (>= 0.9.8), libavahi-client-dev, libavahi-common-dev, libbluetooth-dev, libbluray-dev, libbz2-dev, libcdio-dev, libcec4-dev | libcec-dev, libp8-platform-dev, libcrossguid-dev, libcurl4-openssl-dev | libcurl4-gnutls-dev | libcurl-dev, libcwiid-dev, libdbus-1-dev, libegl1-mesa-dev, libenca-dev, libexiv2-dev, libflac-dev, libfontconfig-dev, libfmt3-dev | libfmt-dev, libfreetype6-dev, libfribidi-dev, libfstrcmp-dev, libgcrypt-dev, libgif-dev (>= 5.0.5), libgles2-mesa-dev [armel] | libgl1-mesa-dev | libgl-dev, libglew-dev, libglu1-mesa-dev | libglu-dev, libgnutls-dev | libgnutls28-dev, libgpg-error-dev, libgtest-dev, libiso9660-dev, libjpeg-dev, liblcms2-dev, liblirc-dev, libltdl-dev, liblzo2-dev, libmicrohttpd-dev, libmysqlclient-dev, libnfs-dev, libogg-dev, libomxil-bellagio-dev [armel], libpcre2-dev, libplist-dev, libpng12-dev | libpng-dev, libpulse-dev, libshairplay-dev, libsmbclient-dev, libspdlog-dev, libsqlite3-dev, libssl-dev, libtag1-dev (>= 1.8) | libtag1x8, libtiff5-dev | libtiff-dev | libtiff4-dev, libtinyxml-dev, libtinyxml2-dev, libtool, libudev-dev, libunistring-dev, libva-dev, libvdpau-dev, libvorbis-dev, libxkbcommon-dev, libxmu-dev, libxrandr-dev, libxslt1-dev | libxslt-dev, libxt-dev, waylandpp-dev | netcat, wayland-protocols | wipe, lsb-release, meson (>= 0.47.0), nasm (>= 2.14), ninja-build, python3-dev, python3-pil | python-imaging, python-support | python3-minimal, rapidjson-dev, swig, unzip, uuid-dev, zip, zlib1g-dev ### 3.1. Build missing dependencies Some packages may be missing or outdated in older distributions. Notably `crossguid`, `libfmt`, `libspdlog`, `waylandpp`, `wayland-protocols`, etc. are known to be outdated or missing. Fortunately there is an easy way to build individual dependencies with **[Kodi's unified depends build system](../tools/depends/README.md)**. diff --git a/docs/README.Ubuntu.md b/docs/README.Ubuntu.md index 6b991db76e6b1..38801cb07a7ac 100644 --- a/docs/README.Ubuntu.md +++ b/docs/README.Ubuntu.md @@ -126,7 +126,7 @@ If you get a `package not found` type of message with the below command, remove Install build dependencies manually: ``` -sudo apt install debhelper autoconf automake autopoint gettext autotools-dev cmake curl default-jre doxygen gawk gcc gdc gperf libasound2-dev libass-dev libavahi-client-dev libavahi-common-dev libbluetooth-dev libbluray-dev libbz2-dev libcdio-dev libp8-platform-dev libcrossguid-dev libcurl4-openssl-dev libcwiid-dev libdbus-1-dev libdrm-dev libegl1-mesa-dev libenca-dev libflac-dev libfmt-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgcrypt-dev libgif-dev libgles2-mesa-dev libgl1-mesa-dev libglu1-mesa-dev libgnutls28-dev libgpg-error-dev libgtest-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libogg-dev libpcre2-dev libplist-dev libpng-dev libpulse-dev libshairplay-dev libsmbclient-dev libspdlog-dev libsqlite3-dev libssl-dev libtag1-dev libtiff5-dev libtinyxml-dev libtinyxml2-dev libtool libudev-dev libunistring-dev libva-dev libvdpau-dev libvorbis-dev libxmu-dev libxrandr-dev libxslt1-dev libxt-dev lsb-release meson nasm ninja-build python3-dev python3-pil python3-pip rapidjson-dev swig unzip uuid-dev zip zlib1g-dev +sudo apt install debhelper autoconf automake autopoint gettext autotools-dev cmake curl default-jre doxygen gawk gcc gdc gperf libasound2-dev libass-dev libavahi-client-dev libavahi-common-dev libbluetooth-dev libbluray-dev libbz2-dev libcdio-dev libp8-platform-dev libcrossguid-dev libcurl4-openssl-dev libcwiid-dev libdbus-1-dev libdrm-dev libegl1-mesa-dev libenca-dev libexiv2-dev libflac-dev libfmt-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgcrypt-dev libgif-dev libgles2-mesa-dev libgl1-mesa-dev libglu1-mesa-dev libgnutls28-dev libgpg-error-dev libgtest-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libogg-dev libpcre2-dev libplist-dev libpng-dev libpulse-dev libshairplay-dev libsmbclient-dev libspdlog-dev libsqlite3-dev libssl-dev libtag1-dev libtiff5-dev libtinyxml-dev libtinyxml2-dev libtool libudev-dev libunistring-dev libva-dev libvdpau-dev libvorbis-dev libxmu-dev libxrandr-dev libxslt1-dev libxt-dev lsb-release meson nasm ninja-build python3-dev python3-pil python3-pip rapidjson-dev swig unzip uuid-dev zip zlib1g-dev ``` > [!WARNING] diff --git a/docs/README.openSUSE.md b/docs/README.openSUSE.md index a8b621e1c4a66..86d1ca62b6584 100644 --- a/docs/README.openSUSE.md +++ b/docs/README.openSUSE.md @@ -83,7 +83,7 @@ If you get a `package not found` type of message with the below command, remove Install build dependencies: ``` -sudo zypper install alsa-devel autoconf automake bluez-devel boost-devel capi4linux-devel ccache cmake doxygen flac-devel fribidi-devel fstrcmp-devel gcc gcc-c++ gettext-devel giflib-devel glew-devel googletest gperf java-openjdk libass-devel libavahi-devel libbluray-devel libbz2-devel libcap-devel libcap-ng-devel libcdio-devel libcec-devel libcurl-devel libdvdread-devel libgudev-1_0-devel libidn2-devel libjasper-devel libjpeg-devel liblcms2-devel libmad-devel libmicrohttpd-devel libmodplug-devel libmpeg2-devel libmysqlclient-devel libnfs-devel libogg-devel libpcap-devel libplist-devel libpng12-devel libpulse-devel libsamplerate-devel libsmbclient-devel libtag-devel libtiff-devel libtool libudev-devel libuuid-devel libva-devel libvdpau-devel libvorbis-devel libXrandr-devel libXrender-devel libxslt-devel lirc-devel lzo-devel make Mesa-libEGL-devel Mesa-libGLESv2-devel Mesa-libGLESv3-devel nasm patch pcre2-devel python3-devel python3-Pillow randrproto-devel renderproto-devel shairplay-devel sqlite3-devel swig tinyxml-devel tinyxml2-devel +sudo zypper install alsa-devel autoconf automake bluez-devel boost-devel capi4linux-devel ccache cmake doxygen flac-devel fribidi-devel fstrcmp-devel gcc gcc-c++ gettext-devel giflib-devel glew-devel googletest gperf java-openjdk libass-devel libavahi-devel libbluray-devel libbz2-devel libcap-devel libcap-ng-devel libcdio-devel libcec-devel libcurl-devel libdvdread-devel libexiv2-devel libgudev-1_0-devel libidn2-devel libjasper-devel libjpeg-devel liblcms2-devel libmad-devel libmicrohttpd-devel libmodplug-devel libmpeg2-devel libmysqlclient-devel libnfs-devel libogg-devel libpcap-devel libplist-devel libpng12-devel libpulse-devel libsamplerate-devel libsmbclient-devel libtag-devel libtiff-devel libtool libudev-devel libuuid-devel libva-devel libvdpau-devel libvorbis-devel libXrandr-devel libXrender-devel libxslt-devel lirc-devel lzo-devel make Mesa-libEGL-devel Mesa-libGLESv2-devel Mesa-libGLESv3-devel nasm patch pcre2-devel python3-devel python3-Pillow randrproto-devel renderproto-devel shairplay-devel sqlite3-devel swig tinyxml-devel tinyxml2-devel ``` > [!WARNING] From b446e55f659a1dbe114515d8abe2f6854ac07697 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 19 Jun 2024 11:31:07 +0100 Subject: [PATCH 165/651] [EDL] Remove const (fixme implementation) --- xbmc/cores/VideoPlayer/Edl.cpp | 22 +++++++++++----------- xbmc/cores/VideoPlayer/Edl.h | 29 +++++++---------------------- 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/xbmc/cores/VideoPlayer/Edl.cpp b/xbmc/cores/VideoPlayer/Edl.cpp index 01c7ed09b1912..309464ec16328 100644 --- a/xbmc/cores/VideoPlayer/Edl.cpp +++ b/xbmc/cores/VideoPlayer/Edl.cpp @@ -43,7 +43,7 @@ void CEdl::Clear() m_lastEditTime = -1; } -bool CEdl::ReadEditDecisionLists(const CFileItem& fileItem, const float fFramesPerSecond) +bool CEdl::ReadEditDecisionLists(const CFileItem& fileItem, float fps) { bool bFound = false; @@ -66,10 +66,10 @@ bool CEdl::ReadEditDecisionLists(const CFileItem& fileItem, const float fFramesP bFound = ReadVideoReDo(strMovie); if (!bFound) - bFound = ReadEdl(strMovie, fFramesPerSecond); + bFound = ReadEdl(strMovie, fps); if (!bFound) - bFound = ReadComskip(strMovie, fFramesPerSecond); + bFound = ReadComskip(strMovie, fps); if (!bFound) bFound = ReadBeyondTV(strMovie); @@ -88,7 +88,7 @@ bool CEdl::ReadEditDecisionLists(const CFileItem& fileItem, const float fFramesP return bFound; } -bool CEdl::ReadEdl(const std::string& strMovie, const float fFramesPerSecond) +bool CEdl::ReadEdl(const std::string& strMovie, float fps) { Clear(); @@ -190,10 +190,10 @@ bool CEdl::ReadEdl(const std::string& strMovie, const float fFramesPerSecond) } else if (strFields[i][0] == '#') // #12345 format for frame number { - if (fFramesPerSecond > 0.0f) + if (fps > 0.0f) { - editStartEnd[i] = static_cast(std::atol(strFields[i].substr(1).c_str()) / - fFramesPerSecond * 1000); // frame number to ms + editStartEnd[i] = static_cast(std::atol(strFields[i].substr(1).c_str()) / fps * + 1000); // frame number to ms } else { @@ -281,7 +281,7 @@ bool CEdl::ReadEdl(const std::string& strMovie, const float fFramesPerSecond) } } -bool CEdl::ReadComskip(const std::string& strMovie, const float fFramesPerSecond) +bool CEdl::ReadComskip(const std::string& strMovie, float fps) { Clear(); @@ -315,9 +315,9 @@ bool CEdl::ReadComskip(const std::string& strMovie, const float fFramesPerSecond /* * Not all generated Comskip files have the frame rate information. */ - if (fFramesPerSecond > 0.0f) + if (fps > 0.0f) { - fFrameRate = fFramesPerSecond; + fFrameRate = fps; CLog::Log(LOGWARNING, "Edl::ReadComskip - Frame rate not in Comskip file. Using detected frames per " "second: {:.3f}", @@ -707,7 +707,7 @@ bool CEdl::AddEdit(const Edit& newEdit) return true; } -bool CEdl::AddSceneMarker(const int iSceneMarker) +bool CEdl::AddSceneMarker(int iSceneMarker) { Edit edit; diff --git a/xbmc/cores/VideoPlayer/Edl.h b/xbmc/cores/VideoPlayer/Edl.h index faf3e732f1ae0..a97c284799d77 100644 --- a/xbmc/cores/VideoPlayer/Edl.h +++ b/xbmc/cores/VideoPlayer/Edl.h @@ -20,10 +20,7 @@ class CEdl public: CEdl(); - // FIXME: remove const modifier for fFramesPerSecond as it makes no sense as it means nothing - // for the reader of the interface, but limits the implementation - // to not modify the parameter on stack - bool ReadEditDecisionLists(const CFileItem& fileItem, const float fFramesPerSecond); + bool ReadEditDecisionLists(const CFileItem& fileItem, float fps); void Clear(); /*! @@ -170,21 +167,12 @@ class CEdl */ EDL::Action m_lastEditActionType{EDL::EDL_ACTION_NONE}; - // FIXME: remove const modifier for fFramesPerSecond as it makes no sense as it means nothing - // for the reader of the interface, but limits the implementation - // to not modify the parameter on stack - bool ReadEdl(const std::string& strMovie, const float fFramesPerSecond); - // FIXME: remove const modifier for fFramesPerSecond as it makes no sense as it means nothing - // for the reader of the interface, but limits the implementation - // to not modify the parameter on stack - bool ReadComskip(const std::string& strMovie, const float fFramesPerSecond); - // FIXME: remove const modifier for strMovie as it makes no sense as it means nothing - // for the reader of the interface, but limits the implementation - // to not modify the parameter on stack + bool ReadEdl(const std::string& strMovie, float fps); + + bool ReadComskip(const std::string& strMovie, float fps); + bool ReadVideoReDo(const std::string& strMovie); - // FIXME: remove const modifier for strMovie as it makes no sense as it means nothing - // for the reader of the interface, but limits the implementation - // to not modify the parameter on stack + bool ReadBeyondTV(const std::string& strMovie); bool ReadPvr(const CFileItem& fileItem); @@ -195,10 +183,7 @@ class CEdl */ bool AddEdit(const EDL::Edit& newEdit); - // FIXME: remove const modifier for strMovie as it makes no sense as it means nothing - // for the reader of the interface, but limits the implementation - // to not modify the parameter on stack - bool AddSceneMarker(const int sceneMarker); + bool AddSceneMarker(int sceneMarker); void MergeShortCommBreaks(); From 38baff701ca7e1eefce3626336d61ebf7c356d0e Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 19 Jun 2024 11:33:32 +0100 Subject: [PATCH 166/651] [EDL] Remove const in MillisecondsToTimeString --- xbmc/cores/VideoPlayer/Edl.cpp | 7 ++++--- xbmc/cores/VideoPlayer/Edl.h | 5 +---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/xbmc/cores/VideoPlayer/Edl.cpp b/xbmc/cores/VideoPlayer/Edl.cpp index 309464ec16328..e8520a7253752 100644 --- a/xbmc/cores/VideoPlayer/Edl.cpp +++ b/xbmc/cores/VideoPlayer/Edl.cpp @@ -928,10 +928,11 @@ bool CEdl::GetNextSceneMarker(bool bPlus, const int iClock, int *iSceneMarker) return bFound; } -std::string CEdl::MillisecondsToTimeString(const int iMilliseconds) +std::string CEdl::MillisecondsToTimeString(int milliSeconds) { - std::string strTimeString = StringUtils::SecondsToTimeString((long)(iMilliseconds / 1000), TIME_FORMAT_HH_MM_SS); // milliseconds to seconds - strTimeString += StringUtils::Format(".{:03}", iMilliseconds % 1000); + std::string strTimeString = StringUtils::SecondsToTimeString( + static_cast(milliSeconds / 1000), TIME_FORMAT_HH_MM_SS); // milliseconds to seconds + strTimeString += StringUtils::Format(".{:03}", milliSeconds % 1000); return strTimeString; } diff --git a/xbmc/cores/VideoPlayer/Edl.h b/xbmc/cores/VideoPlayer/Edl.h index a97c284799d77..21bb64ebecad5 100644 --- a/xbmc/cores/VideoPlayer/Edl.h +++ b/xbmc/cores/VideoPlayer/Edl.h @@ -146,10 +146,7 @@ class CEdl // to not modify the parameter on stack bool GetNextSceneMarker(bool bPlus, const int iClock, int *iSceneMarker); - // FIXME: remove const modifier as it makes no sense as it means nothing - // for the reader of the interface, but limits the implementation - // to not modify the parameter on stack - static std::string MillisecondsToTimeString(const int iMilliseconds); + static std::string MillisecondsToTimeString(int milliSeconds); private: // total cut time (edl cuts) in ms From 16cff7de79e6f04d996d4c55f9376a698986dfdd Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 19 Jun 2024 11:38:03 +0100 Subject: [PATCH 167/651] [EDL] Remove hungarian notation on the interface file --- xbmc/cores/VideoPlayer/Edl.cpp | 17 +++++++++-------- xbmc/cores/VideoPlayer/Edl.h | 9 +++++---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/xbmc/cores/VideoPlayer/Edl.cpp b/xbmc/cores/VideoPlayer/Edl.cpp index e8520a7253752..7a606d0c9a4fa 100644 --- a/xbmc/cores/VideoPlayer/Edl.cpp +++ b/xbmc/cores/VideoPlayer/Edl.cpp @@ -88,11 +88,11 @@ bool CEdl::ReadEditDecisionLists(const CFileItem& fileItem, float fps) return bFound; } -bool CEdl::ReadEdl(const std::string& strMovie, float fps) +bool CEdl::ReadEdl(const std::string& mediaFilePath, float fps) { Clear(); - std::string edlFilename(URIUtils::ReplaceExtension(strMovie, ".edl")); + const std::string edlFilename(URIUtils::ReplaceExtension(mediaFilePath, ".edl")); if (!CFile::Exists(edlFilename)) return false; @@ -281,11 +281,11 @@ bool CEdl::ReadEdl(const std::string& strMovie, float fps) } } -bool CEdl::ReadComskip(const std::string& strMovie, float fps) +bool CEdl::ReadComskip(const std::string& mediaFilePath, float fps) { Clear(); - std::string comskipFilename(URIUtils::ReplaceExtension(strMovie, ".txt")); + const std::string comskipFilename(URIUtils::ReplaceExtension(mediaFilePath, ".txt")); if (!CFile::Exists(comskipFilename)) return false; @@ -376,7 +376,7 @@ bool CEdl::ReadComskip(const std::string& strMovie, float fps) } } -bool CEdl::ReadVideoReDo(const std::string& strMovie) +bool CEdl::ReadVideoReDo(const std::string& mediaFilePath) { /* * VideoReDo file is strange. Tags are XML like, but it isn't an XML file. @@ -385,7 +385,7 @@ bool CEdl::ReadVideoReDo(const std::string& strMovie) */ Clear(); - std::string videoReDoFilename(URIUtils::ReplaceExtension(strMovie, ".Vprj")); + const std::string videoReDoFilename(URIUtils::ReplaceExtension(mediaFilePath, ".Vprj")); if (!CFile::Exists(videoReDoFilename)) return false; @@ -474,11 +474,12 @@ bool CEdl::ReadVideoReDo(const std::string& strMovie) } } -bool CEdl::ReadBeyondTV(const std::string& strMovie) +bool CEdl::ReadBeyondTV(const std::string& mediaFilePath) { Clear(); - std::string beyondTVFilename(URIUtils::ReplaceExtension(strMovie, URIUtils::GetExtension(strMovie) + ".chapters.xml")); + const std::string beyondTVFilename(URIUtils::ReplaceExtension( + mediaFilePath, URIUtils::GetExtension(mediaFilePath) + ".chapters.xml")); if (!CFile::Exists(beyondTVFilename)) return false; diff --git a/xbmc/cores/VideoPlayer/Edl.h b/xbmc/cores/VideoPlayer/Edl.h index 21bb64ebecad5..f27ad8b7339d2 100644 --- a/xbmc/cores/VideoPlayer/Edl.h +++ b/xbmc/cores/VideoPlayer/Edl.h @@ -164,13 +164,14 @@ class CEdl */ EDL::Action m_lastEditActionType{EDL::EDL_ACTION_NONE}; - bool ReadEdl(const std::string& strMovie, float fps); + bool ReadEdl(const std::string& mediaFilePath, float fps); - bool ReadComskip(const std::string& strMovie, float fps); + bool ReadComskip(const std::string& mediaFilePath, float fps); - bool ReadVideoReDo(const std::string& strMovie); + bool ReadVideoReDo(const std::string& mediaFilePath); + + bool ReadBeyondTV(const std::string& mediaFilePath); - bool ReadBeyondTV(const std::string& strMovie); bool ReadPvr(const CFileItem& fileItem); /*! From 2b0446649729fd698424d7b32867d4355ab991d9 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 19 Jun 2024 12:00:14 +0100 Subject: [PATCH 168/651] [EDL][Players] Move seek direction to enum class --- xbmc/SeekHandler.cpp | 4 ++-- xbmc/application/ApplicationPlayer.cpp | 4 ++-- xbmc/application/ApplicationPlayer.h | 2 +- xbmc/cores/EdlEdit.h | 9 +++++++++ xbmc/cores/IPlayer.h | 14 +++++++++++++- xbmc/cores/VideoPlayer/Edl.cpp | 6 +++--- xbmc/cores/VideoPlayer/Edl.h | 2 +- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 15 +++++++++------ xbmc/cores/VideoPlayer/VideoPlayer.h | 2 +- xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp | 8 ++++---- 10 files changed, 45 insertions(+), 21 deletions(-) diff --git a/xbmc/SeekHandler.cpp b/xbmc/SeekHandler.cpp index 3e93694b1628b..fb773b0eb06c2 100644 --- a/xbmc/SeekHandler.cpp +++ b/xbmc/SeekHandler.cpp @@ -311,12 +311,12 @@ bool CSeekHandler::OnAction(const CAction &action) } case ACTION_NEXT_SCENE: { - appPlayer->SeekScene(true); + appPlayer->SeekScene(PlayerSeekDirection::FORWARD); return true; } case ACTION_PREV_SCENE: { - appPlayer->SeekScene(false); + appPlayer->SeekScene(PlayerSeekDirection::BACKWARD); return true; } case ACTION_ANALOG_SEEK_FORWARD: diff --git a/xbmc/application/ApplicationPlayer.cpp b/xbmc/application/ApplicationPlayer.cpp index 18e78d3c0cf45..a2ab7b5f455ec 100644 --- a/xbmc/application/ApplicationPlayer.cpp +++ b/xbmc/application/ApplicationPlayer.cpp @@ -327,10 +327,10 @@ bool CApplicationPlayer::CanSeek() const return (player && player->CanSeek()); } -bool CApplicationPlayer::SeekScene(bool bPlus) +bool CApplicationPlayer::SeekScene(PlayerSeekDirection seekDirection) { std::shared_ptr player = GetInternal(); - return (player && player->SeekScene(bPlus)); + return (player && player->SeekScene(seekDirection)); } void CApplicationPlayer::SeekTime(int64_t iTime) diff --git a/xbmc/application/ApplicationPlayer.h b/xbmc/application/ApplicationPlayer.h index d695f66aef818..484e5d52ac4e9 100644 --- a/xbmc/application/ApplicationPlayer.h +++ b/xbmc/application/ApplicationPlayer.h @@ -144,7 +144,7 @@ class CApplicationPlayer : public IApplicationComponent void Seek(bool bPlus = true, bool bLargeStep = false, bool bChapterOverride = false); int SeekChapter(int iChapter); void SeekPercentage(float fPercent = 0); - bool SeekScene(bool bPlus = true); + bool SeekScene(PlayerSeekDirection seekDirection = PlayerSeekDirection::FORWARD); void SeekTime(int64_t iTime = 0); void SeekTimeRelative(int64_t iTime = 0); void SetAudioStream(int iStream); diff --git a/xbmc/cores/EdlEdit.h b/xbmc/cores/EdlEdit.h index a06ed1572a1fc..73d3b791623dd 100644 --- a/xbmc/cores/EdlEdit.h +++ b/xbmc/cores/EdlEdit.h @@ -28,4 +28,13 @@ struct Edit Action action = Action::CUT; }; +/*! + * @brief Specify the search direction for an EDL edit +*/ +enum class EditDirection +{ + FORWARD, /*!< Search forward */ + BACKWARD /*!< Search backwards */ +}; + } // namespace EDL diff --git a/xbmc/cores/IPlayer.h b/xbmc/cores/IPlayer.h index a782dd72ac812..e41d704b9490b 100644 --- a/xbmc/cores/IPlayer.h +++ b/xbmc/cores/IPlayer.h @@ -84,6 +84,15 @@ enum ERENDERFEATURE RENDERFEATURE_TONEMAP }; +/*! + * @brief Specify the Seek Direction +*/ +enum class PlayerSeekDirection +{ + FORWARD, /*!< Seek forward */ + BACKWARD /*!< Seek backwards */ +}; + class IPlayer { public: @@ -105,7 +114,10 @@ class IPlayer virtual bool IsPassthrough() const { return false;} virtual bool CanSeek() const { return true; } virtual void Seek(bool bPlus = true, bool bLargeStep = false, bool bChapterOverride = false) = 0; - virtual bool SeekScene(bool bPlus = true) {return false;} + virtual bool SeekScene(PlayerSeekDirection seekDirection = PlayerSeekDirection::FORWARD) + { + return false; + } virtual void SeekPercentage(float fPercent = 0){} virtual float GetCachePercentage() const { return 0; } virtual void SetMute(bool bOnOff){} diff --git a/xbmc/cores/VideoPlayer/Edl.cpp b/xbmc/cores/VideoPlayer/Edl.cpp index 7a606d0c9a4fa..ef3c36276c9f5 100644 --- a/xbmc/cores/VideoPlayer/Edl.cpp +++ b/xbmc/cores/VideoPlayer/Edl.cpp @@ -883,7 +883,7 @@ EDL::Action CEdl::GetLastEditActionType() const return m_lastEditActionType; } -bool CEdl::GetNextSceneMarker(bool bPlus, const int iClock, int *iSceneMarker) +bool CEdl::GetNextSceneMarker(EDL::EditDirection direction, const int iClock, int* iSceneMarker) { if (!HasSceneMarker()) return false; @@ -893,7 +893,7 @@ bool CEdl::GetNextSceneMarker(bool bPlus, const int iClock, int *iSceneMarker) int iDiff = 10 * 60 * 60 * 1000; // 10 hours to ms. bool bFound = false; - if (bPlus) // Find closest scene forwards + if (direction == EDL::EditDirection::FORWARD) // Find closest scene forwards { for (int i = 0; i < (int)m_vecSceneMarkers.size(); i++) { @@ -905,7 +905,7 @@ bool CEdl::GetNextSceneMarker(bool bPlus, const int iClock, int *iSceneMarker) } } } - else // Find closest scene backwards + else if (direction == EDL::EditDirection::BACKWARD) // Find closest scene backwards { for (int i = 0; i < (int)m_vecSceneMarkers.size(); i++) { diff --git a/xbmc/cores/VideoPlayer/Edl.h b/xbmc/cores/VideoPlayer/Edl.h index f27ad8b7339d2..8c765f1a77a1b 100644 --- a/xbmc/cores/VideoPlayer/Edl.h +++ b/xbmc/cores/VideoPlayer/Edl.h @@ -144,7 +144,7 @@ class CEdl // FIXME: remove const modifier for iClock as it makes no sense as it means nothing // for the reader of the interface, but limits the implementation // to not modify the parameter on stack - bool GetNextSceneMarker(bool bPlus, const int iClock, int *iSceneMarker); + bool GetNextSceneMarker(EDL::EditDirection direction, const int iClock, int* iSceneMarker); static std::string MillisecondsToTimeString(int milliSeconds); diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index d2568bd710829..aa7e46e066148 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -3254,7 +3254,7 @@ void CVideoPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride) m_callback.OnPlayBackSeek(seekTarget, seekTarget - time); } -bool CVideoPlayer::SeekScene(bool bPlus) +bool CVideoPlayer::SeekScene(PlayerSeekDirection seekDirection) { if (!m_Edl.HasSceneMarker()) return false; @@ -3264,18 +3264,21 @@ bool CVideoPlayer::SeekScene(bool bPlus) * grace period applied it is impossible to go backwards past a scene marker. */ int64_t clock = GetTime(); - if (!bPlus && clock > 5 * 1000) // 5 seconds + if (seekDirection == PlayerSeekDirection::BACKWARD && clock > 5 * 1000) // 5 seconds clock -= 5 * 1000; int iScenemarker; - if (m_Edl.GetNextSceneMarker(bPlus, clock, &iScenemarker)) + if (m_Edl.GetNextSceneMarker(seekDirection == PlayerSeekDirection::FORWARD + ? EDL::EditDirection::FORWARD + : EDL::EditDirection::BACKWARD, + clock, &iScenemarker)) { /* * Seeking is flushed and inaccurate, just like Seek() */ CDVDMsgPlayerSeek::CMode mode; mode.time = iScenemarker; - mode.backward = !bPlus; + mode.backward = seekDirection == PlayerSeekDirection::BACKWARD; mode.accurate = false; mode.restore = false; mode.trickplay = false; @@ -4527,7 +4530,7 @@ bool CVideoPlayer::OnAction(const CAction &action) m_processInfo->SeekFinished(0); return true; } - else if (SeekScene(true)) + else if (SeekScene(PlayerSeekDirection::FORWARD)) return true; else break; @@ -4538,7 +4541,7 @@ bool CVideoPlayer::OnAction(const CAction &action) m_processInfo->SeekFinished(0); return true; } - else if (SeekScene(false)) + else if (SeekScene(PlayerSeekDirection::BACKWARD)) return true; else break; diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.h b/xbmc/cores/VideoPlayer/VideoPlayer.h index 1300b0fd4ee09..c09d60d125357 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.h +++ b/xbmc/cores/VideoPlayer/VideoPlayer.h @@ -264,7 +264,7 @@ class CVideoPlayer : public IPlayer, public CThread, public IVideoPlayer, bool IsPassthrough() const override; bool CanSeek() const override; void Seek(bool bPlus, bool bLargeStep, bool bChapterOverride) override; - bool SeekScene(bool bPlus = true) override; + bool SeekScene(PlayerSeekDirection seekDirection = PlayerSeekDirection::FORWARD) override; void SeekPercentage(float iPercent) override; float GetCachePercentage() const override; diff --git a/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp b/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp index 7098b9144e6d7..b804cc3597d8c 100644 --- a/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp +++ b/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp @@ -86,14 +86,14 @@ TEST_F(TestEdl, TestParsingMplayerTimeBasedEDL) // We should have a scenemarker at the commbreak start and another on commbreak end int time; // lets cycle to the next scenemarker if starting from 1 msec before the start (or end) of the commbreak - EXPECT_EQ(edl.GetNextSceneMarker(true, commbreak.start - 1, &time), true); + EXPECT_EQ(edl.GetNextSceneMarker(EDL::EditDirection::FORWARD, commbreak.start - 1, &time), true); EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.start); - EXPECT_EQ(edl.GetNextSceneMarker(true, commbreak.end - 1, &time), true); + EXPECT_EQ(edl.GetNextSceneMarker(EDL::EditDirection::FORWARD, commbreak.end - 1, &time), true); EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.end); // same if we cycle backwards - EXPECT_EQ(edl.GetNextSceneMarker(false, commbreak.start + 1, &time), true); + EXPECT_EQ(edl.GetNextSceneMarker(EDL::EditDirection::BACKWARD, commbreak.start + 1, &time), true); EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.start); - EXPECT_EQ(edl.GetNextSceneMarker(false, commbreak.end + 1, &time), true); + EXPECT_EQ(edl.GetNextSceneMarker(EDL::EditDirection::BACKWARD, commbreak.end + 1, &time), true); EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.end); // We should be in an edit if we are in the middle of a commbreak... // lets check and confirm the edits match (after restoring cuts) From f6a3fcad566958f6d692cb5823779b838c699be8 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 19 Jun 2024 12:02:27 +0100 Subject: [PATCH 169/651] [Players] Remove default seek direction --- xbmc/application/ApplicationPlayer.h | 2 +- xbmc/cores/IPlayer.h | 5 +---- xbmc/cores/VideoPlayer/VideoPlayer.h | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/xbmc/application/ApplicationPlayer.h b/xbmc/application/ApplicationPlayer.h index 484e5d52ac4e9..90bca97c5b280 100644 --- a/xbmc/application/ApplicationPlayer.h +++ b/xbmc/application/ApplicationPlayer.h @@ -144,7 +144,7 @@ class CApplicationPlayer : public IApplicationComponent void Seek(bool bPlus = true, bool bLargeStep = false, bool bChapterOverride = false); int SeekChapter(int iChapter); void SeekPercentage(float fPercent = 0); - bool SeekScene(PlayerSeekDirection seekDirection = PlayerSeekDirection::FORWARD); + bool SeekScene(PlayerSeekDirection seekDirection); void SeekTime(int64_t iTime = 0); void SeekTimeRelative(int64_t iTime = 0); void SetAudioStream(int iStream); diff --git a/xbmc/cores/IPlayer.h b/xbmc/cores/IPlayer.h index e41d704b9490b..658d786b535b2 100644 --- a/xbmc/cores/IPlayer.h +++ b/xbmc/cores/IPlayer.h @@ -114,10 +114,7 @@ class IPlayer virtual bool IsPassthrough() const { return false;} virtual bool CanSeek() const { return true; } virtual void Seek(bool bPlus = true, bool bLargeStep = false, bool bChapterOverride = false) = 0; - virtual bool SeekScene(PlayerSeekDirection seekDirection = PlayerSeekDirection::FORWARD) - { - return false; - } + virtual bool SeekScene(PlayerSeekDirection seekDirection) { return false; } virtual void SeekPercentage(float fPercent = 0){} virtual float GetCachePercentage() const { return 0; } virtual void SetMute(bool bOnOff){} diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.h b/xbmc/cores/VideoPlayer/VideoPlayer.h index c09d60d125357..4377984d303ad 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.h +++ b/xbmc/cores/VideoPlayer/VideoPlayer.h @@ -264,7 +264,7 @@ class CVideoPlayer : public IPlayer, public CThread, public IVideoPlayer, bool IsPassthrough() const override; bool CanSeek() const override; void Seek(bool bPlus, bool bLargeStep, bool bChapterOverride) override; - bool SeekScene(PlayerSeekDirection seekDirection = PlayerSeekDirection::FORWARD) override; + bool SeekScene(PlayerSeekDirection seekDirection) override; void SeekPercentage(float iPercent) override; float GetCachePercentage() const override; From 579236d1a305b0f223680ad667e7f47b8ffa4fff Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 19 Jun 2024 12:05:54 +0100 Subject: [PATCH 170/651] [EDL] Remove more hungarian notation --- xbmc/cores/VideoPlayer/Edl.cpp | 63 +++++++++++++++++----------------- xbmc/cores/VideoPlayer/Edl.h | 5 +-- 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/xbmc/cores/VideoPlayer/Edl.cpp b/xbmc/cores/VideoPlayer/Edl.cpp index ef3c36276c9f5..e76b4384e9091 100644 --- a/xbmc/cores/VideoPlayer/Edl.cpp +++ b/xbmc/cores/VideoPlayer/Edl.cpp @@ -45,47 +45,48 @@ void CEdl::Clear() bool CEdl::ReadEditDecisionLists(const CFileItem& fileItem, float fps) { - bool bFound = false; + bool found = false; /* * Only check for edit decision lists if the movie is on the local hard drive, or accessed over a * network share (even if from a different private network). */ - const std::string& strMovie = fileItem.GetDynPath(); - if ((URIUtils::IsHD(strMovie) || URIUtils::IsOnLAN(strMovie, LanCheckMode::ANY_PRIVATE_SUBNET)) && - !URIUtils::IsInternetStream(strMovie)) + const std::string& mediaFilePath = fileItem.GetDynPath(); + if ((URIUtils::IsHD(mediaFilePath) || + URIUtils::IsOnLAN(mediaFilePath, LanCheckMode::ANY_PRIVATE_SUBNET)) && + !URIUtils::IsInternetStream(mediaFilePath)) { CLog::Log(LOGDEBUG, "{} - Checking for edit decision lists (EDL) on local drive or remote share for: {}", - __FUNCTION__, CURL::GetRedacted(strMovie)); + __FUNCTION__, CURL::GetRedacted(mediaFilePath)); /* * Read any available file format until a valid EDL related file is found. */ - if (!bFound) - bFound = ReadVideoReDo(strMovie); + if (!found) + found = ReadVideoReDo(mediaFilePath); - if (!bFound) - bFound = ReadEdl(strMovie, fps); + if (!found) + found = ReadEdl(mediaFilePath, fps); - if (!bFound) - bFound = ReadComskip(strMovie, fps); + if (!found) + found = ReadComskip(mediaFilePath, fps); - if (!bFound) - bFound = ReadBeyondTV(strMovie); + if (!found) + found = ReadBeyondTV(mediaFilePath); } else { - bFound = ReadPvr(fileItem); + found = ReadPvr(fileItem); } - if (bFound) + if (found) { MergeShortCommBreaks(); AddSceneMarkersAtStartAndEndOfEdits(); } - return bFound; + return found; } bool CEdl::ReadEdl(const std::string& mediaFilePath, float fps) @@ -883,25 +884,25 @@ EDL::Action CEdl::GetLastEditActionType() const return m_lastEditActionType; } -bool CEdl::GetNextSceneMarker(EDL::EditDirection direction, const int iClock, int* iSceneMarker) +bool CEdl::GetNextSceneMarker(EDL::EditDirection direction, int clock, int* sceneMarker) { if (!HasSceneMarker()) return false; - int iSeek = GetTimeAfterRestoringCuts(iClock); + const int seekTime = GetTimeAfterRestoringCuts(clock); - int iDiff = 10 * 60 * 60 * 1000; // 10 hours to ms. - bool bFound = false; + int diff = 10 * 60 * 60 * 1000; // 10 hours to ms. + bool found = false; if (direction == EDL::EditDirection::FORWARD) // Find closest scene forwards { for (int i = 0; i < (int)m_vecSceneMarkers.size(); i++) { - if ((m_vecSceneMarkers[i] > iSeek) && ((m_vecSceneMarkers[i] - iSeek) < iDiff)) + if ((m_vecSceneMarkers[i] > seekTime) && ((m_vecSceneMarkers[i] - seekTime) < diff)) { - iDiff = m_vecSceneMarkers[i] - iSeek; - *iSceneMarker = m_vecSceneMarkers[i]; - bFound = true; + diff = m_vecSceneMarkers[i] - seekTime; + *sceneMarker = m_vecSceneMarkers[i]; + found = true; } } } @@ -909,11 +910,11 @@ bool CEdl::GetNextSceneMarker(EDL::EditDirection direction, const int iClock, in { for (int i = 0; i < (int)m_vecSceneMarkers.size(); i++) { - if ((m_vecSceneMarkers[i] < iSeek) && ((iSeek - m_vecSceneMarkers[i]) < iDiff)) + if ((m_vecSceneMarkers[i] < seekTime) && ((seekTime - m_vecSceneMarkers[i]) < diff)) { - iDiff = iSeek - m_vecSceneMarkers[i]; - *iSceneMarker = m_vecSceneMarkers[i]; - bFound = true; + diff = seekTime - m_vecSceneMarkers[i]; + *sceneMarker = m_vecSceneMarkers[i]; + found = true; } } } @@ -923,10 +924,10 @@ bool CEdl::GetNextSceneMarker(EDL::EditDirection direction, const int iClock, in * picked up when scene markers are added. */ Edit edit; - if (bFound && InEdit(*iSceneMarker, &edit) && edit.action == Action::CUT) - *iSceneMarker = edit.end; + if (found && InEdit(*sceneMarker, &edit) && edit.action == Action::CUT) + *sceneMarker = edit.end; - return bFound; + return found; } std::string CEdl::MillisecondsToTimeString(int milliSeconds) diff --git a/xbmc/cores/VideoPlayer/Edl.h b/xbmc/cores/VideoPlayer/Edl.h index 8c765f1a77a1b..23c28c40b0e2f 100644 --- a/xbmc/cores/VideoPlayer/Edl.h +++ b/xbmc/cores/VideoPlayer/Edl.h @@ -141,10 +141,7 @@ class CEdl */ EDL::Action GetLastEditActionType() const; - // FIXME: remove const modifier for iClock as it makes no sense as it means nothing - // for the reader of the interface, but limits the implementation - // to not modify the parameter on stack - bool GetNextSceneMarker(EDL::EditDirection direction, const int iClock, int* iSceneMarker); + bool GetNextSceneMarker(EDL::EditDirection direction, int clock, int* sceneMarker); static std::string MillisecondsToTimeString(int milliSeconds); From 6c51ece2e839a1a48d1074553a2c79d4f6316ca7 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 19 Jun 2024 14:48:42 +0100 Subject: [PATCH 171/651] [Players][EDL] Use single direction enum class --- xbmc/SeekHandler.cpp | 4 ++-- xbmc/application/ApplicationPlayer.cpp | 2 +- xbmc/application/ApplicationPlayer.h | 2 +- xbmc/cores/Direction.h | 18 ++++++++++++++++++ xbmc/cores/EdlEdit.h | 9 --------- xbmc/cores/IPlayer.h | 12 ++---------- xbmc/cores/VideoPlayer/Edl.cpp | 6 +++--- xbmc/cores/VideoPlayer/Edl.h | 3 ++- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 15 ++++++--------- xbmc/cores/VideoPlayer/VideoPlayer.h | 2 +- xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp | 8 ++++---- 11 files changed, 40 insertions(+), 41 deletions(-) create mode 100644 xbmc/cores/Direction.h diff --git a/xbmc/SeekHandler.cpp b/xbmc/SeekHandler.cpp index fb773b0eb06c2..7d8492634840d 100644 --- a/xbmc/SeekHandler.cpp +++ b/xbmc/SeekHandler.cpp @@ -311,12 +311,12 @@ bool CSeekHandler::OnAction(const CAction &action) } case ACTION_NEXT_SCENE: { - appPlayer->SeekScene(PlayerSeekDirection::FORWARD); + appPlayer->SeekScene(Direction::FORWARD); return true; } case ACTION_PREV_SCENE: { - appPlayer->SeekScene(PlayerSeekDirection::BACKWARD); + appPlayer->SeekScene(Direction::BACKWARD); return true; } case ACTION_ANALOG_SEEK_FORWARD: diff --git a/xbmc/application/ApplicationPlayer.cpp b/xbmc/application/ApplicationPlayer.cpp index a2ab7b5f455ec..9a59242a85b7d 100644 --- a/xbmc/application/ApplicationPlayer.cpp +++ b/xbmc/application/ApplicationPlayer.cpp @@ -327,7 +327,7 @@ bool CApplicationPlayer::CanSeek() const return (player && player->CanSeek()); } -bool CApplicationPlayer::SeekScene(PlayerSeekDirection seekDirection) +bool CApplicationPlayer::SeekScene(Direction seekDirection) { std::shared_ptr player = GetInternal(); return (player && player->SeekScene(seekDirection)); diff --git a/xbmc/application/ApplicationPlayer.h b/xbmc/application/ApplicationPlayer.h index 90bca97c5b280..ccee506bd62d1 100644 --- a/xbmc/application/ApplicationPlayer.h +++ b/xbmc/application/ApplicationPlayer.h @@ -144,7 +144,7 @@ class CApplicationPlayer : public IApplicationComponent void Seek(bool bPlus = true, bool bLargeStep = false, bool bChapterOverride = false); int SeekChapter(int iChapter); void SeekPercentage(float fPercent = 0); - bool SeekScene(PlayerSeekDirection seekDirection); + bool SeekScene(Direction seekDirection); void SeekTime(int64_t iTime = 0); void SeekTimeRelative(int64_t iTime = 0); void SetAudioStream(int iStream); diff --git a/xbmc/cores/Direction.h b/xbmc/cores/Direction.h new file mode 100644 index 0000000000000..ffcfe7368370d --- /dev/null +++ b/xbmc/cores/Direction.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2024 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 + +/*! + * @brief Specifies/Abstracts a direction +*/ +enum class Direction : bool +{ + FORWARD, /*!< Forward */ + BACKWARD /*!< Backward */ +}; diff --git a/xbmc/cores/EdlEdit.h b/xbmc/cores/EdlEdit.h index 73d3b791623dd..a06ed1572a1fc 100644 --- a/xbmc/cores/EdlEdit.h +++ b/xbmc/cores/EdlEdit.h @@ -28,13 +28,4 @@ struct Edit Action action = Action::CUT; }; -/*! - * @brief Specify the search direction for an EDL edit -*/ -enum class EditDirection -{ - FORWARD, /*!< Search forward */ - BACKWARD /*!< Search backwards */ -}; - } // namespace EDL diff --git a/xbmc/cores/IPlayer.h b/xbmc/cores/IPlayer.h index 658d786b535b2..d988d775271b6 100644 --- a/xbmc/cores/IPlayer.h +++ b/xbmc/cores/IPlayer.h @@ -8,6 +8,7 @@ #pragma once +#include "Direction.h" #include "IPlayerCallback.h" #include "Interface/StreamInfo.h" #include "MenuType.h" @@ -84,15 +85,6 @@ enum ERENDERFEATURE RENDERFEATURE_TONEMAP }; -/*! - * @brief Specify the Seek Direction -*/ -enum class PlayerSeekDirection -{ - FORWARD, /*!< Seek forward */ - BACKWARD /*!< Seek backwards */ -}; - class IPlayer { public: @@ -114,7 +106,7 @@ class IPlayer virtual bool IsPassthrough() const { return false;} virtual bool CanSeek() const { return true; } virtual void Seek(bool bPlus = true, bool bLargeStep = false, bool bChapterOverride = false) = 0; - virtual bool SeekScene(PlayerSeekDirection seekDirection) { return false; } + virtual bool SeekScene(Direction seekDirection) { return false; } virtual void SeekPercentage(float fPercent = 0){} virtual float GetCachePercentage() const { return 0; } virtual void SetMute(bool bOnOff){} diff --git a/xbmc/cores/VideoPlayer/Edl.cpp b/xbmc/cores/VideoPlayer/Edl.cpp index e76b4384e9091..0fca333eb3ab4 100644 --- a/xbmc/cores/VideoPlayer/Edl.cpp +++ b/xbmc/cores/VideoPlayer/Edl.cpp @@ -884,7 +884,7 @@ EDL::Action CEdl::GetLastEditActionType() const return m_lastEditActionType; } -bool CEdl::GetNextSceneMarker(EDL::EditDirection direction, int clock, int* sceneMarker) +bool CEdl::GetNextSceneMarker(Direction direction, int clock, int* sceneMarker) { if (!HasSceneMarker()) return false; @@ -894,7 +894,7 @@ bool CEdl::GetNextSceneMarker(EDL::EditDirection direction, int clock, int* scen int diff = 10 * 60 * 60 * 1000; // 10 hours to ms. bool found = false; - if (direction == EDL::EditDirection::FORWARD) // Find closest scene forwards + if (direction == Direction::FORWARD) // Find closest scene forwards { for (int i = 0; i < (int)m_vecSceneMarkers.size(); i++) { @@ -906,7 +906,7 @@ bool CEdl::GetNextSceneMarker(EDL::EditDirection direction, int clock, int* scen } } } - else if (direction == EDL::EditDirection::BACKWARD) // Find closest scene backwards + else if (direction == Direction::BACKWARD) // Find closest scene backwards { for (int i = 0; i < (int)m_vecSceneMarkers.size(); i++) { diff --git a/xbmc/cores/VideoPlayer/Edl.h b/xbmc/cores/VideoPlayer/Edl.h index 23c28c40b0e2f..886302b2aa32a 100644 --- a/xbmc/cores/VideoPlayer/Edl.h +++ b/xbmc/cores/VideoPlayer/Edl.h @@ -8,6 +8,7 @@ #pragma once +#include "cores/Direction.h" #include "cores/EdlEdit.h" #include @@ -141,7 +142,7 @@ class CEdl */ EDL::Action GetLastEditActionType() const; - bool GetNextSceneMarker(EDL::EditDirection direction, int clock, int* sceneMarker); + bool GetNextSceneMarker(Direction direction, int clock, int* sceneMarker); static std::string MillisecondsToTimeString(int milliSeconds); diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index aa7e46e066148..f83237fd1fab1 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -3254,7 +3254,7 @@ void CVideoPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride) m_callback.OnPlayBackSeek(seekTarget, seekTarget - time); } -bool CVideoPlayer::SeekScene(PlayerSeekDirection seekDirection) +bool CVideoPlayer::SeekScene(Direction seekDirection) { if (!m_Edl.HasSceneMarker()) return false; @@ -3264,21 +3264,18 @@ bool CVideoPlayer::SeekScene(PlayerSeekDirection seekDirection) * grace period applied it is impossible to go backwards past a scene marker. */ int64_t clock = GetTime(); - if (seekDirection == PlayerSeekDirection::BACKWARD && clock > 5 * 1000) // 5 seconds + if (seekDirection == Direction::BACKWARD && clock > 5 * 1000) // 5 seconds clock -= 5 * 1000; int iScenemarker; - if (m_Edl.GetNextSceneMarker(seekDirection == PlayerSeekDirection::FORWARD - ? EDL::EditDirection::FORWARD - : EDL::EditDirection::BACKWARD, - clock, &iScenemarker)) + if (m_Edl.GetNextSceneMarker(seekDirection, clock, &iScenemarker)) { /* * Seeking is flushed and inaccurate, just like Seek() */ CDVDMsgPlayerSeek::CMode mode; mode.time = iScenemarker; - mode.backward = seekDirection == PlayerSeekDirection::BACKWARD; + mode.backward = seekDirection == Direction::BACKWARD; mode.accurate = false; mode.restore = false; mode.trickplay = false; @@ -4530,7 +4527,7 @@ bool CVideoPlayer::OnAction(const CAction &action) m_processInfo->SeekFinished(0); return true; } - else if (SeekScene(PlayerSeekDirection::FORWARD)) + else if (SeekScene(Direction::FORWARD)) return true; else break; @@ -4541,7 +4538,7 @@ bool CVideoPlayer::OnAction(const CAction &action) m_processInfo->SeekFinished(0); return true; } - else if (SeekScene(PlayerSeekDirection::BACKWARD)) + else if (SeekScene(Direction::BACKWARD)) return true; else break; diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.h b/xbmc/cores/VideoPlayer/VideoPlayer.h index 4377984d303ad..f340b85e831e4 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.h +++ b/xbmc/cores/VideoPlayer/VideoPlayer.h @@ -264,7 +264,7 @@ class CVideoPlayer : public IPlayer, public CThread, public IVideoPlayer, bool IsPassthrough() const override; bool CanSeek() const override; void Seek(bool bPlus, bool bLargeStep, bool bChapterOverride) override; - bool SeekScene(PlayerSeekDirection seekDirection) override; + bool SeekScene(Direction seekDirection) override; void SeekPercentage(float iPercent) override; float GetCachePercentage() const override; diff --git a/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp b/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp index b804cc3597d8c..9db0321ed1bc6 100644 --- a/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp +++ b/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp @@ -86,14 +86,14 @@ TEST_F(TestEdl, TestParsingMplayerTimeBasedEDL) // We should have a scenemarker at the commbreak start and another on commbreak end int time; // lets cycle to the next scenemarker if starting from 1 msec before the start (or end) of the commbreak - EXPECT_EQ(edl.GetNextSceneMarker(EDL::EditDirection::FORWARD, commbreak.start - 1, &time), true); + EXPECT_EQ(edl.GetNextSceneMarker(Direction::FORWARD, commbreak.start - 1, &time), true); EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.start); - EXPECT_EQ(edl.GetNextSceneMarker(EDL::EditDirection::FORWARD, commbreak.end - 1, &time), true); + EXPECT_EQ(edl.GetNextSceneMarker(Direction::FORWARD, commbreak.end - 1, &time), true); EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.end); // same if we cycle backwards - EXPECT_EQ(edl.GetNextSceneMarker(EDL::EditDirection::BACKWARD, commbreak.start + 1, &time), true); + EXPECT_EQ(edl.GetNextSceneMarker(Direction::BACKWARD, commbreak.start + 1, &time), true); EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.start); - EXPECT_EQ(edl.GetNextSceneMarker(EDL::EditDirection::BACKWARD, commbreak.end + 1, &time), true); + EXPECT_EQ(edl.GetNextSceneMarker(Direction::BACKWARD, commbreak.end + 1, &time), true); EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.end); // We should be in an edit if we are in the middle of a commbreak... // lets check and confirm the edits match (after restoring cuts) From c27ff4bc365ab0245045ff810a76a03cb0163809 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 19 Jun 2024 14:49:30 +0100 Subject: [PATCH 172/651] [EDL] Blank line cleanup --- xbmc/cores/VideoPlayer/Edl.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/xbmc/cores/VideoPlayer/Edl.h b/xbmc/cores/VideoPlayer/Edl.h index 886302b2aa32a..9e4fa26ba7821 100644 --- a/xbmc/cores/VideoPlayer/Edl.h +++ b/xbmc/cores/VideoPlayer/Edl.h @@ -163,13 +163,9 @@ class CEdl EDL::Action m_lastEditActionType{EDL::EDL_ACTION_NONE}; bool ReadEdl(const std::string& mediaFilePath, float fps); - bool ReadComskip(const std::string& mediaFilePath, float fps); - bool ReadVideoReDo(const std::string& mediaFilePath); - bool ReadBeyondTV(const std::string& mediaFilePath); - bool ReadPvr(const CFileItem& fileItem); /*! From b872735ef607ae43d0745d0683d7076d6c494e2e Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 22 Jun 2024 16:12:08 +0100 Subject: [PATCH 173/651] [EDL] Move GetNextSceneMarker to std::optional --- xbmc/cores/VideoPlayer/Edl.cpp | 18 ++++++++---------- xbmc/cores/VideoPlayer/Edl.h | 9 ++++++++- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 7 ++++--- xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp | 21 ++++++++++++--------- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/xbmc/cores/VideoPlayer/Edl.cpp b/xbmc/cores/VideoPlayer/Edl.cpp index 0fca333eb3ab4..664358a4f5b4b 100644 --- a/xbmc/cores/VideoPlayer/Edl.cpp +++ b/xbmc/cores/VideoPlayer/Edl.cpp @@ -884,15 +884,15 @@ EDL::Action CEdl::GetLastEditActionType() const return m_lastEditActionType; } -bool CEdl::GetNextSceneMarker(Direction direction, int clock, int* sceneMarker) +std::optional CEdl::GetNextSceneMarker(Direction direction, int clock) { if (!HasSceneMarker()) - return false; + return std::nullopt; + std::optional sceneMarker; const int seekTime = GetTimeAfterRestoringCuts(clock); int diff = 10 * 60 * 60 * 1000; // 10 hours to ms. - bool found = false; if (direction == Direction::FORWARD) // Find closest scene forwards { @@ -901,8 +901,7 @@ bool CEdl::GetNextSceneMarker(Direction direction, int clock, int* sceneMarker) if ((m_vecSceneMarkers[i] > seekTime) && ((m_vecSceneMarkers[i] - seekTime) < diff)) { diff = m_vecSceneMarkers[i] - seekTime; - *sceneMarker = m_vecSceneMarkers[i]; - found = true; + sceneMarker = m_vecSceneMarkers[i]; } } } @@ -913,8 +912,7 @@ bool CEdl::GetNextSceneMarker(Direction direction, int clock, int* sceneMarker) if ((m_vecSceneMarkers[i] < seekTime) && ((seekTime - m_vecSceneMarkers[i]) < diff)) { diff = seekTime - m_vecSceneMarkers[i]; - *sceneMarker = m_vecSceneMarkers[i]; - found = true; + sceneMarker = m_vecSceneMarkers[i]; } } } @@ -924,10 +922,10 @@ bool CEdl::GetNextSceneMarker(Direction direction, int clock, int* sceneMarker) * picked up when scene markers are added. */ Edit edit; - if (found && InEdit(*sceneMarker, &edit) && edit.action == Action::CUT) - *sceneMarker = edit.end; + if (sceneMarker && InEdit(sceneMarker.value(), &edit) && edit.action == Action::CUT) + sceneMarker = edit.end; - return found; + return sceneMarker; } std::string CEdl::MillisecondsToTimeString(int milliSeconds) diff --git a/xbmc/cores/VideoPlayer/Edl.h b/xbmc/cores/VideoPlayer/Edl.h index 9e4fa26ba7821..3b0279c34d3c5 100644 --- a/xbmc/cores/VideoPlayer/Edl.h +++ b/xbmc/cores/VideoPlayer/Edl.h @@ -11,6 +11,7 @@ #include "cores/Direction.h" #include "cores/EdlEdit.h" +#include #include #include @@ -142,7 +143,13 @@ class CEdl */ EDL::Action GetLastEditActionType() const; - bool GetNextSceneMarker(Direction direction, int clock, int* sceneMarker); + /*! + * @brief Get the next scene marker with respect to the provided clock time + * @param direction (the direction of the search - backward or forward) + * @param clock the current position of the clock + * @return the position of the scenemarker (nullopt if none) + */ + std::optional GetNextSceneMarker(Direction direction, int clock); static std::string MillisecondsToTimeString(int milliSeconds); diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index f83237fd1fab1..be3d3a030b503 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -3267,14 +3267,15 @@ bool CVideoPlayer::SeekScene(Direction seekDirection) if (seekDirection == Direction::BACKWARD && clock > 5 * 1000) // 5 seconds clock -= 5 * 1000; - int iScenemarker; - if (m_Edl.GetNextSceneMarker(seekDirection, clock, &iScenemarker)) + const std::optional sceneMarker = + m_Edl.GetNextSceneMarker(seekDirection, static_cast(clock)); + if (sceneMarker) { /* * Seeking is flushed and inaccurate, just like Seek() */ CDVDMsgPlayerSeek::CMode mode; - mode.time = iScenemarker; + mode.time = sceneMarker.value(); mode.backward = seekDirection == Direction::BACKWARD; mode.accurate = false; mode.restore = false; diff --git a/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp b/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp index 9db0321ed1bc6..4516d9666c227 100644 --- a/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp +++ b/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp @@ -84,17 +84,20 @@ TEST_F(TestEdl, TestParsingMplayerTimeBasedEDL) const auto commbreak = edl.GetEditList().at(1); EXPECT_EQ(commbreak.action, Action::COMM_BREAK); // We should have a scenemarker at the commbreak start and another on commbreak end - int time; + std::optional time = edl.GetNextSceneMarker(Direction::FORWARD, commbreak.start - 1); // lets cycle to the next scenemarker if starting from 1 msec before the start (or end) of the commbreak - EXPECT_EQ(edl.GetNextSceneMarker(Direction::FORWARD, commbreak.start - 1, &time), true); - EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.start); - EXPECT_EQ(edl.GetNextSceneMarker(Direction::FORWARD, commbreak.end - 1, &time), true); - EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.end); + EXPECT_NE(time, std::nullopt); + EXPECT_EQ(edl.GetTimeWithoutCuts(time.value()), commbreak.start); + time = edl.GetNextSceneMarker(Direction::FORWARD, commbreak.end - 1); + EXPECT_NE(time, std::nullopt); + EXPECT_EQ(edl.GetTimeWithoutCuts(time.value()), commbreak.end); // same if we cycle backwards - EXPECT_EQ(edl.GetNextSceneMarker(Direction::BACKWARD, commbreak.start + 1, &time), true); - EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.start); - EXPECT_EQ(edl.GetNextSceneMarker(Direction::BACKWARD, commbreak.end + 1, &time), true); - EXPECT_EQ(edl.GetTimeWithoutCuts(time), commbreak.end); + time = edl.GetNextSceneMarker(Direction::BACKWARD, commbreak.start + 1); + EXPECT_NE(time, std::nullopt); + EXPECT_EQ(edl.GetTimeWithoutCuts(time.value()), commbreak.start); + time = edl.GetNextSceneMarker(Direction::BACKWARD, commbreak.end + 1); + EXPECT_NE(time, std::nullopt); + EXPECT_EQ(edl.GetTimeWithoutCuts(time.value()), commbreak.end); // We should be in an edit if we are in the middle of a commbreak... // lets check and confirm the edits match (after restoring cuts) Edit thisEdit; From 0e32600a4028bd410080f6c13b798202324bb9ae Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 14 Jun 2024 11:19:44 +0200 Subject: [PATCH 174/651] [PVR] Channel groups: Fix duplicate database entries caused by temporarily unavailable groups/members. --- xbmc/pvr/channels/PVRChannelGroup.cpp | 9 ++ xbmc/pvr/channels/PVRChannelGroup.h | 9 ++ .../pvr/channels/PVRChannelGroupAllChannels.h | 12 ++ xbmc/pvr/channels/PVRChannelGroupFromUser.h | 12 ++ xbmc/pvr/channels/PVRChannelGroups.cpp | 126 +++++++++++------- xbmc/pvr/channels/PVRChannelGroups.h | 10 ++ 6 files changed, 128 insertions(+), 50 deletions(-) diff --git a/xbmc/pvr/channels/PVRChannelGroup.cpp b/xbmc/pvr/channels/PVRChannelGroup.cpp index a501f1b4a8983..af4a04d29c695 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.cpp +++ b/xbmc/pvr/channels/PVRChannelGroup.cpp @@ -245,6 +245,15 @@ void CPVRChannelGroup::UpdateClientPriorities() SortAndRenumber(); } +bool CPVRChannelGroup::ShouldBeIgnored( + const std::vector>& allChannelGroups) const +{ + std::unique_lock lock(m_critSection); + + // Empty group should be ignored. + return m_members.empty(); +} + bool CPVRChannelGroup::UpdateMembersClientPriority() { const std::shared_ptr clients = CServiceBroker::GetPVRManager().Clients(); diff --git a/xbmc/pvr/channels/PVRChannelGroup.h b/xbmc/pvr/channels/PVRChannelGroup.h index 61b832bcbc6ce..d660f14ec00cf 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.h +++ b/xbmc/pvr/channels/PVRChannelGroup.h @@ -499,6 +499,15 @@ class CPVRChannelGroup : public IChannelGroupSettingsCallback */ virtual bool IsChannelsOwner() const = 0; + /*! + * @brief Check whether this group should be ignored, e.g. not presented to the user and API. + * @param allChannelGroups All available channel groups. This information might be needed by + * implementations to calculate the ignore state. + * @return True if to be ignored, false otherwise. + */ + virtual bool ShouldBeIgnored( + const std::vector>& allChannelGroups) const; + protected: /*! * @brief Remove deleted group members from this group. diff --git a/xbmc/pvr/channels/PVRChannelGroupAllChannels.h b/xbmc/pvr/channels/PVRChannelGroupAllChannels.h index 83f53f92175f0..a51aadae761d6 100644 --- a/xbmc/pvr/channels/PVRChannelGroupAllChannels.h +++ b/xbmc/pvr/channels/PVRChannelGroupAllChannels.h @@ -88,6 +88,18 @@ class CPVRChannelGroupAllChannels : public CPVRChannelGroup */ bool IsChannelsOwner() const override { return true; } + /*! + * @brief Check whether this group should be ignored, e.g. not presented to the user and API. + * @param allChannelGroups All available channel groups. + * @return True if to be ignored, false otherwise. + */ + bool ShouldBeIgnored( + const std::vector>& allChannelGroups) const override + { + // "All channels" groups must always be present. + return false; + } + protected: /*! * @brief Remove deleted group members from this group. Delete stale channels. diff --git a/xbmc/pvr/channels/PVRChannelGroupFromUser.h b/xbmc/pvr/channels/PVRChannelGroupFromUser.h index 39c0a0c127d52..e54b88aa1c5ce 100644 --- a/xbmc/pvr/channels/PVRChannelGroupFromUser.h +++ b/xbmc/pvr/channels/PVRChannelGroupFromUser.h @@ -62,6 +62,18 @@ class CPVRChannelGroupFromUser : public CPVRChannelGroup */ bool IsChannelsOwner() const override { return false; } + /*! + * @brief Check whether this group should be ignored, e.g. not presented to the user and API. + * @param allChannelGroups All available channel groups. + * @return True if to be ignored, false otherwise. + */ + bool ShouldBeIgnored( + const std::vector>& allChannelGroups) const override + { + // User-created groups shall always be present. + return false; + } + /*! * @brief Update data with channel group members from the given clients, sync with local data. * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp index 6eca862225ff4..add597cf103e8 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.cpp +++ b/xbmc/pvr/channels/PVRChannelGroups.cpp @@ -111,7 +111,7 @@ bool CPVRChannelGroups::Update(const std::shared_ptr& group, // try to find the group by id if (!updateGroup && group->GroupID() > 0) - updateGroup = GetById(group->GroupID()); + updateGroup = GetGroupById(group->GroupID(), Exclude::NONE); // try to find the group by name if we didn't find it yet if (!updateGroup) @@ -260,10 +260,21 @@ std::vector> CPVRChannelGroups::GetMembe std::shared_ptr CPVRChannelGroups::GetById(int iGroupId) const { + return GetGroupById(iGroupId, Exclude::IGNORED); +} + +std::shared_ptr CPVRChannelGroups::GetGroupById(int groupId, + Exclude exclude) const +{ + const bool excludeIgnored{exclude == Exclude::IGNORED}; + std::unique_lock lock(m_critSection); - const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [iGroupId](const auto& group) { - return group->GroupID() == iGroupId; - }); + const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), + [groupId, excludeIgnored, this](const auto& group) + { + return (group->GroupID() == groupId) && + (!excludeIgnored || !group->ShouldBeIgnored(m_groups)); + }); return (it != m_groups.cend()) ? (*it) : std::shared_ptr(); } @@ -274,8 +285,10 @@ std::shared_ptr CPVRChannelGroups::GetGroupByPath( if (path.IsChannelGroup()) { std::unique_lock lock(m_critSection); - const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), - [&path](const auto& group) { return group->GetPath() == path; }); + const auto it = + std::find_if(m_groups.cbegin(), m_groups.cend(), + [&path, this](const auto& group) + { return (group->GetPath() == path) && !group->ShouldBeIgnored(m_groups); }); if (it != m_groups.cend()) return (*it); } @@ -285,11 +298,23 @@ std::shared_ptr CPVRChannelGroups::GetGroupByPath( std::shared_ptr CPVRChannelGroups::GetByName(const std::string& strName, int clientID) const { + return GetGroupByName(strName, clientID, Exclude::IGNORED); +} + +std::shared_ptr CPVRChannelGroups::GetGroupByName(const std::string& name, + int clientID, + Exclude exclude) const +{ + const bool excludeIgnored{exclude == Exclude::IGNORED}; + std::unique_lock lock(m_critSection); - const auto it = - std::find_if(m_groups.cbegin(), m_groups.cend(), [&strName, clientID](const auto& group) { - return group->GroupName() == strName && group->GetClientID() == clientID; - }); + const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), + [&name, clientID, excludeIgnored, this](const auto& group) + { + return (group->GetClientID() == clientID) && + (group->GroupName() == name) && + (!excludeIgnored || !group->ShouldBeIgnored(m_groups)); + }); return (it != m_groups.cend()) ? (*it) : std::shared_ptr(); } @@ -411,15 +436,6 @@ bool CPVRChannelGroups::LoadFromDatabase(const std::vectorSize() == 0 && !(*it)->IsChannelsOwner()) - it = m_groups.erase(it); - else - ++it; - } - // Register for client priority changes if (!m_isSubscribed) { @@ -450,21 +466,29 @@ std::shared_ptr CPVRChannelGroups::GetGroupAll() const std::shared_ptr CPVRChannelGroups::GetLastGroup() const { std::unique_lock lock(m_critSection); - if (!m_groups.empty()) - return m_groups.back(); - - return std::shared_ptr(); + for (auto it = m_groups.crbegin(); it != m_groups.crend(); ++it) + { + const auto group{*it}; + if (!group->ShouldBeIgnored(m_groups)) + return group; + } + return {}; } GroupMemberPair CPVRChannelGroups::GetLastAndPreviousToLastPlayedChannelGroupMember() const { std::unique_lock lock(m_critSection); - if (m_groups.empty()) - return {}; - auto groups = m_groups; + std::vector> groups; + std::copy_if(m_groups.cbegin(), m_groups.cend(), std::back_inserter(groups), + [this](const auto& group) + { return !group->IsHidden() && !group->ShouldBeIgnored(m_groups); }); + lock.unlock(); + if (groups.empty()) + return {}; + std::sort(groups.begin(), groups.end(), [](const auto& a, const auto& b) { return a->LastWatched() > b->LastWatched(); }); @@ -507,9 +531,11 @@ std::vector> CPVRChannelGroups::GetMembers( std::vector> groups; std::unique_lock lock(m_critSection); - std::copy_if( - m_groups.cbegin(), m_groups.cend(), std::back_inserter(groups), - [bExcludeHidden](const auto& group) { return (!bExcludeHidden || !group->IsHidden()); }); + std::copy_if(m_groups.cbegin(), m_groups.cend(), std::back_inserter(groups), + [bExcludeHidden, this](const auto& group) { + return (!bExcludeHidden || !group->IsHidden()) && + !group->ShouldBeIgnored(m_groups); + }); return groups; } @@ -520,26 +546,25 @@ std::shared_ptr CPVRChannelGroups::GetPreviousGroup( bool bReturnNext = false; std::unique_lock lock(m_critSection); - for (std::vector>::const_reverse_iterator it = - m_groups.rbegin(); - it != m_groups.rend(); ++it) + for (auto it = m_groups.crbegin(); it != m_groups.crend(); ++it) { + const auto currentGroup{*it}; + // return this entry - if (bReturnNext && !(*it)->IsHidden()) - return *it; + if (bReturnNext && !currentGroup->IsHidden() && !currentGroup->ShouldBeIgnored(m_groups)) + return currentGroup; // return the next entry - if ((*it)->GroupID() == group.GroupID()) + if (currentGroup->GroupID() == group.GroupID()) bReturnNext = true; } // no match return last visible group - for (std::vector>::const_reverse_iterator it = - m_groups.rbegin(); - it != m_groups.rend(); ++it) + for (auto it = m_groups.crbegin(); it != m_groups.crend(); ++it) { - if (!(*it)->IsHidden()) - return *it; + const auto currentGroup{*it}; + if (!currentGroup->IsHidden() && !currentGroup->ShouldBeIgnored(m_groups)) + return currentGroup; } } @@ -554,24 +579,25 @@ std::shared_ptr CPVRChannelGroups::GetNextGroup( bool bReturnNext = false; std::unique_lock lock(m_critSection); - for (std::vector>::const_iterator it = m_groups.begin(); - it != m_groups.end(); ++it) + for (auto it = m_groups.cbegin(); it != m_groups.cend(); ++it) { + const auto currentGroup{*it}; + // return this entry - if (bReturnNext && !(*it)->IsHidden()) - return *it; + if (bReturnNext && !currentGroup->IsHidden() && !currentGroup->ShouldBeIgnored(m_groups)) + return currentGroup; // return the next entry - if ((*it)->GroupID() == group.GroupID()) + if (currentGroup->GroupID() == group.GroupID()) bReturnNext = true; } // no match return first visible group - for (std::vector>::const_iterator it = m_groups.begin(); - it != m_groups.end(); ++it) + for (auto it = m_groups.cbegin(); it != m_groups.cend(); ++it) { - if (!(*it)->IsHidden()) - return *it; + const auto currentGroup{*it}; + if (!currentGroup->IsHidden() && !currentGroup->ShouldBeIgnored(m_groups)) + return currentGroup; } } @@ -588,7 +614,7 @@ std::shared_ptr CPVRChannelGroups::AddGroup(const std::string& std::unique_lock lock(m_critSection); // check if there's another local group with the same name already - group = GetByName(strName, PVR_GROUP_CLIENT_ID_LOCAL); + group = GetGroupByName(strName, PVR_GROUP_CLIENT_ID_LOCAL, Exclude::NONE); if (!group) { // create a new local group diff --git a/xbmc/pvr/channels/PVRChannelGroups.h b/xbmc/pvr/channels/PVRChannelGroups.h index 459759546141a..b787c62d6fdfb 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.h +++ b/xbmc/pvr/channels/PVRChannelGroups.h @@ -244,6 +244,16 @@ class CPVRChannelGroups : public ISettingCallback int GetGroupClientPriority(const std::shared_ptr& group) const; + enum class Exclude + { + NONE, + IGNORED, + }; + std::shared_ptr GetGroupByName(const std::string& name, + int clientID, + Exclude exclude) const; + std::shared_ptr GetGroupById(int groupId, Exclude exclude) const; + bool m_bRadio{false}; std::vector> m_groups; mutable CCriticalSection m_critSection; From c49b45a03ebbe87d4040574b56aa65087220f847 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 22 Jun 2024 15:40:00 +0200 Subject: [PATCH 175/651] Cleanup: Fix typos in PVRChannel.h --- xbmc/pvr/channels/PVRChannel.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/xbmc/pvr/channels/PVRChannel.h b/xbmc/pvr/channels/PVRChannel.h index 0e2497f7c75dd..91cfb9ac36ea0 100644 --- a/xbmc/pvr/channels/PVRChannel.h +++ b/xbmc/pvr/channels/PVRChannel.h @@ -93,7 +93,7 @@ class CPVRChannel : public ISerializable, public ISortable /*! * @brief Set the identifier for this channel. * @param iDatabaseId The new channel ID - * @return True if the something changed, false otherwise. + * @return True if something changed, false otherwise. */ bool SetChannelID(int iDatabaseId); @@ -114,7 +114,7 @@ class CPVRChannel : public ISerializable, public ISortable * The EPG of hidden channels won't be updated. * @param bIsHidden The new setting. * @param bIsUserSetIcon true if user changed the hidden flag via GUI, false otherwise. - * @return True if the something changed, false otherwise. + * @return True if something changed, false otherwise. */ bool SetHidden(bool bIsHidden, bool bIsUserSetHidden = false); @@ -129,7 +129,7 @@ class CPVRChannel : public ISerializable, public ISortable * Set to true to lock this channel. Set to false to unlock it. * Locked channels need can only be viewed if parental PIN entered. * @param bIsLocked The new setting. - * @return True if the something changed, false otherwise. + * @return True if something changed, false otherwise. */ bool SetLocked(bool bIsLocked); @@ -191,7 +191,7 @@ class CPVRChannel : public ISerializable, public ISortable * @brief Set the path to the icon for this channel. * @param strIconPath The new path. * @param bIsUserSetIcon true if user changed the icon via GUI, false otherwise. - * @return True if the something changed, false otherwise. + * @return True if something changed, false otherwise. */ bool SetIconPath(const std::string& strIconPath, bool bIsUserSetIcon = false); @@ -204,7 +204,7 @@ class CPVRChannel : public ISerializable, public ISortable * @brief Set the name for this channel used by XBMC. * @param strChannelName The new channel name. * @param bIsUserSetName whether the change was triggered by the user directly - * @return True if the something changed, false otherwise. + * @return True if something changed, false otherwise. */ bool SetChannelName(const std::string& strChannelName, bool bIsUserSetName = false); @@ -217,7 +217,7 @@ class CPVRChannel : public ISerializable, public ISortable * @brief Set the last time the channel has been watched and the channel group used to watch. * @param lastWatched The new last watched time value. * @param groupId the id of the group used to watch the channel. - * @return True if the something changed, false otherwise. + * @return True if something changed, false otherwise. */ bool SetLastWatched(time_t lastWatched, int groupId); @@ -229,7 +229,7 @@ class CPVRChannel : public ISerializable, public ISortable /*! * @brief Set the date and time the channel was added to the TV database. * @param dateTimeAdded The date and time. - * @return True if the something changed, false otherwise. + * @return True if something changed, false otherwise. */ bool SetDateTimeAdded(const CDateTime& dateTimeAdded); @@ -267,7 +267,7 @@ class CPVRChannel : public ISerializable, public ISortable /*! * @brief Set the identifier of the client that serves this channel. * @param iClientId The new ID. - * @return True if the something changed, false otherwise. + * @return True if something changed, false otherwise. */ bool SetClientID(int iClientId); @@ -415,7 +415,7 @@ class CPVRChannel : public ISerializable, public ISortable /*! * @brief Set to true if an EPG should be used for this channel. Set to false otherwise. * @param bEPGEnabled The new value. - * @return True if the something changed, false otherwise. + * @return True if something changed, false otherwise. */ bool SetEPGEnabled(bool bEPGEnabled); @@ -436,7 +436,7 @@ class CPVRChannel : public ISerializable, public ISortable * Set to "client" to load the EPG from the backend * * @param strScraper The new scraper name. - * @return True if the something changed, false otherwise. + * @return True if something changed, false otherwise. */ bool SetEPGScraper(const std::string& strScraper); @@ -512,7 +512,7 @@ class CPVRChannel : public ISerializable, public ISortable /*! * @brief Set the client provider Uid for this channel * @param iClientProviderUid The provider Uid for this channel - * @return True if the something changed, false otherwise. + * @return True if something changed, false otherwise. */ bool SetClientProviderUid(int iClientProviderUid); From b9486293db5a7082b82488b4df7b6e9aecfb77da Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Fri, 7 Jun 2024 23:06:13 +0200 Subject: [PATCH 176/651] CGUIControlFactory::parseString make method static like the others --- xbmc/guilib/GUIControlFactory.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/guilib/GUIControlFactory.h b/xbmc/guilib/GUIControlFactory.h index 6fba3de3b3270..c80785aeecc2c 100644 --- a/xbmc/guilib/GUIControlFactory.h +++ b/xbmc/guilib/GUIControlFactory.h @@ -137,7 +137,7 @@ class CGUIControlFactory static bool GetConditionalVisibility(const TiXmlNode* control, std::string& condition, std::string& allowHiddenFocus); - bool GetString(const TiXmlNode* pRootNode, const char* strTag, std::string& strString); + static bool GetString(const TiXmlNode* pRootNode, const char* strTag, std::string& strString); static bool GetFloatRange(const TiXmlNode* pRootNode, const char* strTag, float& iMinValue, From 2828cf1232def08ae0b17c801383093968edc50e Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Fri, 7 Jun 2024 23:06:36 +0200 Subject: [PATCH 177/651] CAspectRatio: add constructor with full parameter set required for testing purposes --- xbmc/guilib/GUITexture.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/xbmc/guilib/GUITexture.h b/xbmc/guilib/GUITexture.h index fdc76a01474b3..ae6825213a1b1 100644 --- a/xbmc/guilib/GUITexture.h +++ b/xbmc/guilib/GUITexture.h @@ -34,7 +34,13 @@ class CAspectRatio ratio = aspect; align = ASPECT_ALIGN_CENTER | ASPECT_ALIGNY_CENTER; scaleDiffuse = true; - }; + } + + CAspectRatio(ASPECT_RATIO aspect, uint32_t al, bool scaleD) + : ratio(aspect), align(al), scaleDiffuse(scaleD) + { + } + bool operator!=(const CAspectRatio &right) const { if (ratio != right.ratio) return true; From 4c2e4f2b718c0e4da3a710d8fec0d4ac4e36e8b0 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Fri, 7 Jun 2024 23:07:01 +0200 Subject: [PATCH 178/651] EventCfg: add comparison operator needed for testing purposes --- xbmc/utils/MovingSpeed.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xbmc/utils/MovingSpeed.h b/xbmc/utils/MovingSpeed.h index 076d43a2fda89..bc890df611d71 100644 --- a/xbmc/utils/MovingSpeed.h +++ b/xbmc/utils/MovingSpeed.h @@ -44,6 +44,12 @@ struct EventCfg { } + bool operator==(const EventCfg& right) const + { + return m_acceleration == right.m_acceleration && m_maxVelocity == right.m_maxVelocity && + m_resetTimeout == right.m_resetTimeout && m_delta == right.m_delta; + } + float m_acceleration; float m_maxVelocity; uint32_t m_resetTimeout; From 7a890d027587fe42a98ef2a7e32589cfc491d0bb Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Fri, 7 Jun 2024 23:07:20 +0200 Subject: [PATCH 179/651] CGUIControlFactory: make private members protected to facilite testing --- xbmc/guilib/GUIControlFactory.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/guilib/GUIControlFactory.h b/xbmc/guilib/GUIControlFactory.h index c80785aeecc2c..e6e01e162de0b 100644 --- a/xbmc/guilib/GUIControlFactory.h +++ b/xbmc/guilib/GUIControlFactory.h @@ -129,7 +129,7 @@ class CGUIControlFactory const std::string& scrollerTag, CScroller& scroller); -private: +protected: static std::string GetType(const TiXmlElement* pControlNode); static bool GetMovingSpeedConfig(const TiXmlNode* pRootNode, const char* strTag, From 9d6ca3da7c152f26987266affc1f37fd2453426e Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Tue, 11 Jun 2024 14:47:26 +0200 Subject: [PATCH 180/651] CGUIComponent: add a dummy constructor for use in tests --- xbmc/guilib/GUIComponent.cpp | 7 ++++++- xbmc/guilib/GUIComponent.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/xbmc/guilib/GUIComponent.cpp b/xbmc/guilib/GUIComponent.cpp index 8e52acc12c06c..5c8b414fd8cb3 100644 --- a/xbmc/guilib/GUIComponent.cpp +++ b/xbmc/guilib/GUIComponent.cpp @@ -32,6 +32,10 @@ CGUIComponent::CGUIComponent() { } +CGUIComponent::CGUIComponent(bool) +{ +} + CGUIComponent::~CGUIComponent() { Deinit(); @@ -50,7 +54,8 @@ void CGUIComponent::Deinit() { CServiceBroker::UnregisterGUI(); - m_pWindowManager->DeInitialize(); + if (m_pWindowManager) + m_pWindowManager->DeInitialize(); } CGUIWindowManager& CGUIComponent::GetWindowManager() diff --git a/xbmc/guilib/GUIComponent.h b/xbmc/guilib/GUIComponent.h index 97ada489b5a6a..b7eb75c58a394 100644 --- a/xbmc/guilib/GUIComponent.h +++ b/xbmc/guilib/GUIComponent.h @@ -23,6 +23,7 @@ class CGUIComponent { public: CGUIComponent(); + explicit CGUIComponent(bool); virtual ~CGUIComponent(); void Init(); void Deinit(); From 6231cc7d092ad33585e1da2dca64e75e27b20552 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Tue, 11 Jun 2024 14:53:49 +0200 Subject: [PATCH 181/651] CTextureInfo: add constructor taking all parameters for use in tests --- xbmc/guilib/GUITexture.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/xbmc/guilib/GUITexture.h b/xbmc/guilib/GUITexture.h index ae6825213a1b1..dfaa26653542a 100644 --- a/xbmc/guilib/GUITexture.h +++ b/xbmc/guilib/GUITexture.h @@ -59,6 +59,23 @@ class CTextureInfo public: CTextureInfo(); explicit CTextureInfo(const std::string &file); + CTextureInfo(bool large, + CRect bord, + bool fill, + int orient, + std::string diff, + KODI::GUILIB::GUIINFO::CGUIInfoColor color, + std::string f) + : useLarge(large), + border(bord), + m_infill(fill), + orientation(orient), + diffuse(std::move(diff)), + diffuseColor(color), + filename(std::move(f)) + { + } + bool useLarge; CRect border; // scaled - unneeded if we get rid of scale on load bool m_infill{ From 747024dc33409575cd162771dbabbe3d38ea6ccb Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Tue, 11 Jun 2024 14:54:10 +0200 Subject: [PATCH 182/651] CGUIInfoColor: add constructor taking all params for use in tests --- xbmc/guilib/guiinfo/GUIInfoColor.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xbmc/guilib/guiinfo/GUIInfoColor.h b/xbmc/guilib/guiinfo/GUIInfoColor.h index 6d2a0527543b8..c867c58392f6b 100644 --- a/xbmc/guilib/guiinfo/GUIInfoColor.h +++ b/xbmc/guilib/guiinfo/GUIInfoColor.h @@ -30,7 +30,8 @@ namespace GUIINFO class CGUIInfoColor { public: - constexpr CGUIInfoColor(KODI::UTILS::COLOR::Color color = 0) : m_color(color) {} + constexpr CGUIInfoColor(UTILS::COLOR::Color color = 0) : m_color(color) {} + constexpr CGUIInfoColor(UTILS::COLOR::Color color, int info) : m_info(info), m_color(color) {} constexpr operator KODI::UTILS::COLOR::Color() const { return m_color; } From f6ba20c6a0bb8af00ffac131f3642db98f47eb83 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Tue, 11 Jun 2024 19:46:20 +0200 Subject: [PATCH 183/651] CGUIAction: add comparison operator useful for tests --- xbmc/guilib/GUIAction.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/xbmc/guilib/GUIAction.h b/xbmc/guilib/GUIAction.h index 71a8f0e6733c8..d80d6f3b18384 100644 --- a/xbmc/guilib/GUIAction.h +++ b/xbmc/guilib/GUIAction.h @@ -63,6 +63,11 @@ class CGUIAction */ void SetAction(const std::string& action); + bool operator==(const CExecutableAction& right) const + { + return m_condition == right.m_condition && m_action == right.m_action; + } + private: /** * Executable action default constructor @@ -123,6 +128,11 @@ class CGUIAction */ void Reset(); + bool operator==(const CGUIAction& rhs) const + { + return m_actions == rhs.m_actions && m_sendThreadMessages == rhs.m_sendThreadMessages; + } + private: std::vector m_actions; bool m_sendThreadMessages = false; From e9bdd97f9c73591c5d23f554c1ab9c41d02c6e62 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Fri, 7 Jun 2024 23:08:14 +0200 Subject: [PATCH 184/651] added: some tests for GUIControlFactory not exhaustive in testing logic but aimed at testing the xml parsing bits --- cmake/treedata/common/tests.txt | 1 + xbmc/guilib/test/CMakeLists.txt | 3 + xbmc/guilib/test/TestGUIControlFactory.cpp | 935 +++++++++++++++++++++ 3 files changed, 939 insertions(+) create mode 100644 xbmc/guilib/test/CMakeLists.txt create mode 100644 xbmc/guilib/test/TestGUIControlFactory.cpp diff --git a/cmake/treedata/common/tests.txt b/cmake/treedata/common/tests.txt index d7068b066d0fc..050fc8561f981 100644 --- a/cmake/treedata/common/tests.txt +++ b/cmake/treedata/common/tests.txt @@ -6,6 +6,7 @@ xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test test/videoshaders xbmc/filesystem/test test/filesystem xbmc/games/addons/input/test test/games/addons/input xbmc/games/controllers/input/test test/games/controllers/input +xbmc/guilib/test test/guilib xbmc/imagefiles/test test/imagefiles xbmc/input/keyboard/test test/input/keyboard xbmc/interfaces/python/test test/python diff --git a/xbmc/guilib/test/CMakeLists.txt b/xbmc/guilib/test/CMakeLists.txt new file mode 100644 index 0000000000000..858673b63d1ee --- /dev/null +++ b/xbmc/guilib/test/CMakeLists.txt @@ -0,0 +1,3 @@ +set(SOURCES TestGUIControlFactory.cpp) + +core_add_test_library(guilib_test) diff --git a/xbmc/guilib/test/TestGUIControlFactory.cpp b/xbmc/guilib/test/TestGUIControlFactory.cpp new file mode 100644 index 0000000000000..70f51cd6760af --- /dev/null +++ b/xbmc/guilib/test/TestGUIControlFactory.cpp @@ -0,0 +1,935 @@ +/* + * Copyright (C) 2024 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 "GUIInfoManager.h" +#include "LangInfo.h" +#include "guilib/GUIAction.h" +#include "guilib/GUIColorManager.h" +#include "guilib/GUIControlFactory.h" +#include "guilib/GUIFont.h" +#include "guilib/GUILabelControl.h" +#include "guilib/GUITexture.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/guiinfo/GUIInfoLabel.h" +#include "utils/SystemInfo.h" +#include "utils/XBMCTinyXML.h" + +#include +#include + +#include + +using namespace KODI; + +class CGFTestable : public CGUIControlFactory +{ +public: + static std::string GetType(const TiXmlElement* pControlNode) + { + return CGUIControlFactory::GetType(pControlNode); + } + + static bool GetIntRange(const TiXmlNode* pRootNode, + const char* strTag, + int& iMinValue, + int& iMaxValue, + int& iIntervalValue) + { + return CGUIControlFactory::GetIntRange(pRootNode, strTag, iMinValue, iMaxValue, iIntervalValue); + } + + static bool GetFloatRange(const TiXmlNode* pRootNode, + const char* strTag, + float& fMinValue, + float& fMaxValue, + float& fIntervalValue) + { + return CGUIControlFactory::GetFloatRange(pRootNode, strTag, fMinValue, fMaxValue, + fIntervalValue); + } + + static bool GetPosition(const TiXmlNode* node, + const char* tag, + const float parentSize, + float& value) + { + return CGUIControlFactory::GetPosition(node, tag, parentSize, value); + } + + static bool GetDimension( + const TiXmlNode* node, const char* strTag, const float parentSize, float& value, float& min) + { + return CGUIControlFactory::GetDimension(node, strTag, parentSize, value, min); + } + + static bool GetDimensions(const TiXmlNode* node, + const char* leftTag, + const char* rightTag, + const char* centerLeftTag, + const char* centerRightTag, + const char* widthTag, + const float parentSize, + float& left, + float& width, + float& min_width) + { + return CGUIControlFactory::GetDimensions(node, leftTag, rightTag, centerLeftTag, centerRightTag, + widthTag, parentSize, left, width, min_width); + } + + static bool GetMovingSpeedConfig(const TiXmlNode* pRootNode, + const char* strTag, + UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg) + { + return CGUIControlFactory::GetMovingSpeedConfig(pRootNode, strTag, movingSpeedCfg); + } + + static bool GetConditionalVisibility(const TiXmlNode* control, + std::string& condition, + std::string& allowHiddenFocus) + { + return CGUIControlFactory::GetConditionalVisibility(control, condition, allowHiddenFocus); + } + + static bool GetString(const TiXmlNode* pRootNode, const char* strTag, std::string& strString) + { + return CGUIControlFactory::GetString(pRootNode, strTag, strString); + } +}; + +namespace +{ + +using namespace std::string_literals; + +class CGUITestColorManager : public CGUIColorManager +{ +public: + CGUITestColorManager() + { + CXBMCTinyXML xmlDoc; + xmlDoc.Parse(R"( + ffffffff + 00000000 + ff000080 + )"s); + LoadXML(xmlDoc); + } +}; + +class CGUITestComponent : public CGUIComponent +{ +public: + CGUITestComponent() : CGUIComponent(false) + { + m_guiColorManager = std::make_unique(); + m_guiInfoManager = std::make_unique(); + CServiceBroker::RegisterGUI(this); + } +}; + +struct ActionsTest +{ + std::string def; + std::vector actions; + bool result = true; +}; + +class TestGetActions : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto ActionsTests = std::array{ + ActionsTest{R"( + bar + foo + )"s, + {{"foo"s, "bar"s}, {"bar"s, "foo"s}}}, + ActionsTest{R"(bar)"s, {{""s, "bar"s}}}, + ActionsTest{R"()"s, {}, false}, + ActionsTest{R"()"s, {}, false}, +}; + +struct AlignmentTest +{ + std::string def; + uint32_t align; + bool result = true; +}; + +class TestGetAlignment : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto AlignmentTests = std::array{ + AlignmentTest{R"(right)"s, XBFONT_RIGHT}, + AlignmentTest{R"(bottom)"s, XBFONT_RIGHT}, + AlignmentTest{R"(center)"s, XBFONT_CENTER_X}, + AlignmentTest{R"(justify)"s, XBFONT_JUSTIFIED}, + AlignmentTest{R"(left)"s, XBFONT_LEFT}, + AlignmentTest{R"(foo)"s, XBFONT_LEFT}, + AlignmentTest{R"()"s, 0, false}, + AlignmentTest{R"()"s, 0, false}, +}; + +class TestGetAlignmentY : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto AlignmentYTests = std::array{ + AlignmentTest{R"(center)"s, XBFONT_CENTER_Y}, + AlignmentTest{R"(bottom)"s, 0}, + AlignmentTest{R"()"s, std::numeric_limits::max(), false}, + AlignmentTest{R"()"s, std::numeric_limits::max(), false}, +}; + +struct AspectRatioTest +{ + std::string def; + CAspectRatio ratio; + bool result = true; +}; + +class TestAspectRatio : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto AspectRatioTests = std::array{ + AspectRatioTest{R"(keep)"s, + {CAspectRatio::AR_KEEP, ASPECT_ALIGN_CENTER, true}}, + AspectRatioTest{R"(scale)"s, + {CAspectRatio::AR_SCALE, ASPECT_ALIGN_RIGHT, true}}, + AspectRatioTest{R"(center)"s, + {CAspectRatio::AR_CENTER, ASPECT_ALIGN_LEFT, true}}, + AspectRatioTest{R"(stretch)"s, + {CAspectRatio::AR_STRETCH, ASPECT_ALIGN_CENTER, true}}, + AspectRatioTest{R"(keep)"s, + {CAspectRatio::AR_KEEP, ASPECT_ALIGNY_CENTER, true}}, + AspectRatioTest{R"(keep)"s, + {CAspectRatio::AR_KEEP, ASPECT_ALIGNY_BOTTOM, true}}, + AspectRatioTest{ + R"(keep)"s, + {CAspectRatio::AR_KEEP, ASPECT_ALIGN_RIGHT | ASPECT_ALIGNY_TOP, false}}, + AspectRatioTest{R"(keep)"s, + {CAspectRatio::AR_KEEP, ASPECT_ALIGN_CENTER, false}}, + AspectRatioTest{ + R"()"s, {CAspectRatio::AR_STRETCH, ASPECT_ALIGN_CENTER, true}, false}, + AspectRatioTest{R"()"s, {CAspectRatio::AR_STRETCH, ASPECT_ALIGN_CENTER, true}, false}, +}; + +struct ColorTest +{ + std::string def; + UTILS::COLOR::Color value; + bool result = true; +}; + +class TestGetColor : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto ColorTests = std::array{ + ColorTest{R"(white)"s, 0xFFFFFFFF}, + ColorTest{R"(navy)"s, 0xFF000080}, + ColorTest{R"(black)"s, 0x00000000}, + ColorTest{R"(0x12345678)"s, 0x12345678}, + ColorTest{R"()"s, std::numeric_limits::max(), false}, + ColorTest{R"()"s, std::numeric_limits::max(), false}, +}; + +struct ConditionalVisibilityTest +{ + std::string def; + std::string condition; + std::string hidden; + bool result = true; +}; + +class TestConditionalVisibility : public testing::WithParamInterface, + public testing::Test +{ +}; + +const auto ConditionalVisibilityTests = std::array{ + ConditionalVisibilityTest{R"( + foo + )"s, + "foo"s, ""s}, + ConditionalVisibilityTest{R"( + foo + )"s, + "foo"s, "bar"s}, + ConditionalVisibilityTest{R"( + foo + bar + )"s, + "[foo] + [bar]"s, "foobar"s}, + ConditionalVisibilityTest{R"()", ""s, ""s, false}, +}; + +struct DimensionTest +{ + std::string def; + float min, value; + bool result = true; +}; + +class TestGetDimension : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto DimensionTests = std::array{ + DimensionTest{R"(0.1)"s, 0.f, 0.1f}, + DimensionTest{R"(auto)"s, 1.f, 3.f}, + DimensionTest{R"(auto)"s, 1.f, -2.9f}, + DimensionTest{R"(auto)"s, 1.f, 3.f * 0.1f / 100.f}, + DimensionTest{R"(auto)"s, 2.f, 3.f}, + DimensionTest{R"(auto)"s, -1.9f, 3.f}, + DimensionTest{R"(auto)"s, 2.f * 0.1f / 100.f, + 3.f}, + DimensionTest{R"(something)"s, 0.f, 0.f}, + DimensionTest{R"()"s, 0.f, 0.f, false}, + DimensionTest{R"()"s, 0.f, 0.f, false}, +}; + +struct DimensionsTest +{ + std::string def; + float left, width, min_width; + bool result = true; +}; + +class TestGetDimensions : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto DimensionsTests = std::array{ + DimensionsTest{R"(0.10.2)"s, 0.1f, 0.2f, 0.f}, + DimensionsTest{R"(0.0150%)"s, 0.01f, + 0.1f - 0.1f * 50.f / 100.f - 0.01f, 0.f}, + DimensionsTest{R"(0.025f50%)"s, 0.f, + (0.05f - 0.025f) * 2, 0.f}, + DimensionsTest{ + R"(0.025fauto)"s, + (0.1f - 0.025f) - 0.2f / 2, 0.2f, 0.5f}, +}; + +template +struct RangeTest +{ + std::string def; + T min, max, interval; + bool result = true; +}; + +class TestGetFloatRange : public testing::WithParamInterface>, public testing::Test +{ +}; + +const auto FloatRangeTests = std::array{ + RangeTest{R"(1.0,100.0,0.25)"s, 1.f, 100.f, 0.25f}, + RangeTest{R"(1.0,100.0)"s, 1.f, 100.f, 0.f}, + RangeTest{R"(,100.0,5.0)"s, 0.f, 100.f, 5.f}, + RangeTest{R"(1.0)"s, 1.f, 0.f, 0.f}, + RangeTest{R"()"s, 0.f, 0.f, 0.f, false}, + RangeTest{R"()"s, 0.f, 0.f, 0.f, false}, +}; + +struct HitRectTest +{ + std::string def; + CRect rect; + bool result = true; +}; + +class TestGetHitRect : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto HitRectTests = std::array{ + HitRectTest{R"()"s, + CRect{1.0, 2.0, 4.0, 6.0}}, + HitRectTest{R"()"s, + CRect{0.0, 2.0, 0.0, 6.0}}, + HitRectTest{R"()"s, + CRect{1.0, 2.0, 4.0, 2.0}}, + HitRectTest{R"()"s, CRect{0.0, 0.0, 0.0, 0.0}, false}, +}; + +struct InfoLabelTest +{ + std::string def; + std::string label; + std::string fallback; + bool result = true; +}; + +class TestGetInfoLabel : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto InfoLabelTests = std::array{ + InfoLabelTest{R"(Foo dat bar)"s, "Foo dat bar"s, ""s}, + InfoLabelTest{R"(Foo dat bar)"s, "Foo dat bar"s, "yo"s}, + InfoLabelTest{R"(1)"s, "Pictures"s, "yo"s}, + InfoLabelTest{R"(Bar)"s, "Bar"s, "Pictures"s}, + InfoLabelTest{R"()"s, ""s, ""s, false}, + InfoLabelTest{R"()"s, ""s, ""s, false}, +}; + +struct InfoLabelsTest +{ + std::string def; + std::vector> labels; +}; + +class TestGetInfoLabels : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto InfoLabelsTests = std::array{ + InfoLabelsTest{R"(1234)"s, {{"1234"s, ""s}}}, + InfoLabelsTest{R"(1foo)"s, + {{"Pictures"s, ""s}, {"foo"s, "Pictures"s}}}, + InfoLabelsTest{R"( + foo + System.BuildVersion + System.BuildVersionShort + )"s, + {{CSysInfo::GetVersion(), "foo"s}, {CSysInfo::GetVersionShort(), "foo"s}}}, + InfoLabelsTest{R"(>, public testing::Test +{ +}; + +const auto IntRangeTests = std::array{ + RangeTest{R"(1,100,5)"s, 1, 100, 5}, + RangeTest{R"(1,100)"s, 1, 100, 0}, + RangeTest{R"(,100,5)"s, 0, 100, 5}, + RangeTest{R"(1)"s, 1, 0, 0}, + RangeTest{R"()"s, 0, 0, 0, false}, + RangeTest{R"()"s, 0, 0, 0, false}, +}; + +struct MovingSpeedTest +{ + std::string def; + UTILS::MOVING_SPEED::MapEventConfig config; + bool result = true; +}; + +class TestGetMovingSpeed : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto MovingSpeedTests = std::array{ + MovingSpeedTest{ + R"( + + + + + + )"s, + { + {UTILS::MOVING_SPEED::EventType::UP, UTILS::MOVING_SPEED::EventCfg{1.0, 2.0, 3, 4.0}}, + {UTILS::MOVING_SPEED::EventType::DOWN, UTILS::MOVING_SPEED::EventCfg{2.0, 2.0, 3, 4.0}}, + {UTILS::MOVING_SPEED::EventType::LEFT, UTILS::MOVING_SPEED::EventCfg{1.0, 3.0, 3, 4.0}}, + {UTILS::MOVING_SPEED::EventType::RIGHT, + UTILS::MOVING_SPEED::EventCfg{1.0, 2.0, 4, 4.0}}, + {UTILS::MOVING_SPEED::EventType::NONE, UTILS::MOVING_SPEED::EventCfg{1.0, 2.0, 3, 5.0}}, + }}, + MovingSpeedTest{ + R"( + + + + + + )"s, + { + {UTILS::MOVING_SPEED::EventType::UP, UTILS::MOVING_SPEED::EventCfg{0.0, 0.0, 0, 0.0}}, + {UTILS::MOVING_SPEED::EventType::DOWN, UTILS::MOVING_SPEED::EventCfg{2.0, 0.0, 0, 0.0}}, + {UTILS::MOVING_SPEED::EventType::LEFT, UTILS::MOVING_SPEED::EventCfg{0.0, 3.0, 0, 0.0}}, + {UTILS::MOVING_SPEED::EventType::RIGHT, + UTILS::MOVING_SPEED::EventCfg{0.0, 0.0, 4, 0.0}}, + {UTILS::MOVING_SPEED::EventType::NONE, UTILS::MOVING_SPEED::EventCfg{0.0, 0.0, 0, 5.0}}, + }}, + MovingSpeedTest{R"()"s, {}, true}, + MovingSpeedTest{R"()"s, {}, false}, +}; + +struct PositionTest +{ + std::string def; + float pos; + bool result = true; +}; + +class TestGetPosition : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto PositionTests = std::array{ + PositionTest{R"(0.1)"s, 0.1f}, + PositionTest{R"(0.2r)"s, -0.1f}, + PositionTest{R"(0.2%)"s, 0.2f * 0.1f / 100.f}, + PositionTest{R"(something)"s, 0.f}, + PositionTest{R"()"s, 0.f, false}, + PositionTest{R"()"s, 0.f, false}, +}; + +struct ScrollerTest +{ + std::string def; + int duration; + std::vector> values; + bool result = true; +}; + +class TestGetScroller : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto ScrollerTests = std::array{ + ScrollerTest{R"(400)"s, 400, {{100, 24.75f}, {200, 62.186874f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 24.75f}, {200, 62.186874f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 43.374378f}, {200, 85.701675f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 57.389225f}, {200, 94.593353f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 37.905243f}, {200, 81.640106f}}}, + ScrollerTest{ + R"(400)"s, 400, {{100, 81.236595f}, {200, 101.63f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 65.85923f}, {200, 95.376564f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 46.325039f}, {200, 87.514725f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 91.834221f}, {200, 100.14141f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 24.75f}, {200, 62.186874f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 24.75f}, {200, 62.186874f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 6.1256237f}, {200, 29.360115f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 24.502501f}, {200, 61.87281f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 1.5160923f}, {200, 13.642845f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 6.0643692f}, {200, 88.081032f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 7.4624777f}, {200, 34.309639f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 14.368075f}, {200, 74.68074f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, -6.3273964f}, {200, -15.736771f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, -9.9900274f}, {200, 121.89823f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 3.1112075f}, {200, 15.952465f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 6.5553517f}, {200, 87.345459f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 2.9874623f}, {200, 25.886932f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 11.881172f}, {200, 79.502777f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, -0.5421927f}, {200, -1.9441023f}}}, + ScrollerTest{R"(400)"s, + 400, + {{100, 1.085682f}, {200, 97.521629f}}}, + ScrollerTest{R"()"s, 200, {}, false}, + ScrollerTest{R"()"s, 200, {}, false}, +}; + +struct StringTest +{ + std::string def; + std::string value; + bool result = true; +}; + +class TestGetString : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto StringTests = std::array{ + StringTest{R"(foo)"s, "foo"s}, + StringTest{R"(1)"s, "Pictures"s}, + StringTest{R"(-1)"s, "-1"s}, + StringTest{R"()"s, ""s, true}, + StringTest{R"()"s, ""s, false}, +}; + +struct TextureTest +{ + std::string def; + CTextureInfo info; + bool result = true; +}; + +class TestGetTexture : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto TextureTests = std::array{ + TextureTest{R"(foo.png)"s, + {false, CRect{0.0, 0.0, 0.0, 0.0}, true, 0, ""s, + KODI::GUILIB::GUIINFO::CGUIInfoColor{0, 0}, "foo.png"s}}, + TextureTest{R"(foo.png)"s, + {false, CRect{0.0, 0.0, 0.0, 0.0}, true, 1, ""s, + KODI::GUILIB::GUIINFO::CGUIInfoColor{0, 0}, "foo.png"s}}, + TextureTest{R"(foo.png)"s, + {false, CRect{0.0, 0.0, 0.0, 0.0}, true, 3, ""s, + KODI::GUILIB::GUIINFO::CGUIInfoColor{0, 0}, "foo.png"s}}, + TextureTest{R"(foo.png)"s, + {false, CRect{0.0, 0.0, 0.0, 0.0}, true, 2, ""s, + KODI::GUILIB::GUIINFO::CGUIInfoColor{0, 0}, "foo.png"s}}, + TextureTest{R"(foo.png)"s, + {false, CRect{1.0, 2.0, 3.0, 4.0}, true, 0, ""s, + KODI::GUILIB::GUIINFO::CGUIInfoColor{0, 0}, "foo.png"s}}, + TextureTest{R"(foo.png)"s, + {false, CRect{1.0, 2.0, 3.0, 4.0}, false, 0, ""s, + KODI::GUILIB::GUIINFO::CGUIInfoColor{0, 0}, "foo.png"s}}, + TextureTest{R"(foo.png)"s, + {false, CRect{0.0, 0.0, 0.0, 0.0}, true, 0, "bar.png"s, + KODI::GUILIB::GUIINFO::CGUIInfoColor{0, 0}, "foo.png"s}}, + TextureTest{R"(foo.png)"s, + {false, CRect{0.0, 0.0, 0.0, 0.0}, true, 0, ""s, + KODI::GUILIB::GUIINFO::CGUIInfoColor{1, 0}, "foo.png"s}}, + TextureTest{R"(foo.png)"s, + {false, CRect{0.0, 0.0, 0.0, 0.0}, true, 0, ""s, + KODI::GUILIB::GUIINFO::CGUIInfoColor{0xFFFFFFFF, 0}, "foo.png"s}}, + TextureTest{R"(foo.png)"s, + {true, CRect{0.0, 0.0, 0.0, 0.0}, true, 0, ""s, + KODI::GUILIB::GUIINFO::CGUIInfoColor{0, 0}, "foo.png"s}}, + TextureTest{R"()"s, + {false, CRect{0.0, 0.0, 0.0, 0.0}, true, 0, ""s, + KODI::GUILIB::GUIINFO::CGUIInfoColor{0, 0}, ""s}, + true}, + TextureTest{R"()"s, + {false, CRect{0.0, 0.0, 0.0, 0.0}, true, 0, ""s, + KODI::GUILIB::GUIINFO::CGUIInfoColor{0, 0}, ""s}, + false}, +}; + +struct TypeTest +{ + std::string def; + std::string type; +}; + +class TestGetType : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto TypeTests = std::array{ + TypeTest{R"()"s, "foo"s}, + TypeTest{R"(foo)"s, "foo"s}, + TypeTest{R"()"s, ""s}, +}; + +} // namespace + +TEST_P(TestGetActions, GetActions) +{ + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + CGUIAction action; + EXPECT_EQ(CGFTestable::GetActions(doc.RootElement(), "test", action), GetParam().result); + EXPECT_EQ(action.GetActionCount(), GetParam().actions.size()); + CGUIAction ref; + for (const auto& act : GetParam().actions) + ref.Append(act); + EXPECT_EQ(action, ref); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, TestGetActions, testing::ValuesIn(ActionsTests)); + +TEST_P(TestGetAlignment, GetAlignment) +{ + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + uint32_t align{}; + EXPECT_EQ(CGFTestable::GetAlignment(doc.RootElement(), "test", align), GetParam().result); + EXPECT_EQ(align, GetParam().align); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, + TestGetAlignment, + testing::ValuesIn(AlignmentTests)); + +TEST_P(TestGetAlignmentY, GetAlignmentY) +{ + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + uint32_t align = std::numeric_limits::max(); + EXPECT_EQ(CGFTestable::GetAlignmentY(doc.RootElement(), "test", align), GetParam().result); + EXPECT_EQ(align, GetParam().align); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, + TestGetAlignmentY, + testing::ValuesIn(AlignmentYTests)); + +TEST_P(TestAspectRatio, GetAspectRatio) +{ + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + CAspectRatio ratio; + EXPECT_EQ(CGFTestable::GetAspectRatio(doc.RootElement(), "test", ratio), GetParam().result); + EXPECT_EQ(ratio.ratio, GetParam().ratio.ratio); + EXPECT_EQ(ratio.align, GetParam().ratio.align); + EXPECT_EQ(ratio.scaleDiffuse, GetParam().ratio.scaleDiffuse); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, + TestAspectRatio, + testing::ValuesIn(AspectRatioTests)); + +TEST_P(TestGetColor, GetColor) +{ + CGUITestComponent comp; + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + UTILS::COLOR::Color value = std::numeric_limits::max(); + EXPECT_EQ(CGFTestable::GetColor(doc.RootElement(), "test", value), GetParam().result); + EXPECT_EQ(value, GetParam().value); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, TestGetColor, testing::ValuesIn(ColorTests)); + +TEST_P(TestGetDimension, GetDimension) +{ + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + float min = 0.f, value = 0.f; + EXPECT_EQ(CGFTestable::GetDimension(doc.RootElement(), "test", 0.1f, value, min), + GetParam().result); + EXPECT_EQ(min, GetParam().min); + EXPECT_EQ(value, GetParam().value); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, + TestGetDimension, + testing::ValuesIn(DimensionTests)); + +TEST_P(TestGetDimensions, GetDimensions) +{ + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + float left = 0.f, width = 0.f, min_width = 0.f; + EXPECT_EQ(CGFTestable::GetDimensions(doc.RootElement(), "left", "right", "center_left", + "center_right", "width", 0.1f, left, width, min_width), + GetParam().result); + EXPECT_EQ(left, GetParam().left); + EXPECT_EQ(width, GetParam().width); + EXPECT_EQ(min_width, GetParam().min_width); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, + TestGetDimensions, + testing::ValuesIn(DimensionsTests)); + +TEST_P(TestGetFloatRange, GetFloatRange) +{ + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + float min = 0.f, max = 0.f, interval = 0.f; + EXPECT_EQ(CGFTestable::GetFloatRange(doc.RootElement(), "test", min, max, interval), + GetParam().result); + EXPECT_EQ(min, GetParam().min); + EXPECT_EQ(max, GetParam().max); + EXPECT_EQ(interval, GetParam().interval); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, + TestGetFloatRange, + testing::ValuesIn(FloatRangeTests)); + +TEST_P(TestGetHitRect, GetHitRect) +{ + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + CRect rect, parentRect{1.0, 2.0, 3.0, 4.0}; + EXPECT_EQ(CGFTestable::GetHitRect(doc.RootElement(), rect, parentRect), GetParam().result); + EXPECT_EQ(rect.x1, GetParam().rect.x1); + EXPECT_EQ(rect.x2, GetParam().rect.x2); + EXPECT_EQ(rect.y1, GetParam().rect.y1); + EXPECT_EQ(rect.y2, GetParam().rect.y2); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, TestGetHitRect, testing::ValuesIn(HitRectTests)); + +TEST_P(TestGetInfoLabel, GetInfoLabel) +{ + CGUITestComponent comp; + ASSERT_TRUE(g_localizeStrings.Load(g_langInfo.GetLanguagePath(), "resource.language.en_gb")); + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + KODI::GUILIB::GUIINFO::CGUIInfoLabel infoLabel; + EXPECT_EQ(CGFTestable::GetInfoLabelFromElement(doc.RootElement(), infoLabel, 100), + GetParam().result); + EXPECT_EQ(infoLabel.GetLabel(INFO::DEFAULT_CONTEXT), GetParam().label); + EXPECT_EQ(infoLabel.GetFallback(), GetParam().fallback); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, + TestGetInfoLabel, + testing::ValuesIn(InfoLabelTests)); + +TEST_P(TestGetInfoLabels, GetInfoLabels) +{ + CGUITestComponent comp; + ASSERT_TRUE(g_localizeStrings.Load(g_langInfo.GetLanguagePath(), "resource.language.en_gb")); + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + std::vector infoLabel; + CGFTestable::GetInfoLabels(doc.RootElement(), "test", infoLabel, 100); + EXPECT_EQ(infoLabel.size(), GetParam().labels.size()); + for (size_t i = 0; i < infoLabel.size(); ++i) + { + EXPECT_EQ(infoLabel[i].GetLabel(INFO::DEFAULT_CONTEXT), GetParam().labels[i][0]); + EXPECT_EQ(infoLabel[i].GetFallback(), GetParam().labels[i][1]); + } +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, + TestGetInfoLabels, + testing::ValuesIn(InfoLabelsTests)); + +TEST_P(TestGetIntRange, GetIntRange) +{ + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + int min = 0, max = 0, interval = 0; + EXPECT_EQ(CGFTestable::GetIntRange(doc.RootElement(), "test", min, max, interval), + GetParam().result); + EXPECT_EQ(min, GetParam().min); + EXPECT_EQ(max, GetParam().max); + EXPECT_EQ(interval, GetParam().interval); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, TestGetIntRange, testing::ValuesIn(IntRangeTests)); + +TEST_P(TestGetMovingSpeed, GetMovingSpeed) +{ + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + UTILS::MOVING_SPEED::MapEventConfig config; + EXPECT_EQ(CGFTestable::GetMovingSpeedConfig(doc.RootElement(), "test", config), + GetParam().result); + EXPECT_EQ(config, GetParam().config); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, + TestGetMovingSpeed, + testing::ValuesIn(MovingSpeedTests)); + +TEST_P(TestGetPosition, GetPosition) +{ + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + float pos = 0.f; + EXPECT_EQ(CGFTestable::GetPosition(doc.RootElement(), "test", 0.1f, pos), GetParam().result); + EXPECT_EQ(pos, GetParam().pos); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, TestGetPosition, testing::ValuesIn(PositionTests)); + +TEST_P(TestGetScroller, GetScroller) +{ + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + CScroller value; + EXPECT_EQ(CGFTestable::GetScroller(doc.RootElement(), "test", value), GetParam().result); + EXPECT_EQ(value.GetDuration(), GetParam().duration); + if (!GetParam().values.empty()) + { + value.ScrollTo(GetParam().values[0].first); + EXPECT_TRUE(value.Update(1)); + } + for (const auto& def : GetParam().values) + { + EXPECT_TRUE(value.Update(def.first)); + EXPECT_FLOAT_EQ(value.GetValue(), def.second); + value.ScrollTo(def.first); + EXPECT_TRUE(value.Update(1)); + } +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, TestGetScroller, testing::ValuesIn(ScrollerTests)); + +TEST_P(TestGetString, GetString) +{ + ASSERT_TRUE(g_localizeStrings.Load(g_langInfo.GetLanguagePath(), "resource.language.en_gb")); + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + std::string value; + EXPECT_EQ(CGFTestable::GetString(doc.RootElement(), "test", value), GetParam().result); + EXPECT_EQ(value, GetParam().value); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, TestGetString, testing::ValuesIn(StringTests)); + +TEST_P(TestGetTexture, GetTexture) +{ + CGUITestComponent comp; + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + CTextureInfo info; + EXPECT_EQ(CGFTestable::GetTexture(doc.RootElement(), "test", info), GetParam().result); + EXPECT_EQ(info.border, GetParam().info.border); + EXPECT_EQ(info.diffuse, GetParam().info.diffuse); + EXPECT_EQ(info.diffuseColor, GetParam().info.diffuseColor); + EXPECT_EQ(info.filename, GetParam().info.filename); + EXPECT_EQ(info.m_infill, GetParam().info.m_infill); + EXPECT_EQ(info.orientation, GetParam().info.orientation); + EXPECT_EQ(info.useLarge, GetParam().info.useLarge); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, TestGetTexture, testing::ValuesIn(TextureTests)); + +TEST_P(TestGetType, GetType) +{ + CXBMCTinyXML doc; + doc.Parse(GetParam().def); + std::string type; + EXPECT_EQ(CGFTestable::GetType(doc.RootElement()), GetParam().type); +} + +INSTANTIATE_TEST_SUITE_P(TestGUIControlFactory, TestGetType, testing::ValuesIn(TypeTests)); From 290aeec0a5b02745a798559442f477be884ae46d Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Sun, 11 Feb 2024 00:59:50 +0100 Subject: [PATCH 185/651] move CFileItem::IsPlayList to PlayListFileItemClassify --- xbmc/FileItem.cpp | 21 +++---- xbmc/FileItem.h | 1 - xbmc/FileItemList.cpp | 9 ++- xbmc/PlayListPlayer.cpp | 3 +- xbmc/Util.cpp | 7 ++- xbmc/application/Application.cpp | 7 ++- xbmc/cores/VideoPlayer/DVDFileInfo.cpp | 4 +- xbmc/interfaces/builtins/PlayerBuiltins.cpp | 5 +- xbmc/interfaces/json-rpc/FileOperations.cpp | 7 ++- xbmc/interfaces/legacy/PlayList.cpp | 3 +- xbmc/music/MusicInfoLoader.cpp | 7 ++- xbmc/music/MusicUtils.cpp | 5 +- xbmc/music/infoscanner/MusicInfoScanner.cpp | 10 +-- xbmc/music/windows/GUIWindowMusicBase.cpp | 23 ++++--- xbmc/music/windows/GUIWindowMusicNav.cpp | 20 +++--- xbmc/network/upnp/UPnPInternal.cpp | 5 +- xbmc/network/upnp/UPnPServer.cpp | 3 +- .../pictures/PictureFolderImageFileLoader.cpp | 4 +- xbmc/pictures/PictureThumbLoader.cpp | 13 ++-- xbmc/playlists/CMakeLists.txt | 2 + xbmc/playlists/PlayListFileItemClassify.cpp | 22 +++++++ xbmc/playlists/PlayListFileItemClassify.h | 19 ++++++ xbmc/playlists/test/CMakeLists.txt | 1 + .../test/TestPlayListFileItemClassify.cpp | 63 +++++++++++++++++++ xbmc/utils/ExecString.cpp | 3 +- xbmc/video/VideoInfoScanner.cpp | 10 +-- xbmc/video/VideoUtils.cpp | 9 +-- xbmc/video/guilib/VideoGUIUtils.cpp | 5 +- xbmc/video/windows/GUIWindowVideoBase.cpp | 7 ++- xbmc/video/windows/GUIWindowVideoNav.cpp | 14 +++-- xbmc/view/GUIViewState.cpp | 3 +- xbmc/windows/GUIMediaWindow.cpp | 7 ++- xbmc/windows/GUIWindowFileManager.cpp | 6 +- 33 files changed, 235 insertions(+), 93 deletions(-) create mode 100644 xbmc/playlists/PlayListFileItemClassify.cpp create mode 100644 xbmc/playlists/PlayListFileItemClassify.h create mode 100644 xbmc/playlists/test/TestPlayListFileItemClassify.cpp diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 8b7012a0e68ce..9791d29be3a6c 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -36,6 +36,7 @@ #include "pictures/PictureInfoTag.h" #include "playlists/PlayList.h" #include "playlists/PlayListFactory.h" +#include "playlists/PlayListFileItemClassify.h" #include "pvr/PVRManager.h" #include "pvr/channels/PVRChannel.h" #include "pvr/channels/PVRChannelGroupMember.h" @@ -981,7 +982,7 @@ bool CFileItem::IsFileFolder(EFileFolderType types) const if(types & always_type) { if (IsSmartPlayList() || - (IsPlayList() && + (PLAYLIST::IsPlayList(*this) && CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders) || IsAPK() || IsZIP() || IsRAR() || IsRSS() || MUSIC::IsAudioBook(*this) || IsType(".ogg|.oga|.xbt") @@ -999,8 +1000,9 @@ bool CFileItem::IsFileFolder(EFileFolderType types) const if(types & EFILEFOLDER_TYPE_ONBROWSE) { - if((IsPlayList() && !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders) - || IsDiscImage()) + if ((PLAYLIST::IsPlayList(*this) && + !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders) || + IsDiscImage()) return true; } @@ -1023,11 +1025,6 @@ bool CFileItem::IsLibraryFolder() const return URIUtils::IsLibraryFolder(m_strPath); } -bool CFileItem::IsPlayList() const -{ - return CPlayListFactory::IsPlaylist(*this); -} - bool CFileItem::IsPythonScript() const { return URIUtils::HasExtension(m_strPath, ".py"); @@ -1283,7 +1280,7 @@ void CFileItem::FillInDefaultIcon() // picture SetArt("icon", "DefaultPicture.png"); } - else if ( IsPlayList() || IsSmartPlayList()) + else if (PLAYLIST::IsPlayList(*this) || IsSmartPlayList()) { SetArt("icon", "DefaultPlaylist.png"); } @@ -1303,7 +1300,7 @@ void CFileItem::FillInDefaultIcon() } else { - if ( IsPlayList() || IsSmartPlayList()) + if (PLAYLIST::IsPlayList(*this) || IsSmartPlayList()) { SetArt("icon", "DefaultPlaylist.png"); } @@ -2534,7 +2531,7 @@ bool CFileItem::LoadDetails() return false; } - if (!IsPlayList() && VIDEO::IsVideo(*this)) + if (!PLAYLIST::IsPlayList(*this) && VIDEO::IsVideo(*this)) { if (HasVideoInfoTag()) return true; @@ -2558,7 +2555,7 @@ bool CFileItem::LoadDetails() return false; } - if (IsPlayList() && IsType(".strm")) + if (PLAYLIST::IsPlayList(*this) && IsType(".strm")) { const std::unique_ptr playlist(PLAYLIST::CPlayListFactory::Create(*this)); if (playlist) diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index 7083cf77e0bb7..9f7de3d6e3b98 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -172,7 +172,6 @@ class CFileItem : bool IsDeleted() const; bool IsGame() const; - bool IsPlayList() const; bool IsSmartPlayList() const; bool IsLibraryFolder() const; bool IsPythonScript() const; diff --git a/xbmc/FileItemList.cpp b/xbmc/FileItemList.cpp index e14b9a6c61516..d3b05e837a418 100644 --- a/xbmc/FileItemList.cpp +++ b/xbmc/FileItemList.cpp @@ -18,6 +18,7 @@ #include "filesystem/VideoDatabaseDirectory.h" #include "music/MusicFileItemClassify.h" #include "network/NetworkFileItemClassify.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/AdvancedSettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" @@ -654,7 +655,8 @@ void CFileItemList::FilterCueItems() { strMediaFile = URIUtils::ReplaceExtension(pItem->GetPath(), *i); CFileItem item(strMediaFile, false); - if (!MUSIC::IsCUESheet(item) && !item.IsPlayList() && Contains(strMediaFile)) + if (!MUSIC::IsCUESheet(item) && !PLAYLIST::IsPlayList(item) && + Contains(strMediaFile)) { bFoundMediaFile = true; break; @@ -847,7 +849,8 @@ void CFileItemList::StackFiles() CFileItemPtr item1 = Get(i); // skip folders, nfo files, playlists - if (item1->m_bIsFolder || item1->IsParentFolder() || item1->IsNFO() || item1->IsPlayList()) + if (item1->m_bIsFolder || item1->IsParentFolder() || item1->IsNFO() || + PLAYLIST::IsPlayList(*item1)) { // increment index i++; @@ -882,7 +885,7 @@ void CFileItemList::StackFiles() // skip folders, nfo files, playlists if (item2->m_bIsFolder || item2->IsParentFolder() || item2->IsNFO() || - item2->IsPlayList()) + PLAYLIST::IsPlayList(*item2)) { // increment index j++; diff --git a/xbmc/PlayListPlayer.cpp b/xbmc/PlayListPlayer.cpp index 346727ca55b7e..8d45e7f611bb8 100644 --- a/xbmc/PlayListPlayer.cpp +++ b/xbmc/PlayListPlayer.cpp @@ -32,6 +32,7 @@ #include "music/MusicFileItemClassify.h" #include "music/tags/MusicInfoTag.h" #include "playlists/PlayList.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" #include "utils/StringUtils.h" @@ -973,7 +974,7 @@ void PLAYLIST::CPlayListPlayer::OnApplicationMessage(KODI::MESSAGING::ThreadMess ClearPlaylist(playlistId); SetCurrentPlaylist(playlistId); - if (list->Size() == 1 && !(*list)[0]->IsPlayList()) + if (list->Size() == 1 && !IsPlayList(*list->Get(0))) { CFileItemPtr item = (*list)[0]; // if the item is a plugin we need to resolve the URL to ensure the infotags are filled. diff --git a/xbmc/Util.cpp b/xbmc/Util.cpp index eb10ab14fcf16..0b507b502bf91 100644 --- a/xbmc/Util.cpp +++ b/xbmc/Util.cpp @@ -8,6 +8,7 @@ #include "network/Network.h" #include "network/NetworkFileItemClassify.h" +#include "playlists/PlayListFileItemClassify.h" #include "video/VideoFileItemClassify.h" #if defined(TARGET_DARWIN) #include @@ -2057,7 +2058,7 @@ void CUtil::ScanForExternalSubtitles(const std::string& strMovie, std::vector& vecAudio) { CFileItem item(videoPath, false); - if (NETWORK::IsInternetStream(item) || item.IsPlayList() || item.IsLiveTV() || item.IsPVR() || - !VIDEO::IsVideo(item)) + if (NETWORK::IsInternetStream(item) || PLAYLIST::IsPlayList(item) || item.IsLiveTV() || + item.IsPVR() || !VIDEO::IsVideo(item)) return; std::string strBasePath; diff --git a/xbmc/application/Application.cpp b/xbmc/application/Application.cpp index fba4b3763e23d..336e6ed5c0231 100644 --- a/xbmc/application/Application.cpp +++ b/xbmc/application/Application.cpp @@ -62,6 +62,7 @@ #include "filesystem/File.h" #include "music/MusicFileItemClassify.h" #include "network/NetworkFileItemClassify.h" +#include "playlists/PlayListFileItemClassify.h" #include "video/VideoFileItemClassify.h" #ifdef HAS_FILESYSTEM_NFS #include "filesystem/NFSFile.h" @@ -2192,7 +2193,7 @@ bool CApplication::PlayMedia(CFileItem& item, const std::string& player, PLAYLIS return ProcessAndStartPlaylist(smartpl.GetName(), playlist, smartplPlaylistId); } } - else if (item.IsPlayList() || NETWORK::IsInternetStream(item)) + else if (PLAYLIST::IsPlayList(item) || NETWORK::IsInternetStream(item)) { // Not owner. Dialog auto-deletes itself. CGUIDialogCache* dlgCache = @@ -2317,7 +2318,7 @@ bool CApplication::PlayFile(CFileItem item, return CServiceBroker::GetMediaManager().playStubFile(item); } - if (item.IsPlayList()) + if (PLAYLIST::IsPlayList(item)) return false; // Translate/Resolve the url if needed @@ -2842,7 +2843,7 @@ bool CApplication::OnMessage(CGUIMessage& message) std::unique_ptr trailerItem = ContentUtils::GeneratePlayableTrailerItem(*item, g_localizeStrings.Get(20410)); - if (item->IsPlayList()) + if (PLAYLIST::IsPlayList(*item)) { std::unique_ptr fileitemList = std::make_unique(); fileitemList->Add(std::move(trailerItem)); diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp index 9989176bb4d95..33ab24885aaeb 100644 --- a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp +++ b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp @@ -17,6 +17,7 @@ #include "guilib/Texture.h" #include "network/NetworkFileItemClassify.h" #include "pictures/Picture.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" #include "utils/MemUtils.h" @@ -263,7 +264,8 @@ bool CDVDFileInfo::CanExtract(const CFileItem& fileItem) URIUtils::IsPVRRecording(fileItem.GetDynPath()) || // plugin path not fully resolved URIUtils::IsPlugin(fileItem.GetDynPath()) || URIUtils::IsUPnP(fileItem.GetPath()) || - NETWORK::IsInternetStream(fileItem) || VIDEO::IsDiscStub(fileItem) || fileItem.IsPlayList()) + NETWORK::IsInternetStream(fileItem) || VIDEO::IsDiscStub(fileItem) || + PLAYLIST::IsPlayList(fileItem)) return false; // mostly can't extract from discs and files from discs. diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.cpp b/xbmc/interfaces/builtins/PlayerBuiltins.cpp index 2d7f92608f877..9ee63f3281d4c 100644 --- a/xbmc/interfaces/builtins/PlayerBuiltins.cpp +++ b/xbmc/interfaces/builtins/PlayerBuiltins.cpp @@ -27,6 +27,7 @@ #include "music/MusicFileItemClassify.h" #include "music/MusicUtils.h" #include "playlists/PlayList.h" +#include "playlists/PlayListFileItemClassify.h" #include "pvr/PVRManager.h" #include "pvr/channels/PVRChannel.h" #include "pvr/guilib/PVRGUIActionsChannels.h" @@ -538,7 +539,7 @@ int PlayOrQueueMedia(const std::vector& params, bool forcePlay) } item.SetProperty("check_resume", false); - if (!forcePlay /* queue */ || item.m_bIsFolder || item.IsPlayList()) + if (!forcePlay /* queue */ || item.m_bIsFolder || PLAYLIST::IsPlayList(item)) { CFileItemList items; GetItemsForPlayList(std::make_shared(item), items); @@ -560,7 +561,7 @@ int PlayOrQueueMedia(const std::vector& params, bool forcePlay) // Mixed playlist item played by music player, mixed content folder has music removed if (containsMusic && containsVideo) { - if (item.IsPlayList()) + if (PLAYLIST::IsPlayList(item)) playlistId = PLAYLIST::Id::TYPE_MUSIC; else { diff --git a/xbmc/interfaces/json-rpc/FileOperations.cpp b/xbmc/interfaces/json-rpc/FileOperations.cpp index a495607ff97bf..91222f280b278 100644 --- a/xbmc/interfaces/json-rpc/FileOperations.cpp +++ b/xbmc/interfaces/json-rpc/FileOperations.cpp @@ -18,6 +18,7 @@ #include "VideoLibrary.h" #include "filesystem/Directory.h" #include "media/MediaLockState.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/AdvancedSettings.h" #include "settings/MediaSourceSettings.h" #include "settings/SettingsComponent.h" @@ -29,8 +30,9 @@ #include -using namespace XFILE; +using namespace KODI; using namespace JSONRPC; +using namespace XFILE; JSONRPC_STATUS CFileOperations::GetRootDirectory(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) { @@ -378,7 +380,8 @@ bool CFileOperations::FillFileItemList(const CVariant ¶meterObject, CFileIte { // Sort folders and files by filename to avoid reverse item order bug on some platforms, // but leave items from a playlist, smartplaylist or upnp container in order supplied - if (!items.IsPlayList() && !items.IsSmartPlayList() && !URIUtils::IsUPnP(items.GetPath())) + if (!PLAYLIST::IsPlayList(items) && !items.IsSmartPlayList() && + !URIUtils::IsUPnP(items.GetPath())) items.Sort(SortByFile, SortOrderAscending); CFileItemList filteredDirectories; diff --git a/xbmc/interfaces/legacy/PlayList.cpp b/xbmc/interfaces/legacy/PlayList.cpp index 076e1928b131a..3d28645812c3e 100644 --- a/xbmc/interfaces/legacy/PlayList.cpp +++ b/xbmc/interfaces/legacy/PlayList.cpp @@ -12,6 +12,7 @@ #include "PlayListPlayer.h" #include "ServiceBroker.h" #include "playlists/PlayListFactory.h" +#include "playlists/PlayListFileItemClassify.h" #include "utils/URIUtils.h" using namespace KODI; @@ -64,7 +65,7 @@ namespace XBMCAddon CFileItem item(cFileName); item.SetPath(cFileName); - if (item.IsPlayList()) + if (PLAYLIST::IsPlayList(item)) { // load playlist and copy al items to existing playlist diff --git a/xbmc/music/MusicInfoLoader.cpp b/xbmc/music/MusicInfoLoader.cpp index a6bff1a36ef46..1a034e2a3fcd4 100644 --- a/xbmc/music/MusicInfoLoader.cpp +++ b/xbmc/music/MusicInfoLoader.cpp @@ -22,6 +22,7 @@ #include "music/tags/MusicInfoTag.h" #include "music/tags/MusicInfoTagLoaderFactory.h" #include "network/NetworkFileItemClassify.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/Archive.h" @@ -75,7 +76,7 @@ void CMusicInfoLoader::OnLoaderStart() bool CMusicInfoLoader::LoadAdditionalTagInfo(CFileItem* pItem) { - if (!pItem || (pItem->m_bIsFolder && !MUSIC::IsAudio(*pItem)) || pItem->IsPlayList() || + if (!pItem || (pItem->m_bIsFolder && !MUSIC::IsAudio(*pItem)) || PLAYLIST::IsPlayList(*pItem) || pItem->IsNFO() || NETWORK::IsInternetStream(*pItem)) return false; @@ -149,7 +150,7 @@ bool CMusicInfoLoader::LoadItem(CFileItem* pItem) bool CMusicInfoLoader::LoadItemCached(CFileItem* pItem) { - if ((pItem->m_bIsFolder && !MUSIC::IsAudio(*pItem)) || pItem->IsPlayList() || + if ((pItem->m_bIsFolder && !MUSIC::IsAudio(*pItem)) || PLAYLIST::IsPlayList(*pItem) || pItem->IsSmartPlayList() || StringUtils::StartsWithNoCase(pItem->GetPath(), "newplaylist://") || StringUtils::StartsWithNoCase(pItem->GetPath(), "newsmartplaylist://") || pItem->IsNFO() || @@ -168,7 +169,7 @@ bool CMusicInfoLoader::LoadItemLookup(CFileItem* pItem) m_pProgressCallback->SetProgressAdvance(); if ((pItem->m_bIsFolder && !MUSIC::IsAudio(*pItem)) || // - pItem->IsPlayList() || pItem->IsSmartPlayList() || // + PLAYLIST::IsPlayList(*pItem) || pItem->IsSmartPlayList() || // StringUtils::StartsWithNoCase(pItem->GetPath(), "newplaylist://") || // StringUtils::StartsWithNoCase(pItem->GetPath(), "newsmartplaylist://") || // pItem->IsNFO() || (NETWORK::IsInternetStream(*pItem) && !MUSIC::IsMusicDb(*pItem))) diff --git a/xbmc/music/MusicUtils.cpp b/xbmc/music/MusicUtils.cpp index 5434688a8c67d..54b45d24cf531 100644 --- a/xbmc/music/MusicUtils.cpp +++ b/xbmc/music/MusicUtils.cpp @@ -34,6 +34,7 @@ #include "network/NetworkFileItemClassify.h" #include "playlists/PlayList.h" #include "playlists/PlayListFactory.h" +#include "playlists/PlayListFileItemClassify.h" #include "profiles/ProfileManager.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" @@ -581,7 +582,7 @@ void CAsyncGetItemsForPlaylist::GetItemsForPlaylist(const std::shared_ptrIsPlayList()) + if (PLAYLIST::IsPlayList(*item)) { const std::unique_ptr playList( PLAYLIST::CPlayListFactory::Create(*item)); @@ -892,7 +893,7 @@ bool IsItemPlayable(const CFileItem& item) return false; // Include playlists located at one of the possible music playlist locations - if (item.IsPlayList()) + if (PLAYLIST::IsPlayList(item)) { if (StringUtils::StartsWithNoCase(item.GetMimeType(), "audio/")) return true; diff --git a/xbmc/music/infoscanner/MusicInfoScanner.cpp b/xbmc/music/infoscanner/MusicInfoScanner.cpp index 3f8c5791aff23..6f546a63f9372 100644 --- a/xbmc/music/infoscanner/MusicInfoScanner.cpp +++ b/xbmc/music/infoscanner/MusicInfoScanner.cpp @@ -44,6 +44,7 @@ #include "music/MusicUtils.h" #include "music/tags/MusicInfoTag.h" #include "music/tags/MusicInfoTagLoaderFactory.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/AdvancedSettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" @@ -554,7 +555,7 @@ bool CMusicInfoScanner::DoScan(const std::string& strDirectory) if (m_bStop) break; // if we have a directory item (non-playlist) we then recurse into that folder - if (pItem->m_bIsFolder && !pItem->IsParentFolder() && !pItem->IsPlayList()) + if (pItem->m_bIsFolder && !pItem->IsParentFolder() && !PLAYLIST::IsPlayList(*pItem)) { std::string strPath=pItem->GetPath(); if (!DoScan(strPath)) @@ -581,7 +582,8 @@ CInfoScanner::INFO_RET CMusicInfoScanner::ScanTags(const CFileItemList& items, if (CUtil::ExcludeFileOrFolder(pItem->GetPath(), regexps)) continue; - if (pItem->m_bIsFolder || pItem->IsPlayList() || pItem->IsPicture() || MUSIC::IsLyrics(*pItem)) + if (pItem->m_bIsFolder || PLAYLIST::IsPlayList(*pItem) || pItem->IsPicture() || + MUSIC::IsLyrics(*pItem)) continue; m_currentItem++; @@ -1275,7 +1277,7 @@ int CMusicInfoScanner::GetPathHash(const CFileItemList &items, std::string &hash digest.Update((unsigned char *)&pItem->m_dwSize, sizeof(pItem->m_dwSize)); KODI::TIME::FileTime time = pItem->m_dateTime; digest.Update((unsigned char*)&time, sizeof(KODI::TIME::FileTime)); - if (MUSIC::IsAudio(*pItem) && !pItem->IsPlayList() && !pItem->IsNFO()) + if (MUSIC::IsAudio(*pItem) && !PLAYLIST::IsPlayList(*pItem) && !pItem->IsNFO()) count++; } hash = digest.Finalize(); @@ -2336,7 +2338,7 @@ int CMusicInfoScanner::CountFiles(const CFileItemList &items, bool recursive) if (recursive && pItem->m_bIsFolder) count+=CountFilesRecursively(pItem->GetPath()); - else if (MUSIC::IsAudio(*pItem) && !pItem->IsPlayList() && !pItem->IsNFO()) + else if (MUSIC::IsAudio(*pItem) && !PLAYLIST::IsPlayList(*pItem) && !pItem->IsNFO()) count++; } return count; diff --git a/xbmc/music/windows/GUIWindowMusicBase.cpp b/xbmc/music/windows/GUIWindowMusicBase.cpp index 7bef12c9c0500..404b15ad7a2e6 100644 --- a/xbmc/music/windows/GUIWindowMusicBase.cpp +++ b/xbmc/music/windows/GUIWindowMusicBase.cpp @@ -25,6 +25,7 @@ #include "application/ApplicationPlayer.h" #include "music/MusicFileItemClassify.h" #include "network/NetworkFileItemClassify.h" +#include "playlists/PlayListFileItemClassify.h" #include "video/VideoFileItemClassify.h" #ifdef HAS_CDDA_RIPPER #include "cdrip/CDDARipper.h" @@ -78,7 +79,6 @@ using namespace MUSIC_INFO; using namespace KODI; using namespace KODI::MESSAGING; using KODI::MESSAGING::HELPERS::DialogResponse; -using namespace KODI::VIDEO; using namespace std::chrono_literals; @@ -303,7 +303,7 @@ void CGUIWindowMusicBase::OnItemInfo(int iItem) CFileItemPtr item = m_vecItems->Get(iItem); // Match visibility test of CMusicInfo::IsVisible - if (IsVideoDb(*item) && item->HasVideoInfoTag() && + if (VIDEO::IsVideoDb(*item) && item->HasVideoInfoTag() && (item->HasProperty("artist_musicid") || item->HasProperty("album_musicid"))) { // Music video artist or album (navigation by music > music video > artist)) @@ -311,7 +311,7 @@ void CGUIWindowMusicBase::OnItemInfo(int iItem) return; } - if (IsVideo(*item) && item->HasVideoInfoTag() && + if (VIDEO::IsVideo(*item) && item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_type == MediaTypeMusicVideo) { // Music video on a mixed current playlist or navigation by music > music video > artist > video CGUIDialogVideoInfo::ShowFor(*item); @@ -354,8 +354,8 @@ void CGUIWindowMusicBase::RetrieveMusicInfo() for (int i = 0; i < m_vecItems->Size(); ++i) { CFileItemPtr pItem = (*m_vecItems)[i]; - if (pItem->m_bIsFolder || pItem->IsPlayList() || pItem->IsPicture() || - MUSIC::IsLyrics(*pItem) || IsVideo(*pItem)) + if (pItem->m_bIsFolder || PLAYLIST::IsPlayList(*pItem) || pItem->IsPicture() || + MUSIC::IsLyrics(*pItem) || VIDEO::IsVideo(*pItem)) continue; CMusicInfoTag& tag = *pItem->GetMusicInfoTag(); @@ -453,7 +453,7 @@ void CGUIWindowMusicBase::GetContextButtons(int itemNumber, CContextButtons &but if (item->IsSmartPlayList() || m_vecItems->IsSmartPlayList()) buttons.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, 586); - else if (item->IsPlayList() || m_vecItems->IsPlayList()) + else if (PLAYLIST::IsPlayList(*item) || PLAYLIST::IsPlayList(*m_vecItems)) buttons.Add(CONTEXT_BUTTON_EDIT, 586); } #ifdef HAS_OPTICAL_DRIVE @@ -505,7 +505,10 @@ bool CGUIWindowMusicBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) case CONTEXT_BUTTON_EDIT: { - std::string playlist = item->IsPlayList() ? item->GetPath() : m_vecItems->GetPath(); // save path as activatewindow will destroy our items + std::string playlist = + PLAYLIST::IsPlayList(*item) + ? item->GetPath() + : m_vecItems->GetPath(); // save path as activatewindow will destroy our items CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST_EDITOR, playlist); // need to update m_vecItems->RemoveDiscCache(GetID()); @@ -655,7 +658,7 @@ void CGUIWindowMusicBase::PlayItem(int iItem) // play! CServiceBroker::GetPlaylistPlayer().Play(); } - else if (pItem->IsPlayList()) + else if (PLAYLIST::IsPlayList(*pItem)) { // load the playlist the old way LoadPlayList(pItem->GetPath()); @@ -712,7 +715,7 @@ bool CGUIWindowMusicBase::OnPlayMedia(int iItem, const std::string &player) g_partyModeManager.AddUserSongs(playlistTemp, !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICPLAYER_QUEUEBYDEFAULT)); return true; } - else if (!pItem->IsPlayList() && !NETWORK::IsInternetStream(*pItem)) + else if (!PLAYLIST::IsPlayList(*pItem) && !NETWORK::IsInternetStream(*pItem)) { // single music file - if we get here then we have autoplaynextitem turned off or queuebydefault // turned on, but we still want to use the playlist player in order to handle more queued items // following etc. @@ -734,7 +737,7 @@ bool CGUIWindowMusicBase::OnPlayMedia(int iItem, const std::string &player) void CGUIWindowMusicBase::OnRetrieveMusicInfo(CFileItemList& items) { // No need to attempt to read music file tags for music videos - if (IsVideoDb(items)) + if (VIDEO::IsVideoDb(items)) return; if (items.GetFolderCount() == items.Size() || MUSIC::IsMusicDb(items) || (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( diff --git a/xbmc/music/windows/GUIWindowMusicNav.cpp b/xbmc/music/windows/GUIWindowMusicNav.cpp index b54eb59e93881..2d65816d33843 100644 --- a/xbmc/music/windows/GUIWindowMusicNav.cpp +++ b/xbmc/music/windows/GUIWindowMusicNav.cpp @@ -36,6 +36,7 @@ #include "network/NetworkFileItemClassify.h" #include "playlists/PlayList.h" #include "playlists/PlayListFactory.h" +#include "playlists/PlayListFileItemClassify.h" #include "profiles/ProfileManager.h" #include "settings/AdvancedSettings.h" #include "settings/Settings.h" @@ -355,8 +356,8 @@ bool CGUIWindowMusicNav::OnClick(int iItem, const std::string &player /* = "" */ if (MUSIC::IsMusicDb(*item) && !item->m_bIsFolder) m_musicdatabase.SetPropertiesForFileItem(*item); - if (item->IsPlayList() && - !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders) + if (PLAYLIST::IsPlayList(*item) && + !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders) { PlayItem(iItem); return true; @@ -386,7 +387,7 @@ bool CGUIWindowMusicNav::GetDirectory(const std::string &strDirectory, CFileItem bool bResult = CGUIWindowMusicBase::GetDirectory(strDirectory, items); if (bResult) { - if (items.IsPlayList()) + if (PLAYLIST::IsPlayList(items)) OnRetrieveMusicInfo(items); } @@ -471,7 +472,7 @@ bool CGUIWindowMusicNav::GetDirectory(const std::string &strDirectory, CFileItem break; } } - else if (items.IsPlayList()) + else if (PLAYLIST::IsPlayList(items)) items.SetContent("songs"); else if (URIUtils::PathEquals(strDirectory, "special://musicplaylists/") || URIUtils::PathEquals(strDirectory, "library://music/playlists.xml/")) @@ -518,7 +519,7 @@ void CGUIWindowMusicNav::UpdateButtons() if (m_vecItems->IsPath("special://musicplaylists/")) strLabel = g_localizeStrings.Get(136); // "{Playlist Name}" - else if (m_vecItems->IsPlayList()) + else if (PLAYLIST::IsPlayList(*m_vecItems)) { // get playlist name from path std::string strDummy; @@ -618,7 +619,8 @@ void CGUIWindowMusicNav::GetContextButtons(int itemNumber, CContextButtons &butt item->m_bIsFolder && // Folders only, but playlists can be folders too !URIUtils::IsLibraryContent(item->GetPath()) && // database folder or .xsp files !URIUtils::IsSpecial(item->GetPath()) && !item->IsPlugin() && !item->IsScript() && - !item->IsPlayList() && // .m3u etc. that as flagged as folders when playlistasfolders + !PLAYLIST::IsPlayList( + *item) && // .m3u etc. that as flagged as folders when playlistasfolders !StringUtils::StartsWithNoCase(item->GetPath(), "addons://") && (profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser)) @@ -686,8 +688,8 @@ void CGUIWindowMusicNav::GetContextButtons(int itemNumber, CContextButtons &butt buttons.Add(CONTEXT_BUTTON_DELETE, 646); } } - if (inPlaylists && URIUtils::GetFileName(item->GetPath()) != "PartyMode.xsp" - && (item->IsPlayList() || item->IsSmartPlayList())) + if (inPlaylists && URIUtils::GetFileName(item->GetPath()) != "PartyMode.xsp" && + (PLAYLIST::IsPlayList(*item) || item->IsSmartPlayList())) buttons.Add(CONTEXT_BUTTON_DELETE, 117); if (!item->IsReadOnly() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool("filelists.allowfiledeletion")) @@ -822,7 +824,7 @@ bool CGUIWindowMusicNav::OnContextButton(int itemNumber, CONTEXT_BUTTON button) return true; case CONTEXT_BUTTON_DELETE: - if (item->IsPlayList() || item->IsSmartPlayList()) + if (PLAYLIST::IsPlayList(*item) || item->IsSmartPlayList()) { item->m_bIsFolder = false; CGUIComponent *gui = CServiceBroker::GetGUI(); diff --git a/xbmc/network/upnp/UPnPInternal.cpp b/xbmc/network/upnp/UPnPInternal.cpp index f1b7ea4bb2387..f70c0ae285a2d 100644 --- a/xbmc/network/upnp/UPnPInternal.cpp +++ b/xbmc/network/upnp/UPnPInternal.cpp @@ -20,6 +20,7 @@ #include "imagefiles/ImageFileURL.h" #include "music/MusicFileItemClassify.h" #include "music/tags/MusicInfoTag.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/AdvancedSettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" @@ -715,7 +716,7 @@ PLT_MediaObject* BuildObject(CFileItem& item, break; } } - else if (item.IsPlayList() || item.IsSmartPlayList()) + else if (PLAYLIST::IsPlayList(item) || item.IsSmartPlayList()) { container->m_ObjectClass.type += ".playlistContainer"; } @@ -749,7 +750,7 @@ PLT_MediaObject* BuildObject(CFileItem& item, if (!item.GetLabel().empty()) { std::string title = item.GetLabel(); - if (item.IsPlayList() || !item.m_bIsFolder) + if (PLAYLIST::IsPlayList(item) || !item.m_bIsFolder) URIUtils::RemoveExtension(title); object->m_Title = title.c_str(); } diff --git a/xbmc/network/upnp/UPnPServer.cpp b/xbmc/network/upnp/UPnPServer.cpp index 93e5a00db9b3d..275354dd8eec0 100644 --- a/xbmc/network/upnp/UPnPServer.cpp +++ b/xbmc/network/upnp/UPnPServer.cpp @@ -30,6 +30,7 @@ #include "music/MusicLibraryQueue.h" #include "music/MusicThumbLoader.h" #include "music/tags/MusicInfoTag.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/Digest.h" @@ -449,7 +450,7 @@ PLT_MediaObject* CUPnPServer::Build(const std::shared_ptr& item, } } // all playlist types are folders - else if (item->IsPlayList() || item->IsSmartPlayList()) + else if (PLAYLIST::IsPlayList(*item) || item->IsSmartPlayList()) { item->m_bIsFolder = true; } diff --git a/xbmc/pictures/PictureFolderImageFileLoader.cpp b/xbmc/pictures/PictureFolderImageFileLoader.cpp index 1bebd0c225a7a..0370bc50d0108 100644 --- a/xbmc/pictures/PictureFolderImageFileLoader.cpp +++ b/xbmc/pictures/PictureFolderImageFileLoader.cpp @@ -16,10 +16,12 @@ #include "filesystem/Directory.h" #include "guilib/Texture.h" #include "imagefiles/ImageFileURL.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/FileExtensionProvider.h" +using namespace KODI; using namespace XFILE; bool CPictureFolderImageFileLoader::CanLoad(const std::string& specialType) const @@ -38,7 +40,7 @@ std::unique_ptr CPictureFolderImageFileLoader::Load( for (int i = 0; i < imagesInFolder.Size();) { if (!imagesInFolder[i]->IsPicture() || imagesInFolder[i]->IsZIP() || - imagesInFolder[i]->IsRAR() || imagesInFolder[i]->IsPlayList()) + imagesInFolder[i]->IsRAR() || PLAYLIST::IsPlayList(*imagesInFolder[i])) { imagesInFolder.Remove(i); } diff --git a/xbmc/pictures/PictureThumbLoader.cpp b/xbmc/pictures/PictureThumbLoader.cpp index 4a8a3dce94683..d26c7bab38061 100644 --- a/xbmc/pictures/PictureThumbLoader.cpp +++ b/xbmc/pictures/PictureThumbLoader.cpp @@ -19,6 +19,7 @@ #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" #include "imagefiles/ImageFileURL.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/AdvancedSettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" @@ -28,7 +29,7 @@ #include "video/VideoFileItemClassify.h" #include "video/VideoThumbLoader.h" -using namespace KODI::VIDEO; +using namespace KODI; using namespace XFILE; CPictureThumbLoader::CPictureThumbLoader() : CThumbLoader() @@ -78,13 +79,14 @@ bool CPictureThumbLoader::LoadItemCached(CFileItem* pItem) } std::string thumb; - if (pItem->IsPicture() && !pItem->IsZIP() && !pItem->IsRAR() && !pItem->IsCBZ() && !pItem->IsCBR() && !pItem->IsPlayList()) + if (pItem->IsPicture() && !pItem->IsZIP() && !pItem->IsRAR() && !pItem->IsCBZ() && + !pItem->IsCBR() && !PLAYLIST::IsPlayList(*pItem)) { // load the thumb from the image file thumb = pItem->HasArt("thumb") ? pItem->GetArt("thumb") : IMAGE_FILES::URLFromFile(pItem->GetPath()); } - else if (IsVideo(*pItem) && !pItem->IsZIP() && !pItem->IsRAR() && !pItem->IsCBZ() && - !pItem->IsCBR() && !pItem->IsPlayList()) + else if (VIDEO::IsVideo(*pItem) && !pItem->IsZIP() && !pItem->IsRAR() && !pItem->IsCBZ() && + !pItem->IsCBR() && !PLAYLIST::IsPlayList(*pItem)) { // video CVideoThumbLoader loader; loader.LoadItem(pItem); @@ -165,7 +167,8 @@ void CPictureThumbLoader::ProcessFoldersAndArchives(CFileItem *pItem) // count the number of images for (int i=0; i < items.Size();) { - if (!items[i]->IsPicture() || items[i]->IsZIP() || items[i]->IsRAR() || items[i]->IsPlayList()) + if (!items[i]->IsPicture() || items[i]->IsZIP() || items[i]->IsRAR() || + PLAYLIST::IsPlayList(*items[i])) { items.Remove(i); } diff --git a/xbmc/playlists/CMakeLists.txt b/xbmc/playlists/CMakeLists.txt index e3fe1cf8fbca0..b9215a39c3868 100644 --- a/xbmc/playlists/CMakeLists.txt +++ b/xbmc/playlists/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES PlayList.cpp PlayListASX.cpp PlayListB4S.cpp PlayListFactory.cpp + PlayListFileItemClassify.cpp PlayListM3U.cpp PlayListPLS.cpp PlayListRAM.cpp @@ -16,6 +17,7 @@ set(HEADERS PlayList.h PlayListASX.h PlayListB4S.h PlayListFactory.h + PlayListFileItemClassify.h PlayListM3U.h PlayListPLS.h PlayListRAM.h diff --git a/xbmc/playlists/PlayListFileItemClassify.cpp b/xbmc/playlists/PlayListFileItemClassify.cpp new file mode 100644 index 0000000000000..3ca3ec8a25393 --- /dev/null +++ b/xbmc/playlists/PlayListFileItemClassify.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2005-2020 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 "playlists/PlayListFileItemClassify.h" + +#include "FileItem.h" +#include "playlists/PlayListFactory.h" + +namespace KODI::PLAYLIST +{ + +bool IsPlayList(const CFileItem& item) +{ + return CPlayListFactory::IsPlaylist(item); +} + +} // namespace KODI::PLAYLIST diff --git a/xbmc/playlists/PlayListFileItemClassify.h b/xbmc/playlists/PlayListFileItemClassify.h new file mode 100644 index 0000000000000..18a25d3781a62 --- /dev/null +++ b/xbmc/playlists/PlayListFileItemClassify.h @@ -0,0 +1,19 @@ +/* + * 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 + +class CFileItem; + +namespace KODI::PLAYLIST +{ + +//! \brief Check whether an item is a playlist. +bool IsPlayList(const CFileItem& item); + +} // namespace KODI::PLAYLIST diff --git a/xbmc/playlists/test/CMakeLists.txt b/xbmc/playlists/test/CMakeLists.txt index 25e076d9b0d80..af584c511e7bc 100644 --- a/xbmc/playlists/test/CMakeLists.txt +++ b/xbmc/playlists/test/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES TestPlayListASX.cpp TestPlayListB4S.cpp TestPlayListFactory.cpp + TestPlayListFileItemClassify.cpp TestPlayListWPL.cpp TestPlayListXML.cpp TestPlayListXSPF.cpp) diff --git a/xbmc/playlists/test/TestPlayListFileItemClassify.cpp b/xbmc/playlists/test/TestPlayListFileItemClassify.cpp new file mode 100644 index 0000000000000..95912d7fb2070 --- /dev/null +++ b/xbmc/playlists/test/TestPlayListFileItemClassify.cpp @@ -0,0 +1,63 @@ +/* + * 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 "FileItem.h" +#include "playlists/PlayListFileItemClassify.h" +#include "utils/Variant.h" + +#include + +#include + +using namespace KODI; + +struct PlayListClassifyTest +{ + PlayListClassifyTest(const std::string& path, bool res, const std::string& mime = "") + : item(path, false), result(res) + { + if (!mime.empty()) + item.SetMimeType(mime); + } + + CFileItem item; + bool result; +}; + +class PlayListTest : public testing::WithParamInterface, public testing::Test +{ +}; + +TEST_P(PlayListTest, IsPlayList) +{ + EXPECT_EQ(PLAYLIST::IsPlayList(GetParam().item), GetParam().result); +} + +const auto playlist_tests = std::array{ + PlayListClassifyTest{"/home/user/video.avi", false}, + PlayListClassifyTest{"/home/user/video.avi", false, "video/avi"}, + PlayListClassifyTest{"https://some.where/foo.m3u8", false}, + PlayListClassifyTest{"https://some.where/something", true, "audio/x-pn-realaudio"}, + PlayListClassifyTest{"https://some.where/something", true, "playlist"}, + PlayListClassifyTest{"https://some.where/something", true, "audio/x-mpegurl"}, + PlayListClassifyTest{"/home/user/video.m3u", true}, + PlayListClassifyTest{"/home/user/video.m3u8", true}, + PlayListClassifyTest{"/home/user/video.b4s", true}, + PlayListClassifyTest{"/home/user/video.pls", true}, + PlayListClassifyTest{"/home/user/video.strm", true}, + PlayListClassifyTest{"/home/user/video.wpl", true}, + PlayListClassifyTest{"/home/user/video.asx", true}, + PlayListClassifyTest{"/home/user/video.ram", true}, + PlayListClassifyTest{"/home/user/video.url", true}, + PlayListClassifyTest{"/home/user/video.pxml", true}, + PlayListClassifyTest{"/home/user/video.xspf", true}, +}; + +INSTANTIATE_TEST_SUITE_P(TestPlayListFileItemClassify, + PlayListTest, + testing::ValuesIn(playlist_tests)); diff --git a/xbmc/utils/ExecString.cpp b/xbmc/utils/ExecString.cpp index 95164ce7ebcd0..5c55840c40305 100644 --- a/xbmc/utils/ExecString.cpp +++ b/xbmc/utils/ExecString.cpp @@ -14,6 +14,7 @@ #include "Util.h" #include "music/MusicFileItemClassify.h" #include "music/tags/MusicInfoTag.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" #include "utils/StringUtils.h" @@ -106,7 +107,7 @@ bool CExecString::Parse(const CFileItem& item, const std::string& contextWindow) } else if (item.m_bIsFolder && (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders || - !(item.IsSmartPlayList() || item.IsPlayList()))) + !(item.IsSmartPlayList() || PLAYLIST::IsPlayList(item)))) { if (!contextWindow.empty()) Build("ActivateWindow", {contextWindow, StringUtils::Paramify(item.GetPath()), "return"}); diff --git a/xbmc/video/VideoInfoScanner.cpp b/xbmc/video/VideoInfoScanner.cpp index ae74d1f38c206..9dc3588a5ce76 100644 --- a/xbmc/video/VideoInfoScanner.cpp +++ b/xbmc/video/VideoInfoScanner.cpp @@ -33,6 +33,7 @@ #include "interfaces/AnnouncementManager.h" #include "messaging/helpers/DialogHelper.h" #include "messaging/helpers/DialogOKHelper.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/AdvancedSettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" @@ -431,7 +432,8 @@ namespace KODI::VIDEO // if we have a directory item (non-playlist) we then recurse into that folder // do not recurse for tv shows - we have already looked recursively for episodes - if (pItem->m_bIsFolder && !pItem->IsParentFolder() && !pItem->IsPlayList() && settings.recurse > 0 && content != CONTENT_TVSHOWS) + if (pItem->m_bIsFolder && !pItem->IsParentFolder() && !PLAYLIST::IsPlayList(*pItem) && + settings.recurse > 0 && content != CONTENT_TVSHOWS) { if (!DoScan(pItem->GetPath())) { @@ -726,7 +728,7 @@ namespace KODI::VIDEO CGUIDialogProgress* pDlgProgress) { if (pItem->m_bIsFolder || !IsVideo(*pItem) || pItem->IsNFO() || - (pItem->IsPlayList() && !URIUtils::HasExtension(pItem->GetPath(), ".strm"))) + (PLAYLIST::IsPlayList(*pItem) && !URIUtils::HasExtension(pItem->GetPath(), ".strm"))) return INFO_NOT_NEEDED; if (ProgressCancelled(pDlgProgress, 198, pItem->GetLabel())) @@ -831,7 +833,7 @@ namespace KODI::VIDEO CGUIDialogProgress* pDlgProgress) { if (pItem->m_bIsFolder || !IsVideo(*pItem) || pItem->IsNFO() || - (pItem->IsPlayList() && !URIUtils::HasExtension(pItem->GetPath(), ".strm"))) + (PLAYLIST::IsPlayList(*pItem) && !URIUtils::HasExtension(pItem->GetPath(), ".strm"))) return INFO_NOT_NEEDED; if (ProgressCancelled(pDlgProgress, 20394, pItem->GetLabel())) @@ -2207,7 +2209,7 @@ namespace KODI::VIDEO KODI::TIME::FileTime time = pItem->m_dateTime; digest.Update(&time, sizeof(KODI::TIME::FileTime)); } - if (IsVideo(*pItem) && !pItem->IsPlayList() && !pItem->IsNFO()) + if (IsVideo(*pItem) && !PLAYLIST::IsPlayList(*pItem) && !pItem->IsNFO()) count++; } hash = digest.Finalize(); diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index a3258bbac8079..eb6ab9674d3f3 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -14,6 +14,7 @@ #include "Util.h" #include "filesystem/Directory.h" #include "filesystem/VideoDatabaseDirectory/QueryParams.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/SettingUtils.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" @@ -30,7 +31,7 @@ #include #include -using namespace KODI::VIDEO; +using namespace KODI; namespace { @@ -99,7 +100,7 @@ KODI::VIDEO::UTILS::ResumeInformation GetNonFolderItemResumeInformation(const CF return {}; // do not resume playlists, except strm files - if (!item.IsType(".strm") && item.IsPlayList()) + if (!item.IsType(".strm") && PLAYLIST::IsPlayList(item)) return {}; // do not resume Live TV and 'deleted' items (e.g. trashed pvr recordings) @@ -128,13 +129,13 @@ KODI::VIDEO::UTILS::ResumeInformation GetNonFolderItemResumeInformation(const CF } std::string path = item.GetPath(); - if (IsVideoDb(item) || item.IsDVD()) + if (VIDEO::IsVideoDb(item) || item.IsDVD()) { if (item.HasVideoInfoTag()) { path = item.GetVideoInfoTag()->m_strFileNameAndPath; } - else if (IsVideoDb(item)) + else if (VIDEO::IsVideoDb(item)) { // Obtain path+filename from video db XFILE::VIDEODATABASEDIRECTORY::CQueryParams params; diff --git a/xbmc/video/guilib/VideoGUIUtils.cpp b/xbmc/video/guilib/VideoGUIUtils.cpp index 1352ba8c932a9..8197097611e9c 100644 --- a/xbmc/video/guilib/VideoGUIUtils.cpp +++ b/xbmc/video/guilib/VideoGUIUtils.cpp @@ -27,6 +27,7 @@ #include "network/NetworkFileItemClassify.h" #include "playlists/PlayList.h" #include "playlists/PlayListFactory.h" +#include "playlists/PlayListFileItemClassify.h" #include "profiles/ProfileManager.h" #include "settings/MediaSettings.h" #include "settings/SettingUtils.h" @@ -291,7 +292,7 @@ void CAsyncGetItemsForPlaylist::GetItemsForPlaylist(const std::shared_ptrIsPlayList()) + else if (PLAYLIST::IsPlayList(*item)) { // just queue the playlist, it will be expanded on play m_queuedItems.Add(item); @@ -533,7 +534,7 @@ bool IsItemPlayable(const CFileItem& item) return false; // Include playlists located at one of the possible video/mixed playlist locations - if (item.IsPlayList()) + if (PLAYLIST::IsPlayList(item)) { if (StringUtils::StartsWithNoCase(item.GetMimeType(), "video/")) return true; diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index eded77d85d85f..5fe114cc765fa 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -40,6 +40,7 @@ #include "network/NetworkFileItemClassify.h" #include "playlists/PlayList.h" #include "playlists/PlayListFactory.h" +#include "playlists/PlayListFileItemClassify.h" #include "profiles/ProfileManager.h" #include "settings/AdvancedSettings.h" #include "settings/Settings.h" @@ -213,7 +214,7 @@ bool CGUIWindowVideoBase::OnMessage(CGUIMessage& message) bool CGUIWindowVideoBase::OnItemInfo(const CFileItem& fileItem) { if (fileItem.IsParentFolder() || fileItem.m_bIsShareOrDrive || fileItem.IsPath("add") || - (fileItem.IsPlayList() && !URIUtils::HasExtension(fileItem.GetDynPath(), ".strm"))) + (PLAYLIST::IsPlayList(fileItem) && !URIUtils::HasExtension(fileItem.GetDynPath(), ".strm"))) return false; // "Videos/Video Add-ons" lists addons in the video window @@ -303,7 +304,7 @@ bool CGUIWindowVideoBase::OnItemInfo(const CFileItem& fileItem) ->m_moviesExcludeFromScanRegExps; for (const auto& i : items) { - if (VIDEO::IsVideo(*i) && !i->IsPlayList() && + if (VIDEO::IsVideo(*i) && !PLAYLIST::IsPlayList(*i) && !CUtil::ExcludeFileOrFolder(i->GetPath(), excludeFromScan)) { item.SetPath(i->GetPath()); @@ -1152,7 +1153,7 @@ bool CGUIWindowVideoBase::PlayItem(const std::shared_ptr& pItem, CServiceBroker::GetPlaylistPlayer().Play(); return true; } - else if (pItem->IsPlayList() && !pItem->IsType(".strm")) + else if (PLAYLIST::IsPlayList(*pItem) && !pItem->IsType(".strm")) { // Note: strm files being somehow special playlists need to be handled in OnPlay*Media diff --git a/xbmc/video/windows/GUIWindowVideoNav.cpp b/xbmc/video/windows/GUIWindowVideoNav.cpp index a53d72443b3a2..aaba0fe5c2ae5 100644 --- a/xbmc/video/windows/GUIWindowVideoNav.cpp +++ b/xbmc/video/windows/GUIWindowVideoNav.cpp @@ -28,6 +28,7 @@ #include "messaging/ApplicationMessenger.h" #include "messaging/helpers/DialogOKHelper.h" #include "music/MusicDatabase.h" +#include "playlists/PlayListFileItemClassify.h" #include "profiles/ProfileManager.h" #include "settings/AdvancedSettings.h" #include "settings/MediaSettings.h" @@ -540,7 +541,7 @@ void CGUIWindowVideoNav::UpdateButtons() if (m_vecItems->IsPath("special://videoplaylists/")) strLabel = g_localizeStrings.Get(136); // "{Playlist Name}" - else if (m_vecItems->IsPlayList()) + else if (PLAYLIST::IsPlayList(*m_vecItems)) { // get playlist name from path std::string strDummy; @@ -829,16 +830,17 @@ void CGUIWindowVideoNav::GetContextButtons(int itemNumber, CContextButtons &butt if (!VIDEO::IsVideoDb(*m_vecItems) && !m_vecItems->IsVirtualDirectoryRoot()) { // non-video db items, file operations are allowed - if ((CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_ALLOWFILEDELETION) && - CUtil::SupportsWriteFileOperations(item->GetPath())) || - (inPlaylists && URIUtils::GetFileName(item->GetPath()) != "PartyMode-Video.xsp" - && (item->IsPlayList() || item->IsSmartPlayList()))) + if ((CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_FILELISTS_ALLOWFILEDELETION) && + CUtil::SupportsWriteFileOperations(item->GetPath())) || + (inPlaylists && URIUtils::GetFileName(item->GetPath()) != "PartyMode-Video.xsp" && + (PLAYLIST::IsPlayList(*item) || item->IsSmartPlayList()))) { buttons.Add(CONTEXT_BUTTON_DELETE, 117); buttons.Add(CONTEXT_BUTTON_RENAME, 118); } // add "Set/Change content" to folders - if (item->m_bIsFolder && !VIDEO::IsVideoDb(*item) && !item->IsPlayList() && + if (item->m_bIsFolder && !VIDEO::IsVideoDb(*item) && !PLAYLIST::IsPlayList(*item) && !item->IsSmartPlayList() && !item->IsLibraryFolder() && !item->IsLiveTV() && !item->IsPlugin() && !item->IsAddonsPath() && !URIUtils::IsUPnP(item->GetPath())) { diff --git a/xbmc/view/GUIViewState.cpp b/xbmc/view/GUIViewState.cpp index cebf9319bebae..22d56735c2bc0 100644 --- a/xbmc/view/GUIViewState.cpp +++ b/xbmc/view/GUIViewState.cpp @@ -31,6 +31,7 @@ #include "guilib/TextureManager.h" #include "music/GUIViewStateMusic.h" #include "pictures/GUIViewStatePictures.h" +#include "playlists/PlayListFileItemClassify.h" #include "profiles/ProfileManager.h" #include "programs/GUIViewStatePrograms.h" #include "pvr/windows/GUIViewStatePVR.h" @@ -95,7 +96,7 @@ CGUIViewState* CGUIViewState::GetViewState(int windowId, const CFileItemList& it if (url.IsProtocol("library")) return new CGUIViewStateLibrary(items); - if (items.IsPlayList()) + if (PLAYLIST::IsPlayList(items)) { // Playlists (like .strm) can be music or video type if (windowId == WINDOW_VIDEO_NAV) diff --git a/xbmc/windows/GUIMediaWindow.cpp b/xbmc/windows/GUIMediaWindow.cpp index dcb54555031dc..afb236c2050a5 100644 --- a/xbmc/windows/GUIMediaWindow.cpp +++ b/xbmc/windows/GUIMediaWindow.cpp @@ -25,6 +25,7 @@ #include "application/Application.h" #include "messaging/ApplicationMessenger.h" #include "network/NetworkFileItemClassify.h" +#include "playlists/PlayListFileItemClassify.h" #if defined(TARGET_ANDROID) #include "platform/android/activity/XBMCApp.h" #endif @@ -1501,7 +1502,7 @@ bool CGUIMediaWindow::OnPlayMedia(int iItem, const std::string &player) CLog::Log(LOGDEBUG, "{} {}", __FUNCTION__, CURL::GetRedacted(pItem->GetPath())); bool bResult = false; - if (NETWORK::IsInternetStream(*pItem) || pItem->IsPlayList()) + if (NETWORK::IsInternetStream(*pItem) || PLAYLIST::IsPlayList(*pItem)) bResult = g_application.PlayMedia(*pItem, player, m_guiState->GetPlaylist()); else bResult = g_application.PlayFile(*pItem, player); @@ -1603,7 +1604,7 @@ void CGUIMediaWindow::UpdateFileList() if (pItem->m_bIsFolder) continue; - if (!pItem->IsPlayList() && !pItem->IsZIP() && !pItem->IsRAR()) + if (!PLAYLIST::IsPlayList(*pItem) && !pItem->IsZIP() && !pItem->IsRAR()) CServiceBroker::GetPlaylistPlayer().Add(playlistId, pItem); if (pItem->GetPath() == playlistItem.GetPath() && @@ -1619,7 +1620,7 @@ void CGUIMediaWindow::OnDeleteItem(int iItem) if ( iItem < 0 || iItem >= m_vecItems->Size()) return; CFileItemPtr item = m_vecItems->Get(iItem); - if (item->IsPlayList()) + if (PLAYLIST::IsPlayList(*item)) item->m_bIsFolder = false; const std::shared_ptr profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); diff --git a/xbmc/windows/GUIWindowFileManager.cpp b/xbmc/windows/GUIWindowFileManager.cpp index b25c1e49244f9..5c288c0209a38 100644 --- a/xbmc/windows/GUIWindowFileManager.cpp +++ b/xbmc/windows/GUIWindowFileManager.cpp @@ -45,6 +45,7 @@ #include "platform/Filesystem.h" #include "playlists/PlayList.h" #include "playlists/PlayListFactory.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/MediaSourceSettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" @@ -62,7 +63,6 @@ using namespace XFILE; using namespace KODI; using namespace KODI::MESSAGING; -using namespace KODI::VIDEO; #define CONTROL_BTNSELECTALL 1 #define CONTROL_BTNFAVOURITES 2 @@ -644,7 +644,7 @@ void CGUIWindowFileManager::OnClick(int iList, int iItem) void CGUIWindowFileManager::OnStart(CFileItem *pItem, const std::string &player) { // start playlists from file manager - if (pItem->IsPlayList()) + if (PLAYLIST::IsPlayList(*pItem)) { const std::string& strPlayList = pItem->GetPath(); std::unique_ptr pPlayList(PLAYLIST::CPlayListFactory::Create(strPlayList)); @@ -659,7 +659,7 @@ void CGUIWindowFileManager::OnStart(CFileItem *pItem, const std::string &player) g_application.ProcessAndStartPlaylist(strPlayList, *pPlayList, PLAYLIST::Id::TYPE_MUSIC); return; } - if (MUSIC::IsAudio(*pItem) || IsVideo(*pItem)) + if (MUSIC::IsAudio(*pItem) || VIDEO::IsVideo(*pItem)) { CServiceBroker::GetPlaylistPlayer().Play(std::make_shared(*pItem), player); return; From 92ca2329fc1493fb87596ffd36243370be796b09 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Sun, 11 Feb 2024 00:59:50 +0100 Subject: [PATCH 186/651] move CFileItem::IsSmartPlayList to PlayListFileItemClassify --- xbmc/FileItem.cpp | 16 ++++------------ xbmc/FileItem.h | 1 - xbmc/FileItemList.cpp | 2 +- xbmc/application/Application.cpp | 2 +- xbmc/filesystem/Directory.cpp | 3 ++- xbmc/interfaces/builtins/PlayerBuiltins.cpp | 3 ++- xbmc/interfaces/json-rpc/FileOperations.cpp | 2 +- xbmc/music/GUIViewStateMusic.cpp | 5 +++-- xbmc/music/MusicInfoLoader.cpp | 4 ++-- xbmc/music/MusicUtils.cpp | 4 ++-- xbmc/music/windows/GUIWindowMusicBase.cpp | 15 +++++++++------ xbmc/music/windows/GUIWindowMusicNav.cpp | 19 +++++++++---------- xbmc/network/upnp/UPnPInternal.cpp | 2 +- xbmc/network/upnp/UPnPServer.cpp | 2 +- xbmc/playlists/PlayListFileItemClassify.cpp | 12 +++++++++++- xbmc/playlists/PlayListFileItemClassify.h | 5 ++++- .../test/TestPlayListFileItemClassify.cpp | 13 +++++++++++++ xbmc/utils/ExecString.cpp | 2 +- xbmc/video/GUIViewStateVideo.cpp | 9 +++++---- xbmc/video/guilib/VideoGUIUtils.cpp | 2 +- xbmc/video/windows/GUIWindowVideoBase.cpp | 9 ++++++--- xbmc/video/windows/GUIWindowVideoNav.cpp | 6 +++--- xbmc/view/GUIViewState.cpp | 3 +-- 23 files changed, 83 insertions(+), 58 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 9791d29be3a6c..69a23f7ee747d 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -979,9 +979,9 @@ bool CFileItem::IsFileFolder(EFileFolderType types) const if (IsType(".strm") && (types & EFILEFOLDER_TYPE_ONBROWSE)) return false; - if(types & always_type) + if (types & always_type) { - if (IsSmartPlayList() || + if (PLAYLIST::IsSmartPlayList(*this) || (PLAYLIST::IsPlayList(*this) && CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders) || IsAPK() || IsZIP() || IsRAR() || IsRSS() || MUSIC::IsAudioBook(*this) || @@ -1009,14 +1009,6 @@ bool CFileItem::IsFileFolder(EFileFolderType types) const return false; } -bool CFileItem::IsSmartPlayList() const -{ - if (HasProperty("library.smartplaylist") && GetProperty("library.smartplaylist").asBoolean()) - return true; - - return URIUtils::HasExtension(m_strPath, ".xsp"); -} - bool CFileItem::IsLibraryFolder() const { if (HasProperty("library.filter") && GetProperty("library.filter").asBoolean()) @@ -1280,7 +1272,7 @@ void CFileItem::FillInDefaultIcon() // picture SetArt("icon", "DefaultPicture.png"); } - else if (PLAYLIST::IsPlayList(*this) || IsSmartPlayList()) + else if (PLAYLIST::IsPlayList(*this) || PLAYLIST::IsSmartPlayList(*this)) { SetArt("icon", "DefaultPlaylist.png"); } @@ -1300,7 +1292,7 @@ void CFileItem::FillInDefaultIcon() } else { - if (PLAYLIST::IsPlayList(*this) || IsSmartPlayList()) + if (PLAYLIST::IsPlayList(*this) || PLAYLIST::IsSmartPlayList(*this)) { SetArt("icon", "DefaultPlaylist.png"); } diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index 9f7de3d6e3b98..2023c2e43ef07 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -172,7 +172,6 @@ class CFileItem : bool IsDeleted() const; bool IsGame() const; - bool IsSmartPlayList() const; bool IsLibraryFolder() const; bool IsPythonScript() const; bool IsPlugin() const; diff --git a/xbmc/FileItemList.cpp b/xbmc/FileItemList.cpp index d3b05e837a418..95b2dc58ae067 100644 --- a/xbmc/FileItemList.cpp +++ b/xbmc/FileItemList.cpp @@ -1089,7 +1089,7 @@ std::string CFileItemList::GetDiscFileCache(int windowID) const if (VIDEO::IsVideoDb(*this)) return StringUtils::Format("special://temp/archive_cache/vdb-{:08x}.fi", crc); - if (IsSmartPlayList()) + if (PLAYLIST::IsSmartPlayList(*this)) return StringUtils::Format("special://temp/archive_cache/sp-{:08x}.fi", crc); if (windowID) diff --git a/xbmc/application/Application.cpp b/xbmc/application/Application.cpp index 336e6ed5c0231..fe3967edcfbbb 100644 --- a/xbmc/application/Application.cpp +++ b/xbmc/application/Application.cpp @@ -2173,7 +2173,7 @@ bool CApplication::PlayMedia(CFileItem& item, const std::string& player, PLAYLIS if (URIUtils::HasPluginPath(item) && !XFILE::CPluginDirectory::GetResolvedPluginResult(item)) return false; - if (item.IsSmartPlayList()) + if (PLAYLIST::IsSmartPlayList(item)) { CFileItemList items; CUtil::GetRecursiveListing(item.GetPath(), items, "", DIR_FLAG_NO_FILE_DIRS); diff --git a/xbmc/filesystem/Directory.cpp b/xbmc/filesystem/Directory.cpp index a9cedab793b4f..13759a1f9858e 100644 --- a/xbmc/filesystem/Directory.cpp +++ b/xbmc/filesystem/Directory.cpp @@ -21,6 +21,7 @@ #include "guilib/GUIWindowManager.h" #include "messaging/ApplicationMessenger.h" #include "music/MusicFileItemClassify.h" +#include "playlists/PlayListFileItemClassify.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/Job.h" @@ -287,7 +288,7 @@ bool CDirectory::GetDirectory(const CURL& url, // Should any of the files we read be treated as a directory? // Disable for database folders, as they already contain the extracted items if (!(hints.flags & DIR_FLAG_NO_FILE_DIRS) && !MUSIC::IsMusicDb(items) && - !VIDEO::IsVideoDb(items) && !items.IsSmartPlayList()) + !VIDEO::IsVideoDb(items) && !PLAYLIST::IsSmartPlayList(items)) FilterFileDirectories(items, hints.mask); // Correct items for path substitution diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.cpp b/xbmc/interfaces/builtins/PlayerBuiltins.cpp index 9ee63f3281d4c..28b54ecd78d8c 100644 --- a/xbmc/interfaces/builtins/PlayerBuiltins.cpp +++ b/xbmc/interfaces/builtins/PlayerBuiltins.cpp @@ -626,7 +626,8 @@ int PlayOrQueueMedia(const std::vector& params, bool forcePlay) if (forcePlay) { - if ((MUSIC::IsAudio(item) || VIDEO::IsVideo(item)) && !item.IsSmartPlayList() && !item.IsPVR()) + if ((MUSIC::IsAudio(item) || VIDEO::IsVideo(item)) && !PLAYLIST::IsSmartPlayList(item) && + !item.IsPVR()) { if (!item.HasProperty("playlist_type_hint")) item.SetProperty("playlist_type_hint", static_cast(GetPlayListId(item))); diff --git a/xbmc/interfaces/json-rpc/FileOperations.cpp b/xbmc/interfaces/json-rpc/FileOperations.cpp index 91222f280b278..c5eb4e5399adb 100644 --- a/xbmc/interfaces/json-rpc/FileOperations.cpp +++ b/xbmc/interfaces/json-rpc/FileOperations.cpp @@ -380,7 +380,7 @@ bool CFileOperations::FillFileItemList(const CVariant ¶meterObject, CFileIte { // Sort folders and files by filename to avoid reverse item order bug on some platforms, // but leave items from a playlist, smartplaylist or upnp container in order supplied - if (!PLAYLIST::IsPlayList(items) && !items.IsSmartPlayList() && + if (!PLAYLIST::IsPlayList(items) && !PLAYLIST::IsSmartPlayList(items) && !URIUtils::IsUPnP(items.GetPath())) items.Sort(SortByFile, SortOrderAscending); diff --git a/xbmc/music/GUIViewStateMusic.cpp b/xbmc/music/GUIViewStateMusic.cpp index 5d43691b4df4c..89a20a8c164ae 100644 --- a/xbmc/music/GUIViewStateMusic.cpp +++ b/xbmc/music/GUIViewStateMusic.cpp @@ -16,6 +16,7 @@ #include "filesystem/VideoDatabaseDirectory.h" #include "guilib/LocalizeStrings.h" #include "guilib/WindowIDs.h" +#include "playlists/PlayListFileItemClassify.h" #include "playlists/PlayListTypes.h" #include "settings/AdvancedSettings.h" #include "settings/MediaSourceSettings.h" @@ -380,7 +381,7 @@ CGUIViewStateMusicSmartPlaylist::CGUIViewStateMusicSmartPlaylist(const CFileItem AddSortMethod(SortByBPM, 38080, LABEL_MASKS("%T - %A", "%f")); // Title - Artist, bpm, empty, empty - if (items.IsSmartPlayList() || items.IsLibraryFolder()) + if (PLAYLIST::IsSmartPlayList(items) || items.IsLibraryFolder()) AddPlaylistOrder(items, LABEL_MASKS(strTrack, "%D")); else { @@ -424,7 +425,7 @@ CGUIViewStateMusicSmartPlaylist::CGUIViewStateMusicSmartPlaylist(const CFileItem // userrating AddSortMethod(SortByUserRating, 38018, LABEL_MASKS("%F", "", strAlbum, "%r")); // Filename, empty | Userdefined, UserRating - if (items.IsSmartPlayList() || items.IsLibraryFolder()) + if (PLAYLIST::IsSmartPlayList(items) || items.IsLibraryFolder()) AddPlaylistOrder(items, LABEL_MASKS("%F", "", strAlbum, "%D")); else { diff --git a/xbmc/music/MusicInfoLoader.cpp b/xbmc/music/MusicInfoLoader.cpp index 1a034e2a3fcd4..6a5aecd55334d 100644 --- a/xbmc/music/MusicInfoLoader.cpp +++ b/xbmc/music/MusicInfoLoader.cpp @@ -151,7 +151,7 @@ bool CMusicInfoLoader::LoadItem(CFileItem* pItem) bool CMusicInfoLoader::LoadItemCached(CFileItem* pItem) { if ((pItem->m_bIsFolder && !MUSIC::IsAudio(*pItem)) || PLAYLIST::IsPlayList(*pItem) || - pItem->IsSmartPlayList() || + PLAYLIST::IsSmartPlayList(*pItem) || StringUtils::StartsWithNoCase(pItem->GetPath(), "newplaylist://") || StringUtils::StartsWithNoCase(pItem->GetPath(), "newsmartplaylist://") || pItem->IsNFO() || (NETWORK::IsInternetStream(*pItem) && !MUSIC::IsMusicDb(*pItem))) @@ -169,7 +169,7 @@ bool CMusicInfoLoader::LoadItemLookup(CFileItem* pItem) m_pProgressCallback->SetProgressAdvance(); if ((pItem->m_bIsFolder && !MUSIC::IsAudio(*pItem)) || // - PLAYLIST::IsPlayList(*pItem) || pItem->IsSmartPlayList() || // + PLAYLIST::IsPlayList(*pItem) || PLAYLIST::IsSmartPlayList(*pItem) || // StringUtils::StartsWithNoCase(pItem->GetPath(), "newplaylist://") || // StringUtils::StartsWithNoCase(pItem->GetPath(), "newsmartplaylist://") || // pItem->IsNFO() || (NETWORK::IsInternetStream(*pItem) && !MUSIC::IsMusicDb(*pItem))) diff --git a/xbmc/music/MusicUtils.cpp b/xbmc/music/MusicUtils.cpp index 54b45d24cf531..215dbee5f568a 100644 --- a/xbmc/music/MusicUtils.cpp +++ b/xbmc/music/MusicUtils.cpp @@ -796,7 +796,7 @@ void QueueItem(const std::shared_ptr& itemIn, QueuePosition pos) playlistId = PLAYLIST::Id::TYPE_MUSIC; // Check for the partymode playlist item, do nothing when "PartyMode.xsp" not exists - if (item->IsSmartPlayList() && !CFileUtils::Exists(item->GetPath())) + if (PLAYLIST::IsSmartPlayList(*item) && !CFileUtils::Exists(item->GetPath())) { const auto profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); if (item->GetPath() == profileManager->GetUserDataItem("PartyMode.xsp")) @@ -864,7 +864,7 @@ namespace { bool IsNonExistingUserPartyModePlaylist(const CFileItem& item) { - if (!item.IsSmartPlayList()) + if (!PLAYLIST::IsSmartPlayList(item)) return false; const std::string& path{item.GetPath()}; diff --git a/xbmc/music/windows/GUIWindowMusicBase.cpp b/xbmc/music/windows/GUIWindowMusicBase.cpp index 404b15ad7a2e6..6086c8bb9324c 100644 --- a/xbmc/music/windows/GUIWindowMusicBase.cpp +++ b/xbmc/music/windows/GUIWindowMusicBase.cpp @@ -435,7 +435,7 @@ void CGUIWindowMusicBase::GetContextButtons(int itemNumber, CContextButtons &but // Check for the partymode playlist item. // When "PartyMode.xsp" not exist, only context menu button is edit - if (item->IsSmartPlayList() && + if (PLAYLIST::IsSmartPlayList(*item) && (item->GetPath() == profileManager->GetUserDataItem("PartyMode.xsp")) && !CFileUtils::Exists(item->GetPath())) { @@ -448,10 +448,10 @@ void CGUIWindowMusicBase::GetContextButtons(int itemNumber, CContextButtons &but //! @todo get rid of IsAddonsPath and IsScript check. CanQueue should be enough! if (item->CanQueue() && !item->IsAddonsPath() && !item->IsScript()) { - if (item->IsSmartPlayList()) + if (PLAYLIST::IsSmartPlayList(*item)) buttons.Add(CONTEXT_BUTTON_PLAY_PARTYMODE, 15216); // Play in Partymode - if (item->IsSmartPlayList() || m_vecItems->IsSmartPlayList()) + if (PLAYLIST::IsSmartPlayList(*item) || PLAYLIST::IsSmartPlayList(*m_vecItems)) buttons.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, 586); else if (PLAYLIST::IsPlayList(*item) || PLAYLIST::IsPlayList(*m_vecItems)) buttons.Add(CONTEXT_BUTTON_EDIT, 586); @@ -517,7 +517,10 @@ bool CGUIWindowMusicBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) case CONTEXT_BUTTON_EDIT_SMART_PLAYLIST: { - std::string playlist = item->IsSmartPlayList() ? item->GetPath() : m_vecItems->GetPath(); // save path as activatewindow will destroy our items + const std::string playlist = + PLAYLIST::IsSmartPlayList(*item) + ? item->GetPath() + : m_vecItems->GetPath(); // save path as activatewindow will destroy our items if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist, "music")) Refresh(true); // need to update return true; @@ -613,7 +616,7 @@ void CGUIWindowMusicBase::PlayItem(int iItem) #endif // Check for the partymode playlist item, do nothing when "PartyMode.xsp" not exist - if (pItem->IsSmartPlayList()) + if (PLAYLIST::IsSmartPlayList(*pItem)) { const std::shared_ptr profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); @@ -1049,7 +1052,7 @@ void CGUIWindowMusicBase::OnPrepareFileItems(CFileItemList &items) { CGUIMediaWindow::OnPrepareFileItems(items); - if (!MUSIC::IsMusicDb(items) && !items.IsSmartPlayList()) + if (!MUSIC::IsMusicDb(items) && !PLAYLIST::IsSmartPlayList(items)) RetrieveMusicInfo(); } diff --git a/xbmc/music/windows/GUIWindowMusicNav.cpp b/xbmc/music/windows/GUIWindowMusicNav.cpp index 2d65816d33843..840881515a357 100644 --- a/xbmc/music/windows/GUIWindowMusicNav.cpp +++ b/xbmc/music/windows/GUIWindowMusicNav.cpp @@ -58,7 +58,6 @@ using namespace XFILE; using namespace MUSICDATABASEDIRECTORY; using namespace KODI; using namespace KODI::MESSAGING; -using namespace KODI::VIDEO; #define CONTROL_BTNVIEWASICONS 2 #define CONTROL_BTNSORTBY 3 @@ -392,7 +391,7 @@ bool CGUIWindowMusicNav::GetDirectory(const std::string &strDirectory, CFileItem } // update our content in the info manager - if (StringUtils::StartsWithNoCase(strDirectory, "videodb://") || IsVideoDb(items)) + if (StringUtils::StartsWithNoCase(strDirectory, "videodb://") || VIDEO::IsVideoDb(items)) { CVideoDatabaseDirectory dir; VIDEODATABASEDIRECTORY::NODE_TYPE node = dir.GetDirectoryChildType(items.GetPath()); @@ -481,8 +480,8 @@ bool CGUIWindowMusicNav::GetDirectory(const std::string &strDirectory, CFileItem items.SetContent("plugins"); else if (items.IsAddonsPath()) items.SetContent("addons"); - else if (!items.IsSourcesPath() && !items.IsVirtualDirectoryRoot() && - !items.IsLibraryFolder() && !items.IsPlugin() && !items.IsSmartPlayList()) + else if (!items.IsSourcesPath() && !items.IsVirtualDirectoryRoot() && !items.IsLibraryFolder() && + !items.IsPlugin() && !PLAYLIST::IsSmartPlayList(items)) items.SetContent("files"); return bResult; @@ -632,7 +631,7 @@ void CGUIWindowMusicNav::GetContextButtons(int itemNumber, CContextButtons &butt if (!item->IsParentFolder() && !dir.IsAllItem(item->GetPath())) { - if (item->m_bIsFolder && !IsVideoDb(*item) && !item->IsPlugin() && + if (item->m_bIsFolder && !VIDEO::IsVideoDb(*item) && !item->IsPlugin() && !StringUtils::StartsWithNoCase(item->GetPath(), "musicsearch://")) { if (item->IsAlbum()) @@ -689,7 +688,7 @@ void CGUIWindowMusicNav::GetContextButtons(int itemNumber, CContextButtons &butt } } if (inPlaylists && URIUtils::GetFileName(item->GetPath()) != "PartyMode.xsp" && - (PLAYLIST::IsPlayList(*item) || item->IsSmartPlayList())) + (PLAYLIST::IsPlayList(*item) || PLAYLIST::IsSmartPlayList(*item))) buttons.Add(CONTEXT_BUTTON_DELETE, 117); if (!item->IsReadOnly() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool("filelists.allowfiledeletion")) @@ -726,7 +725,7 @@ bool CGUIWindowMusicNav::OnContextButton(int itemNumber, CONTEXT_BUTTON button) { case CONTEXT_BUTTON_INFO: { - if (!IsVideoDb(*item)) + if (!VIDEO::IsVideoDb(*item)) return CGUIWindowMusicBase::OnContextButton(itemNumber,button); // music videos - artists @@ -815,7 +814,7 @@ bool CGUIWindowMusicNav::OnContextButton(int itemNumber, CONTEXT_BUTTON button) } case CONTEXT_BUTTON_RENAME: - if (!IsVideoDb(*item) && !item->IsReadOnly()) + if (!VIDEO::IsVideoDb(*item) && !item->IsReadOnly()) OnRenameItem(itemNumber); CGUIDialogVideoInfo::UpdateVideoItemTitle(item); @@ -824,14 +823,14 @@ bool CGUIWindowMusicNav::OnContextButton(int itemNumber, CONTEXT_BUTTON button) return true; case CONTEXT_BUTTON_DELETE: - if (PLAYLIST::IsPlayList(*item) || item->IsSmartPlayList()) + if (PLAYLIST::IsPlayList(*item) || PLAYLIST::IsSmartPlayList(*item)) { item->m_bIsFolder = false; CGUIComponent *gui = CServiceBroker::GetGUI(); if (gui && gui->ConfirmDelete(item->GetPath())) CFileUtils::DeleteItem(item); } - else if (!IsVideoDb(*item)) + else if (!VIDEO::IsVideoDb(*item)) OnDeleteItem(itemNumber); else { diff --git a/xbmc/network/upnp/UPnPInternal.cpp b/xbmc/network/upnp/UPnPInternal.cpp index f70c0ae285a2d..f7709e0a5411e 100644 --- a/xbmc/network/upnp/UPnPInternal.cpp +++ b/xbmc/network/upnp/UPnPInternal.cpp @@ -716,7 +716,7 @@ PLT_MediaObject* BuildObject(CFileItem& item, break; } } - else if (PLAYLIST::IsPlayList(item) || item.IsSmartPlayList()) + else if (PLAYLIST::IsPlayList(item) || PLAYLIST::IsSmartPlayList(item)) { container->m_ObjectClass.type += ".playlistContainer"; } diff --git a/xbmc/network/upnp/UPnPServer.cpp b/xbmc/network/upnp/UPnPServer.cpp index 275354dd8eec0..340e308847a16 100644 --- a/xbmc/network/upnp/UPnPServer.cpp +++ b/xbmc/network/upnp/UPnPServer.cpp @@ -450,7 +450,7 @@ PLT_MediaObject* CUPnPServer::Build(const std::shared_ptr& item, } } // all playlist types are folders - else if (PLAYLIST::IsPlayList(*item) || item->IsSmartPlayList()) + else if (PLAYLIST::IsPlayList(*item) || PLAYLIST::IsSmartPlayList(*item)) { item->m_bIsFolder = true; } diff --git a/xbmc/playlists/PlayListFileItemClassify.cpp b/xbmc/playlists/PlayListFileItemClassify.cpp index 3ca3ec8a25393..32ae1f827be0d 100644 --- a/xbmc/playlists/PlayListFileItemClassify.cpp +++ b/xbmc/playlists/PlayListFileItemClassify.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2020 Team Kodi + * Copyright (C) 2005-2024 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later @@ -10,6 +10,8 @@ #include "FileItem.h" #include "playlists/PlayListFactory.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" namespace KODI::PLAYLIST { @@ -19,4 +21,12 @@ bool IsPlayList(const CFileItem& item) return CPlayListFactory::IsPlaylist(item); } +bool IsSmartPlayList(const CFileItem& item) +{ + if (item.GetProperty("library.smartplaylist").asBoolean(false)) + return true; + + return URIUtils::HasExtension(item.GetPath(), ".xsp"); +} + } // namespace KODI::PLAYLIST diff --git a/xbmc/playlists/PlayListFileItemClassify.h b/xbmc/playlists/PlayListFileItemClassify.h index 18a25d3781a62..ff3a6203be7bf 100644 --- a/xbmc/playlists/PlayListFileItemClassify.h +++ b/xbmc/playlists/PlayListFileItemClassify.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2018 Team Kodi + * Copyright (C) 2005-2024 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later @@ -16,4 +16,7 @@ namespace KODI::PLAYLIST //! \brief Check whether an item is a playlist. bool IsPlayList(const CFileItem& item); +//! \brief Check whether an item is a smart playlist. +bool IsSmartPlayList(const CFileItem& item); + } // namespace KODI::PLAYLIST diff --git a/xbmc/playlists/test/TestPlayListFileItemClassify.cpp b/xbmc/playlists/test/TestPlayListFileItemClassify.cpp index 95912d7fb2070..03ba20a1a46ef 100644 --- a/xbmc/playlists/test/TestPlayListFileItemClassify.cpp +++ b/xbmc/playlists/test/TestPlayListFileItemClassify.cpp @@ -61,3 +61,16 @@ const auto playlist_tests = std::array{ INSTANTIATE_TEST_SUITE_P(TestPlayListFileItemClassify, PlayListTest, testing::ValuesIn(playlist_tests)); + +TEST(TestPlayListFileItemClassify, IsSmartPlayList) +{ + CFileItem item("/some/where.avi", false); + EXPECT_FALSE(PLAYLIST::IsSmartPlayList(item)); + item.SetProperty("library.smartplaylist", true); + EXPECT_TRUE(PLAYLIST::IsSmartPlayList(item)); + + CFileItem item2("/some/where.xsp", false); + EXPECT_TRUE(PLAYLIST::IsSmartPlayList(item2)); + CFileItem item3("/some/where.xsp", true); + EXPECT_TRUE(PLAYLIST::IsSmartPlayList(item3)); +} diff --git a/xbmc/utils/ExecString.cpp b/xbmc/utils/ExecString.cpp index 5c55840c40305..10f1dca0eb490 100644 --- a/xbmc/utils/ExecString.cpp +++ b/xbmc/utils/ExecString.cpp @@ -107,7 +107,7 @@ bool CExecString::Parse(const CFileItem& item, const std::string& contextWindow) } else if (item.m_bIsFolder && (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders || - !(item.IsSmartPlayList() || PLAYLIST::IsPlayList(item)))) + !(PLAYLIST::IsSmartPlayList(item) || PLAYLIST::IsPlayList(item)))) { if (!contextWindow.empty()) Build("ActivateWindow", {contextWindow, StringUtils::Paramify(item.GetPath()), "return"}); diff --git a/xbmc/video/GUIViewStateVideo.cpp b/xbmc/video/GUIViewStateVideo.cpp index f9c4ca06e0107..52cf65d719884 100644 --- a/xbmc/video/GUIViewStateVideo.cpp +++ b/xbmc/video/GUIViewStateVideo.cpp @@ -15,6 +15,7 @@ #include "filesystem/Directory.h" #include "filesystem/VideoDatabaseDirectory.h" #include "guilib/WindowIDs.h" +#include "playlists/PlayListFileItemClassify.h" #include "playlists/PlayListTypes.h" #include "settings/MediaSettings.h" #include "settings/MediaSourceSettings.h" @@ -475,7 +476,7 @@ CGUIViewStateVideoMovies::CGUIViewStateVideoMovies(const CFileItemList& items) : LABEL_MASKS("%T", "%V", "%T", "%V")); // Title, Playcount | Title, Playcount const CViewState *viewState = CViewStateSettings::GetInstance().Get("videonavtitles"); - if (items.IsSmartPlayList() || items.IsLibraryFolder()) + if (PLAYLIST::IsSmartPlayList(items) || items.IsLibraryFolder()) AddPlaylistOrder(items, LABEL_MASKS("%T", "%R", "%T", "%R")); // Title, Rating | Title, Rating else { @@ -515,7 +516,7 @@ CGUIViewStateVideoMusicVideos::CGUIViewStateVideoMusicVideos(const CFileItemList AddSortMethod(SortByTrackNumber, 554, LABEL_MASKS(strTrack, "%N")); // Userdefined, Track Number | empty, empty const CViewState *viewState = CViewStateSettings::GetInstance().Get("videonavmusicvideos"); - if (items.IsSmartPlayList() || items.IsLibraryFolder()) + if (PLAYLIST::IsSmartPlayList(items) || items.IsLibraryFolder()) AddPlaylistOrder(items, LABEL_MASKS("%A - %T", "%Y")); // Artist - Title, Year | empty, empty else { @@ -554,7 +555,7 @@ CGUIViewStateVideoTVShows::CGUIViewStateVideoTVShows(const CFileItemList& items) LABEL_MASKS("%T", "%r", "%T", "%r")); // Title, Userrating | Title, Userrating const CViewState *viewState = CViewStateSettings::GetInstance().Get("videonavtvshows"); - if (items.IsSmartPlayList() || items.IsLibraryFolder()) + if (PLAYLIST::IsSmartPlayList(items) || items.IsLibraryFolder()) AddPlaylistOrder(items, LABEL_MASKS("%T", "%M", "%T", "%M")); // Title, #Episodes | Title, #Episodes else { @@ -593,7 +594,7 @@ CGUIViewStateVideoEpisodes::CGUIViewStateVideoEpisodes(const CFileItemList& item CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); const CViewState *viewState = CViewStateSettings::GetInstance().Get("videonavepisodes"); - if (items.IsSmartPlayList() || items.IsLibraryFolder()) + if (PLAYLIST::IsSmartPlayList(items) || items.IsLibraryFolder()) AddPlaylistOrder(items, LABEL_MASKS("%Z - %H. %T", "%R")); // TvShow - Order. Title, Rating | empty, empty else { diff --git a/xbmc/video/guilib/VideoGUIUtils.cpp b/xbmc/video/guilib/VideoGUIUtils.cpp index 8197097611e9c..70c60037510ab 100644 --- a/xbmc/video/guilib/VideoGUIUtils.cpp +++ b/xbmc/video/guilib/VideoGUIUtils.cpp @@ -493,7 +493,7 @@ namespace { bool IsNonExistingUserPartyModePlaylist(const CFileItem& item) { - if (!item.IsSmartPlayList()) + if (!PLAYLIST::IsSmartPlayList(item)) return false; const std::string& path{item.GetPath()}; diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index 5fe114cc765fa..95cb103f10b54 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -818,7 +818,7 @@ void CGUIWindowVideoBase::GetContextButtons(int itemNumber, CContextButtons &but } } - if (item->IsSmartPlayList()) + if (PLAYLIST::IsSmartPlayList(*item)) { buttons.Add(CONTEXT_BUTTON_PLAY_PARTYMODE, 15216); // Play in Partymode } @@ -835,7 +835,7 @@ void CGUIWindowVideoBase::GetContextButtons(int itemNumber, CContextButtons &but else buttons.Add(CONTEXT_BUTTON_PLAY_AND_QUEUE, 13412); } - if (item->IsSmartPlayList() || m_vecItems->IsSmartPlayList()) + if (PLAYLIST::IsSmartPlayList(*item) || PLAYLIST::IsSmartPlayList(*m_vecItems)) buttons.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, 586); if (VIDEO::IsBlurayPlaylist(*item)) buttons.Add(CONTEXT_BUTTON_CHOOSE_PLAYLIST, 13424); @@ -938,7 +938,10 @@ bool CGUIWindowVideoBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) return true; case CONTEXT_BUTTON_EDIT_SMART_PLAYLIST: { - std::string playlist = m_vecItems->Get(itemNumber)->IsSmartPlayList() ? m_vecItems->Get(itemNumber)->GetPath() : m_vecItems->GetPath(); // save path as activatewindow will destroy our items + const std::string playlist = + PLAYLIST::IsSmartPlayList(*m_vecItems->Get(itemNumber)) + ? m_vecItems->Get(itemNumber)->GetPath() + : m_vecItems->GetPath(); // save path as activatewindow will destroy our items if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist, "video")) Refresh(true); // need to update return true; diff --git a/xbmc/video/windows/GUIWindowVideoNav.cpp b/xbmc/video/windows/GUIWindowVideoNav.cpp index aaba0fe5c2ae5..17c51cd46f845 100644 --- a/xbmc/video/windows/GUIWindowVideoNav.cpp +++ b/xbmc/video/windows/GUIWindowVideoNav.cpp @@ -834,14 +834,14 @@ void CGUIWindowVideoNav::GetContextButtons(int itemNumber, CContextButtons &butt CSettings::SETTING_FILELISTS_ALLOWFILEDELETION) && CUtil::SupportsWriteFileOperations(item->GetPath())) || (inPlaylists && URIUtils::GetFileName(item->GetPath()) != "PartyMode-Video.xsp" && - (PLAYLIST::IsPlayList(*item) || item->IsSmartPlayList()))) + (PLAYLIST::IsPlayList(*item) || PLAYLIST::IsSmartPlayList(*item)))) { buttons.Add(CONTEXT_BUTTON_DELETE, 117); buttons.Add(CONTEXT_BUTTON_RENAME, 118); } // add "Set/Change content" to folders if (item->m_bIsFolder && !VIDEO::IsVideoDb(*item) && !PLAYLIST::IsPlayList(*item) && - !item->IsSmartPlayList() && !item->IsLibraryFolder() && !item->IsLiveTV() && + !PLAYLIST::IsSmartPlayList(*item) && !item->IsLibraryFolder() && !item->IsLiveTV() && !item->IsPlugin() && !item->IsAddonsPath() && !URIUtils::IsUPnP(item->GetPath())) { if (info && info->Content() != CONTENT_NONE) @@ -1089,7 +1089,7 @@ bool CGUIWindowVideoNav::ApplyWatchedFilter(CFileItemList &items) if (!VIDEO::IsVideoDb(items)) filterWatched = true; if (items.GetContent() == "tvshows" && - (items.IsSmartPlayList() || items.IsLibraryFolder())) + (PLAYLIST::IsSmartPlayList(items) || items.IsLibraryFolder())) node = NODE_TYPE_TITLE_TVSHOWS; // so that the check below works int watchMode = CMediaSettings::GetInstance().GetWatchedMode(m_vecItems->GetContent()); diff --git a/xbmc/view/GUIViewState.cpp b/xbmc/view/GUIViewState.cpp index 22d56735c2bc0..3bd48bd64b3a9 100644 --- a/xbmc/view/GUIViewState.cpp +++ b/xbmc/view/GUIViewState.cpp @@ -76,8 +76,7 @@ CGUIViewState* CGUIViewState::GetViewState(int windowId, const CFileItemList& it if (url.IsProtocol("musicsearch")) return new CGUIViewStateMusicSearch(items); - if (items.IsSmartPlayList() || url.IsProtocol("upnp") || - items.IsLibraryFolder()) + if (PLAYLIST::IsSmartPlayList(items) || url.IsProtocol("upnp") || items.IsLibraryFolder()) { if (items.GetContent() == "songs" || items.GetContent() == "albums" || From 1764127db008a9414a39a65dd70f289d701ed9ad Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Mon, 24 Jun 2024 10:45:09 +0100 Subject: [PATCH 187/651] [EDL] Use std::optional and std::unique_ptr for InEdit --- xbmc/cores/VideoPlayer/Edl.cpp | 32 +++--- xbmc/cores/VideoPlayer/Edl.h | 10 +- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 102 +++++++++++--------- xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp | 15 +-- 4 files changed, 83 insertions(+), 76 deletions(-) diff --git a/xbmc/cores/VideoPlayer/Edl.cpp b/xbmc/cores/VideoPlayer/Edl.cpp index 664358a4f5b4b..025261144259a 100644 --- a/xbmc/cores/VideoPlayer/Edl.cpp +++ b/xbmc/cores/VideoPlayer/Edl.cpp @@ -711,9 +711,8 @@ bool CEdl::AddEdit(const Edit& newEdit) bool CEdl::AddSceneMarker(int iSceneMarker) { - Edit edit; - - if (InEdit(iSceneMarker, &edit) && edit.action == Action::CUT) // Only works for current cuts. + const auto edit = InEdit(iSceneMarker); + if (edit && edit.value()->action == Action::CUT) // Only works for current cuts. return false; CLog::Log(LOGDEBUG, "{} - Inserting new scene marker: {}", __FUNCTION__, @@ -841,22 +840,18 @@ bool CEdl::HasSceneMarker() const return !m_vecSceneMarkers.empty(); } -bool CEdl::InEdit(const int iSeek, Edit* pEdit) +std::optional> CEdl::InEdit(const int seekTime) { for (size_t i = 0; i < m_vecEdits.size(); ++i) { - if (iSeek < m_vecEdits[i].start) // Early exit if not even up to the edit start time. - return false; + if (seekTime < m_vecEdits[i].start) // Early exit if not even up to the edit start time. + return std::nullopt; - if (iSeek >= m_vecEdits[i].start && iSeek <= m_vecEdits[i].end) // Inside edit. - { - if (pEdit) - *pEdit = m_vecEdits[i]; - return true; - } + if (seekTime >= m_vecEdits[i].start && seekTime <= m_vecEdits[i].end) // Inside edit. + return std::make_unique(m_vecEdits[i]); } - return false; + return std::nullopt; } int CEdl::GetLastEditTime() const @@ -921,9 +916,14 @@ std::optional CEdl::GetNextSceneMarker(Direction direction, int clock) * If the scene marker is in a cut then return the end of the cut. Can't guarantee that this is * picked up when scene markers are added. */ - Edit edit; - if (sceneMarker && InEdit(sceneMarker.value(), &edit) && edit.action == Action::CUT) - sceneMarker = edit.end; + if (sceneMarker) + { + auto edit = InEdit(sceneMarker.value()); + if (edit && edit.value()->action == Action::CUT) + { + sceneMarker = edit.value()->end; + } + } return sceneMarker; } diff --git a/xbmc/cores/VideoPlayer/Edl.h b/xbmc/cores/VideoPlayer/Edl.h index 3b0279c34d3c5..01abf3a3db059 100644 --- a/xbmc/cores/VideoPlayer/Edl.h +++ b/xbmc/cores/VideoPlayer/Edl.h @@ -11,6 +11,7 @@ #include "cores/Direction.h" #include "cores/EdlEdit.h" +#include #include #include #include @@ -102,14 +103,13 @@ class CEdl /*! * @brief Check if for the provided seek time is contained within an EDL - * edit and fill pEdit with the respective edit struct. + * edit * @note seek time refers to the time in the original file timeline (i.e. without * considering cut blocks) - * @param iSeek The seek time (on the original timeline) - * @param[in,out] pEdit The edit pointer (or nullptr if iSeek not within an edit) - * @return true if iSeek is within an edit, false otherwise + * @param seekTime The seek time (on the original timeline) + * @return a pointer to the edit struct if seekTime is within an edit, nullopt otherwise */ - bool InEdit(int iSeek, EDL::Edit* pEdit = nullptr); + std::optional> InEdit(int seekTime); /*! * @brief Get the last processed edit time (set during playback when a given diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index be3d3a030b503..52697e21814ca 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -1282,7 +1282,6 @@ void CVideoPlayer::Prepare() * if there was a start time specified as part of the "Start from where last stopped" (aka * auto-resume) feature or if there is an EDL cut or commercial break that starts at time 0. */ - EDL::Edit edit; int starttime = 0; if (m_playerOptions.starttime > 0 || m_playerOptions.startpercent > 0) { @@ -1300,34 +1299,39 @@ void CVideoPlayer::Prepare() CLog::Log(LOGDEBUG, "{} - Start position set to last stopped position: {}", __FUNCTION__, starttime); } - else if (m_Edl.InEdit(starttime, &edit)) + else { - // save last edit times - m_Edl.SetLastEditTime(edit.start); - m_Edl.SetLastEditActionType(edit.action); - - if (edit.action == EDL::Action::CUT) - { - starttime = edit.end; - CLog::Log(LOGDEBUG, "{} - Start position set to end of first cut: {}", __FUNCTION__, - starttime); - } - else if (edit.action == EDL::Action::COMM_BREAK) + const auto hasEdit = m_Edl.InEdit(starttime); + if (hasEdit) { - if (m_SkipCommercials) + const auto& edit = hasEdit.value(); + // save last edit times + m_Edl.SetLastEditTime(edit->start); + m_Edl.SetLastEditActionType(edit->action); + + if (edit->action == EDL::Action::CUT) { - starttime = edit.end; - CLog::Log(LOGDEBUG, "{} - Start position set to end of first commercial break: {}", - __FUNCTION__, starttime); + starttime = edit->end; + CLog::Log(LOGDEBUG, "{} - Start position set to end of first cut: {}", __FUNCTION__, + starttime); } - - const std::shared_ptr advancedSettings = - CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); - if (advancedSettings && advancedSettings->m_EdlDisplayCommbreakNotifications) + else if (edit->action == EDL::Action::COMM_BREAK) { - const std::string timeString = - StringUtils::SecondsToTimeString(edit.end / 1000, TIME_FORMAT_MM_SS); - CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25011), timeString); + if (m_SkipCommercials) + { + starttime = edit->end; + CLog::Log(LOGDEBUG, "{} - Start position set to end of first commercial break: {}", + __FUNCTION__, starttime); + } + + const std::shared_ptr advancedSettings = + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); + if (advancedSettings && advancedSettings->m_EdlDisplayCommbreakNotifications) + { + const std::string timeString = + StringUtils::SecondsToTimeString(edit->end / 1000, TIME_FORMAT_MM_SS); + CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25011), timeString); + } } } } @@ -1703,14 +1707,16 @@ void CVideoPlayer::ProcessAudioData(CDemuxStream* pStream, DemuxPacket* pPacket) /* * If CheckSceneSkip() returns true then demux point is inside an EDL cut and the packets are dropped. */ - EDL::Edit edit; if (CheckSceneSkip(m_CurrentAudio)) - drop = true; - else if (m_Edl.InEdit(DVD_TIME_TO_MSEC(m_CurrentAudio.dts + m_offset_pts), &edit) && - edit.action == EDL::Action::MUTE) { drop = true; } + else + { + const auto hasEdit = m_Edl.InEdit(DVD_TIME_TO_MSEC(m_CurrentAudio.dts + m_offset_pts)); + if (hasEdit && hasEdit.value()->action == EDL::Action::MUTE) + drop = true; + } m_VideoPlayerAudio->SendMessage(std::make_shared(pPacket, drop)); @@ -2386,9 +2392,8 @@ bool CVideoPlayer::CheckSceneSkip(const CCurrentStream& current) if(current.inited == false) return false; - EDL::Edit edit; - return m_Edl.InEdit(DVD_TIME_TO_MSEC(current.dts + m_offset_pts), &edit) && - edit.action == EDL::Action::CUT; + const auto hasEdit = m_Edl.InEdit(DVD_TIME_TO_MSEC(current.dts + m_offset_pts)); + return hasEdit && hasEdit.value()->action == EDL::Action::CUT; } void CVideoPlayer::CheckAutoSceneSkip() @@ -2410,8 +2415,8 @@ void CVideoPlayer::CheckAutoSceneSkip() const int64_t clock = GetTime(); const double correctClock = m_Edl.GetTimeAfterRestoringCuts(clock); - EDL::Edit edit; - if (!m_Edl.InEdit(correctClock, &edit)) + const auto hasEdit = m_Edl.InEdit(correctClock); + if (!hasEdit) { // @note: Users are allowed to jump back into EDL commercial breaks // do not reset the last edit time if the last surpassed edit is a commercial break @@ -2422,17 +2427,18 @@ void CVideoPlayer::CheckAutoSceneSkip() return; } - if (edit.action == EDL::Action::CUT) + const auto& edit = hasEdit.value(); + if (edit->action == EDL::Action::CUT) { - if ((m_playSpeed > 0 && correctClock < (edit.start + 1000)) || - (m_playSpeed < 0 && correctClock < (edit.end - 1000))) + if ((m_playSpeed > 0 && correctClock < (edit->start + 1000)) || + (m_playSpeed < 0 && correctClock < (edit->end - 1000))) { CLog::Log(LOGDEBUG, "{} - Clock in EDL cut [{} - {}]: {}. Automatically skipping over.", - __FUNCTION__, CEdl::MillisecondsToTimeString(edit.start), - CEdl::MillisecondsToTimeString(edit.end), CEdl::MillisecondsToTimeString(clock)); + __FUNCTION__, CEdl::MillisecondsToTimeString(edit->start), + CEdl::MillisecondsToTimeString(edit->end), CEdl::MillisecondsToTimeString(clock)); // Seeking either goes to the start or the end of the cut depending on the play direction. - int seek = m_playSpeed >= 0 ? edit.end : edit.start; + int seek = m_playSpeed >= 0 ? edit->end : edit->start; if (m_Edl.GetLastEditTime() != seek) { CDVDMsgPlayerSeek::CMode mode; @@ -2445,37 +2451,37 @@ void CVideoPlayer::CheckAutoSceneSkip() m_messenger.Put(std::make_shared(mode)); m_Edl.SetLastEditTime(seek); - m_Edl.SetLastEditActionType(edit.action); + m_Edl.SetLastEditActionType(edit->action); } } } - else if (edit.action == EDL::Action::COMM_BREAK) + else if (edit->action == EDL::Action::COMM_BREAK) { // marker for commbreak may be inaccurate. allow user to skip into break from the back - if (m_playSpeed >= 0 && m_Edl.GetLastEditTime() != edit.start && clock < edit.end - 1000) + if (m_playSpeed >= 0 && m_Edl.GetLastEditTime() != edit->start && clock < edit->end - 1000) { const std::shared_ptr advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); if (advancedSettings && advancedSettings->m_EdlDisplayCommbreakNotifications) { const std::string timeString = - StringUtils::SecondsToTimeString((edit.end - edit.start) / 1000, TIME_FORMAT_MM_SS); + StringUtils::SecondsToTimeString((edit->end - edit->start) / 1000, TIME_FORMAT_MM_SS); CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25011), timeString); } - m_Edl.SetLastEditTime(edit.start); - m_Edl.SetLastEditActionType(edit.action); + m_Edl.SetLastEditTime(edit->start); + m_Edl.SetLastEditActionType(edit->action); if (m_SkipCommercials) { CLog::Log(LOGDEBUG, "{} - Clock in commercial break [{} - {}]: {}. Automatically skipping to end of " "commercial break", - __FUNCTION__, CEdl::MillisecondsToTimeString(edit.start), - CEdl::MillisecondsToTimeString(edit.end), CEdl::MillisecondsToTimeString(clock)); + __FUNCTION__, CEdl::MillisecondsToTimeString(edit->start), + CEdl::MillisecondsToTimeString(edit->end), CEdl::MillisecondsToTimeString(clock)); CDVDMsgPlayerSeek::CMode mode; - mode.time = edit.end; + mode.time = edit->end; mode.backward = true; mode.accurate = true; mode.restore = false; diff --git a/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp b/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp index 4516d9666c227..a8072ca868a76 100644 --- a/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp +++ b/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp @@ -70,8 +70,8 @@ TEST_F(TestEdl, TestParsingMplayerTimeBasedEDL) EXPECT_EQ(edl.GetTimeWithoutCuts(mute.start), mute.start - edl.GetTotalCutTime()); EXPECT_EQ(edl.GetTimeAfterRestoringCuts(mute.start - edl.GetTotalCutTime()), mute.start); EXPECT_EQ(muteRaw.start - edl.GetTotalCutTime(), mute.start); - EXPECT_EQ(edl.InEdit(muteRaw.start, nullptr), true); - EXPECT_EQ(edl.InEdit(mute.start, nullptr), false); + EXPECT_NE(edl.InEdit(muteRaw.start), std::nullopt); + EXPECT_EQ(edl.InEdit(mute.start), std::nullopt); // scene markers // one of the scenemarkers (the first) have start and end times defined, kodi should assume the marker at the END position (255.3 secs) @@ -100,12 +100,13 @@ TEST_F(TestEdl, TestParsingMplayerTimeBasedEDL) EXPECT_EQ(edl.GetTimeWithoutCuts(time.value()), commbreak.end); // We should be in an edit if we are in the middle of a commbreak... // lets check and confirm the edits match (after restoring cuts) - Edit thisEdit; const int middleOfCommbreak = commbreak.start + (commbreak.end - commbreak.start) / 2; - EXPECT_EQ(edl.InEdit(edl.GetTimeWithoutCuts(middleOfCommbreak), &thisEdit), true); - EXPECT_EQ(thisEdit.action, Action::COMM_BREAK); - EXPECT_EQ(thisEdit.start, edl.GetTimeAfterRestoringCuts(commbreak.start)); - EXPECT_EQ(thisEdit.end, edl.GetTimeAfterRestoringCuts(commbreak.end)); + const auto hasEdit = edl.InEdit(edl.GetTimeWithoutCuts(middleOfCommbreak)); + EXPECT_NE(hasEdit, std::nullopt); + const auto& edit = hasEdit.value(); + EXPECT_EQ(edit->action, Action::COMM_BREAK); + EXPECT_EQ(edit->start, edl.GetTimeAfterRestoringCuts(commbreak.start)); + EXPECT_EQ(edit->end, edl.GetTimeAfterRestoringCuts(commbreak.end)); } TEST_F(TestEdl, TestParsingMplayerTimeBasedInterleavedCutsEDL) From e91ba3b63696838cb1c59c3828dba498cf141cab Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 25 Jun 2024 01:09:23 +0000 Subject: [PATCH 188/651] Update translation files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Spanish (Spain) (es_es)) Currently translated at 100.0% (169 of 169 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Slovenian (sl_si)) Currently translated at 100.0% (5 of 5 strings) Translated using Weblate (Slovenian (sl_si)) Currently translated at 100.0% (5 of 5 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Spanish (Spain) (es_es)) Currently translated at 100.0% (169 of 169 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Spanish (Spain) (es_es)) Currently translated at 100.0% (169 of 169 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Spanish (Spain) (es_es)) Currently translated at 100.0% (169 of 169 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Spanish (Spain) (es_es)) Currently translated at 100.0% (169 of 169 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Spanish (Spain) (es_es)) Currently translated at 100.0% (169 of 169 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Spanish (Spain) (es_es)) Currently translated at 100.0% (5 of 5 strings) Translated using Weblate (Spanish (Spain) (es_es)) Currently translated at 100.0% (5 of 5 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Dutch (nl_nl)) Currently translated at 100.0% (3 of 3 strings) Translated using Weblate (Dutch (nl_nl)) Currently translated at 100.0% (5 of 5 strings) Translated using Weblate (Dutch (nl_nl)) Currently translated at 100.0% (5 of 5 strings) Translated using Weblate (Dutch (nl_nl)) Currently translated at 100.0% (169 of 169 strings) Translated using Weblate (Dutch (nl_nl)) Currently translated at 100.0% (5 of 5 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Slovenian (sl_si)) Currently translated at 99.4% (168 of 169 strings) Co-authored-by: Hosted Weblate Co-authored-by: José Antonio Alvarado Co-authored-by: Mark Peters Co-authored-by: Simon Co-authored-by: roliverosc Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/es_es/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/nl_nl/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/sl_si/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-aac/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-aac/es_es/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-aac/nl_nl/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-aac/sl_si/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-wma/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-wma/nl_nl/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-wma/sl_si/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/repository-xbmc-org/nl_nl/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/screensaver-xbmc-builtin-dim/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/screensaver-xbmc-builtin-dim/es_es/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/screensaver-xbmc-builtin-dim/nl_nl/ Translation: Kodi add-ons: skins/skin.estuary Translation: Kodi core/audioencoder.kodi.builtin.aac Translation: Kodi core/audioencoder.kodi.builtin.wma Translation: Kodi core/repository.xbmc.org Translation: Kodi core/screensaver.xbmc.builtin.dim --- .../resource.language.es_es/strings.po | 10 +- .../resource.language.nl_nl/strings.po | 6 +- .../resource.language.sl_si/strings.po | 12 +- .../resource.language.nl_nl/strings.po | 6 +- .../resource.language.sl_si/strings.po | 12 +- .../resource.language.nl_nl/strings.po | 8 +- .../resource.language.es_es/strings.po | 17 ++- .../resource.language.nl_nl/strings.po | 6 +- .../resource.language.es_es/strings.po | 88 +++++------ .../resource.language.nl_nl/strings.po | 46 +++--- .../resource.language.sl_si/strings.po | 142 +++++++++--------- 11 files changed, 177 insertions(+), 176 deletions(-) diff --git a/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.es_es/strings.po b/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.es_es/strings.po index 9814919277b5d..c427df087f1a7 100644 --- a/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.es_es/strings.po +++ b/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.es_es/strings.po @@ -7,15 +7,15 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2021-12-01 20:14+0000\n" -"Last-Translator: Alfonso Cachero \n" +"PO-Revision-Date: 2024-05-24 05:07+0000\n" +"Last-Translator: roliverosc \n" "Language-Team: Spanish (Spain) \n" "Language: es_es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.9.1\n" +"X-Generator: Weblate 5.5.4\n" msgctxt "Addon Summary" msgid "AAC Audio Encoder" @@ -23,7 +23,7 @@ msgstr "Codificador de Audio AAC" msgctxt "Addon Description" msgid "AAC is a set of codecs designed to provide better compression than MP3s, and are improved versions of MPEG audio." -msgstr "ACC es un conjunto de codificadores diseñados para comprimir mejor que MP3, y son versiones mejoradas de audio MPEG." +msgstr "AAC es un conjunto de códecs diseñados para proporcionar una mejor compresión que los MP3 y son versiones mejoradas del audio MPEG." #. Bitrate to use on for compression #: resources/settings.xml @@ -35,7 +35,7 @@ msgstr "Tasa de bits" #: resources/settings.xml msgctxt "#30001" msgid "Select which bitrate to use for the AAC audio encoder for audio compression." -msgstr "Elegir qué tasa de bits usar para comprimir audio con el codificador de audio AAC ." +msgstr "Seleccione qué tasa de bits usar para el codificador de audio AAC para la compresión de audio." #. Value format for with bitrate edited field #: resources/settings.xml diff --git a/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.nl_nl/strings.po b/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.nl_nl/strings.po index 53eef529da265..e83cc52b80eaf 100644 --- a/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.nl_nl/strings.po +++ b/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.nl_nl/strings.po @@ -7,15 +7,15 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2022-06-29 16:37+0000\n" -"Last-Translator: SecularSteve \n" +"PO-Revision-Date: 2024-05-20 15:13+0000\n" +"Last-Translator: Mark Peters \n" "Language-Team: Dutch \n" "Language: nl_nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.13\n" +"X-Generator: Weblate 5.5.4\n" msgctxt "Addon Summary" msgid "AAC Audio Encoder" diff --git a/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.sl_si/strings.po b/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.sl_si/strings.po index 9c6fcf91e1e7c..13c5ca673a45e 100644 --- a/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.sl_si/strings.po +++ b/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.sl_si/strings.po @@ -7,23 +7,23 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2021-08-21 08:21+0000\n" -"Last-Translator: Christian Gade \n" +"PO-Revision-Date: 2024-06-11 19:25+0000\n" +"Last-Translator: Simon \n" "Language-Team: Slovenian \n" "Language: sl_si\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n" -"X-Generator: Weblate 4.7.2\n" +"X-Generator: Weblate 5.5.5\n" msgctxt "Addon Summary" msgid "AAC Audio Encoder" -msgstr "" +msgstr "Avdio kodirnik AAC" msgctxt "Addon Description" msgid "AAC is a set of codecs designed to provide better compression than MP3s, and are improved versions of MPEG audio." -msgstr "" +msgstr "AAC je nabor kodekov, zasnovanih za zagotavljanje boljšega stiskanja kot MP3, in so izboljšane različice zvoka MPEG." #. Bitrate to use on for compression #: resources/settings.xml @@ -35,7 +35,7 @@ msgstr "Bitna hitrost" #: resources/settings.xml msgctxt "#30001" msgid "Select which bitrate to use for the AAC audio encoder for audio compression." -msgstr "" +msgstr "Izberite bitno hitrost, ki jo želite uporabiti za zvočni kodirnik AAC za stiskanje zvoka." #. Value format for with bitrate edited field #: resources/settings.xml diff --git a/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.nl_nl/strings.po b/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.nl_nl/strings.po index 84e5eebbce8fe..2212851cf5a2b 100644 --- a/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.nl_nl/strings.po +++ b/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.nl_nl/strings.po @@ -7,15 +7,15 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2022-06-29 16:37+0000\n" -"Last-Translator: SecularSteve \n" +"PO-Revision-Date: 2024-05-20 15:13+0000\n" +"Last-Translator: Mark Peters \n" "Language-Team: Dutch \n" "Language: nl_nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.13\n" +"X-Generator: Weblate 5.5.4\n" msgctxt "Addon Summary" msgid "WMA Audio Encoder" diff --git a/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.sl_si/strings.po b/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.sl_si/strings.po index 4b81a784c9b88..56a96bed94936 100644 --- a/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.sl_si/strings.po +++ b/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.sl_si/strings.po @@ -7,23 +7,23 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2021-08-21 08:21+0000\n" -"Last-Translator: Christian Gade \n" +"PO-Revision-Date: 2024-06-11 19:25+0000\n" +"Last-Translator: Simon \n" "Language-Team: Slovenian \n" "Language: sl_si\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n" -"X-Generator: Weblate 4.7.2\n" +"X-Generator: Weblate 5.5.5\n" msgctxt "Addon Summary" msgid "WMA Audio Encoder" -msgstr "" +msgstr "Kodirnik zvoka WMA" msgctxt "Addon Description" msgid "Windows Media Audio, Microsoft’s lossy audio format." -msgstr "" +msgstr "Windows Media Audio, Microsoftov zvočni format z izgubo." #. Bitrate to use on for compression #: resources/settings.xml @@ -35,7 +35,7 @@ msgstr "Bitna hitrost" #: resources/settings.xml msgctxt "#30001" msgid "Select which bitrate to use for the WMA audio encoder for audio compression." -msgstr "" +msgstr "Izberite bitno hitrost, ki jo želite uporabiti za zvočni kodirnik WMA za stiskanje zvoka." #. Value format for with bitrate edited field #: resources/settings.xml diff --git a/addons/repository.xbmc.org/resources/language/resource.language.nl_nl/strings.po b/addons/repository.xbmc.org/resources/language/resource.language.nl_nl/strings.po index 2632b11e90a98..353843ff6d0a0 100644 --- a/addons/repository.xbmc.org/resources/language/resource.language.nl_nl/strings.po +++ b/addons/repository.xbmc.org/resources/language/resource.language.nl_nl/strings.po @@ -7,15 +7,15 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-03-27 15:08+0000\n" -"Last-Translator: Christian Gade \n" +"PO-Revision-Date: 2024-05-20 15:13+0000\n" +"Last-Translator: Mark Peters \n" "Language-Team: Dutch \n" "Language: nl_nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.15.2\n" +"X-Generator: Weblate 5.5.4\n" msgctxt "Addon Summary" msgid "Install Add-ons from Kodi.tv" @@ -23,7 +23,7 @@ msgstr "Add-ons van Kodi.tv installeren" msgctxt "Addon Description" msgid "Download and install add-ons from the Official Kodi.tv add-on repository.[CR] By using the official Repository you will be able to take advantage of our extensive file mirror service to help get you faster downloads from a region close to you.[CR] All add-ons on this repository have under gone basic testing, if you find a broken or not working add-on please report it to Team Kodi so we can take any action needed." -msgstr "Download en installeer add-ons uit de officiële Kodi.tv add-on depot.[CR]Wanneer u de officiële depot gebruikt beschikt u over onze uitgebreide mirrorservice, waardoor u snel kunt downloaden van locaties in uw buurt.[CR] Alle add-ons in dit depot ondergingen standaardtests. Indien u een defecte of niet-werkende add-on vindt, meld dit dan aan Team Kodi zodat zij actie kunnen ondernemen." +msgstr "Download en installeer add-ons uit de officiële Kodi.tv add-on depot.[CR]Wanneer u de officiële depot gebruikt beschikt u over onze uitgebreide mirror service, waardoor u snel kunt downloaden van locaties in uw buurt.[CR] Alle add-ons in dit depot ondergingen standaardtests. Indien u een defecte of niet-werkende add-on vindt, meld dit dan aan Team Kodi zodat zij actie kunnen ondernemen." msgctxt "Addon Disclaimer" msgid "Team Kodi did not make all the add-ons on this repository and are not responsible for their content" diff --git a/addons/screensaver.xbmc.builtin.dim/resources/language/resource.language.es_es/strings.po b/addons/screensaver.xbmc.builtin.dim/resources/language/resource.language.es_es/strings.po index d701f991bdc84..8030aae01b5c1 100644 --- a/addons/screensaver.xbmc.builtin.dim/resources/language/resource.language.es_es/strings.po +++ b/addons/screensaver.xbmc.builtin.dim/resources/language/resource.language.es_es/strings.po @@ -5,16 +5,17 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Kodi Translation Team\n" -"Language-Team: Spanish (Spain) (http://www.transifex.com/projects/p/kodi-main/language/es_ES/)\n" -"Language: es_ES\n" +"PO-Revision-Date: 2024-05-21 07:19+0000\n" +"Last-Translator: roliverosc \n" +"Language-Team: Spanish (Spain) \n" +"Language: es_es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.5.4\n" msgctxt "Addon Summary" msgid "Screensaver that dims your screen" @@ -22,7 +23,7 @@ msgstr "Un salvapantallas que atenúa tu pantalla" msgctxt "Addon Description" msgid "The Dim screensaver is a simple screensaver that will dim (fade out) your screen to a setable value between 20 and 100% ." -msgstr "Dim es un sencillo salvapantallas que atenúa tu pantalla. El valor es configurable entre el 20% y el 100%." +msgstr "El protector de pantalla Dim es un protector de pantalla simple que atenuará (pasar a negro) su pantalla a un valor configurable entre 20 y 100%." #. Setting name for edit screen dim in percentage (0% = black, 100% = no dim) msgctxt "#30000" @@ -32,7 +33,7 @@ msgstr "Nivel de brillo" #. Setting help text msgctxt "#30001" msgid "Level in percent of how strongly the screen remains illuminated." -msgstr "Nivel en porcentaje de cómo de iluminada se queda la pantalla." +msgstr "Nivel en porcentaje de la intensidad con la que permanece iluminada la pantalla." #. Setting category name msgctxt "#30002" diff --git a/addons/screensaver.xbmc.builtin.dim/resources/language/resource.language.nl_nl/strings.po b/addons/screensaver.xbmc.builtin.dim/resources/language/resource.language.nl_nl/strings.po index ca0eca11f11b0..7fa93a6fbe581 100644 --- a/addons/screensaver.xbmc.builtin.dim/resources/language/resource.language.nl_nl/strings.po +++ b/addons/screensaver.xbmc.builtin.dim/resources/language/resource.language.nl_nl/strings.po @@ -7,15 +7,15 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2022-06-29 16:37+0000\n" -"Last-Translator: SecularSteve \n" +"PO-Revision-Date: 2024-05-20 15:13+0000\n" +"Last-Translator: Mark Peters \n" "Language-Team: Dutch \n" "Language: nl_nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.13\n" +"X-Generator: Weblate 5.5.4\n" msgctxt "Addon Summary" msgid "Screensaver that dims your screen" diff --git a/addons/skin.estuary/language/resource.language.es_es/strings.po b/addons/skin.estuary/language/resource.language.es_es/strings.po index ef737c9f4f11d..c2e4ca90e1e52 100644 --- a/addons/skin.estuary/language/resource.language.es_es/strings.po +++ b/addons/skin.estuary/language/resource.language.es_es/strings.po @@ -5,17 +5,17 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-11-23 17:23+0000\n" -"Last-Translator: José Antonio Alvarado \n" +"PO-Revision-Date: 2024-06-21 17:23+0000\n" +"Last-Translator: roliverosc \n" "Language-Team: Spanish (Spain) \n" "Language: es_es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.1\n" +"X-Generator: Weblate 5.6\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -23,7 +23,7 @@ msgstr "Skin Estuary por phil65. (skin por defecto de Kodi)" msgctxt "Addon Description" msgid "Estuary is the default skin for Kodi 17.0 and above. It attempts to be easy for first time Kodi users to understand and use." -msgstr "Estuary es el skin por defecto para Kodi 17.0 o superior. Su objetivo es la facilidad de comprensión y uso para usuarios primerizos de Kodi." +msgstr "Estuary es el skin por defecto para Kodi 17.0 o superior. Intenta ser fácil de entender y utilizar para los usuarios primerizos de Kodi." msgctxt "Addon Disclaimer" msgid "Estuary is the default skin for Kodi, removing it may cause issues" @@ -83,7 +83,7 @@ msgstr "Álbumes aleatorios" msgctxt "#31013" msgid "Random artists" -msgstr "Intérpretes aleatorios" +msgstr "Artistas aleatorios" msgctxt "#31014" msgid "Unplayed albums" @@ -101,7 +101,7 @@ msgstr "Canales reproducidos recientemente" msgctxt "#31017" msgid "Rated" -msgstr "Puntuados" +msgstr "Valorado" #. home screen channel widget: recently played radio channels. (please note that in some non-english languages #31018 and #31016 might not be equal) msgctxt "#31018" @@ -150,7 +150,7 @@ msgstr "Mostrar fanart" msgctxt "#31029" msgid "Last logged in" -msgstr "Última sesión el" +msgstr "Último inicio de sesión" #. Label to show the system memory usage msgctxt "#31030" @@ -167,7 +167,7 @@ msgstr "Ordenar" msgctxt "#31033" msgid "Your rating" -msgstr "Su puntuación" +msgstr "Tu valoración" msgctxt "#31034" msgid "Extended info" @@ -203,7 +203,7 @@ msgstr "Opciones de lista de reproducción" msgctxt "#31043" msgid "Set the type and add rules to create a smart playlist. These playlists are dynamic and include all media items from your database which apply to your chosen rules." -msgstr "Escoja el tipo y añada reglas para crear listas de reproducción inteligentes. Estas listas son dinámicas e incluyen aquellos elementos de su base de datos en los que aplican las reglas escogidas." +msgstr "Establezca el tipo y agregue reglas para crear una lista de reproducción inteligente. Estas listas de reproducción son dinámicas e incluyen todos los elementos de su base de datos que se ajusten a las reglas escogidas." msgctxt "#31044" msgid "Add group" @@ -215,7 +215,7 @@ msgstr "Renombrar grupo" msgctxt "#31046" msgid "Delete group" -msgstr "Borrar grupo" +msgstr "Eliminar grupo" msgctxt "#31048" msgid "Available" @@ -255,7 +255,7 @@ msgstr "Elementos del menú principal" msgctxt "#31062" msgid "Choose weather fanart pack" -msgstr "Elegir pack de fanart para la meteorología" +msgstr "Elegir paquete de fanarts para meteorología" msgctxt "#31063" msgid "Sections" @@ -299,7 +299,7 @@ msgstr "Duración total" msgctxt "#31075" msgid "Movie sets" -msgstr "Sagas de películas" +msgstr "Colecciones de películas" msgctxt "#31079" msgid "Cast not available" @@ -339,7 +339,7 @@ msgstr "Usar animaciones de desplazamiento" msgctxt "#31096" msgid "Local subtitle available" -msgstr "Subtítulos locales disponibles" +msgstr "Subtítulo local disponible" msgctxt "#31097" msgid "Channel options" @@ -375,11 +375,11 @@ msgstr "Introduzca el texto aquí..." msgctxt "#31104" msgid "Your library is currently empty. In order to populate it with your personal media, enter \"Files\" section, add a media source and configure it. After the source has been added and indexed you will be able to browse your library." -msgstr "Su biblioteca está actualmente vacía. Para llenarlo con sus medios personales, ingrese a la sección \"Archivos\", agregue una fuente de medios y configúrelo. Una vez agregada e indexada la fuente, podrá navegar por su biblioteca." +msgstr "Su biblioteca está actualmente vacía. Para llenarla con su contenido personal, vaya a la sección \"Archivos\", añada una fuente de contenido y configúrelo. Una vez que la fuente haya sido agregada e indexada, podrá navegar por su biblioteca." msgctxt "#31105" msgid "Add video sources and set the appropriate content type in order to populate your video libraries." -msgstr "Agregue fuentes de video y establezca el tipo de contenido apropiado para llenar sus bibliotecas de vídeo." +msgstr "Agregue fuentes de vídeo y establezca el tipo de contenido apropiado para completar sus bibliotecas de vídeo." msgctxt "#31106" msgid "Teletext" @@ -392,11 +392,11 @@ msgstr "Lista amplia" msgctxt "#31110" msgid "Enter files section" -msgstr "Entrar en la sección Archivos" +msgstr "Entrar en la sección archivos" msgctxt "#31111" msgid "View your personal pictures or download one of the many image add-ons from the official repository." -msgstr "Vea sus imágenes personales o descargue uno de los múltiples add-ons de imágenes del repositorio oficial." +msgstr "Vea sus imágenes personales o descargue uno de los muchos complementos de imágenes del repositorio oficial." msgctxt "#31112" msgid "Toggle audio stream" @@ -404,7 +404,7 @@ msgstr "Activar canal de audio" msgctxt "#31113" msgid "Search local library" -msgstr "Buscar en la colección local" +msgstr "Buscar en la biblioteca local" msgctxt "#31114" msgid "Search YouTube" @@ -416,7 +416,7 @@ msgstr "Cambiar subtítulos" msgctxt "#31116" msgid "Remove this main menu item" -msgstr "Eliminar este elemento del menú principal" +msgstr "Quitar este elemento del menú principal" msgctxt "#31117" msgid "Edit nodes" @@ -428,11 +428,11 @@ msgstr "Entrar en el navegador de add-on" msgctxt "#31119" msgid "You do not have any add-ons installed yet. Visit our add-on browser to browse through our collection and improve your Kodi experience." -msgstr "Aún no tienes add-ons instalados. Visite nuestro navegador de add-on para navegar a través de nuestra colección y mejorar la experiencia de Kodi." +msgstr "Aún no tienes add-ons instalados. Visita nuestro explorador de add-on para navegar a través de nuestra colección y mejorar la experiencia Kodi." msgctxt "#31120" msgid "You did not set up a weather provider yet. In order to view weather information, choose a weather provider and set up your location." -msgstr "No ha configurado aún un proveedor de meteorología. Para poder consultar la meteorología, elija un proveedor y establezca su ubicación." +msgstr "Aún no has configurado un proveedor meteorológico. Para ver información meteorológica, elije un proveedor meteorológico y configura tu ubicación." msgctxt "#31121" msgid "Set weather provider" @@ -464,23 +464,23 @@ msgstr "Colaboradores" msgctxt "#31129" msgid "General settings applying to all areas of the skin." -msgstr "Los ajustes generales aplican a todas las áreas del skin." +msgstr "Los ajustes generales se aplican a todas las áreas del skin." msgctxt "#31130" msgid "Main menu-related settings: Configure the home screen to your likings." -msgstr "Ajustes relativos al menú principal: configure la pantalla principal a su gusto." +msgstr "Ajustes relacionados con el menú principal: Configura la pantalla de inicio a tu gusto." msgctxt "#31131" msgid "Choose skin fanart pack" -msgstr "Elegir pack de fanart de skin" +msgstr "Elegir paquete de fanart para el skin" msgctxt "#31132" msgid "Select Program" -msgstr "Elija Programa" +msgstr "Elija programa" msgctxt "#31133" msgid "Select Resolution" -msgstr "Seleccionar Resolución" +msgstr "Seleccionar resolución" msgctxt "#31134" msgid "Remaining" @@ -492,7 +492,7 @@ msgstr "Binario" msgctxt "#31136" msgid "Click here to see latest changes..." -msgstr "Pulse aquí para ver los cambios más recientes..." +msgstr "Haga clic aquí para ver los últimos cambios..." #. Label to show PVR info page msgctxt "#31137" @@ -512,11 +512,11 @@ msgstr "Decodificador de vídeo" #. Label to show the video pixel format msgctxt "#31140" msgid "Pixel format" -msgstr "Formato de Píxel" +msgstr "Formato de píxel" msgctxt "#31141" msgid "Changes for version" -msgstr "Cambios para versión" +msgstr "Cambios para la versión" msgctxt "#31142" msgid "Play speed" @@ -524,11 +524,11 @@ msgstr "Velocidad de reproducción" msgctxt "#31143" msgid "You did not set up PVR yet. In order to use PVR, choose a PVR client addon and configure it. Please visit http://kodi.wiki/view/PVR to learn more." -msgstr "Aún no se ha ajustado el PVR. Para usar PVR, elige un add-on de cliente PVR y configúralo. Por favor, visita http://kodi.wiki/view/PVR para obtener más información." +msgstr "Aún no has configurado PVR. Para utilizar PVR, elije un complemento de cliente PVR y configuralo. Por favor, visita http://kodi.wiki/view/PVR para obtener más información." msgctxt "#31144" msgid "Enter add-on browser" -msgstr "Entrar en el navegador de add-ons" +msgstr "Entrar en el explorador de add-ons" msgctxt "#31145" msgid "Search add-ons" @@ -548,7 +548,7 @@ msgstr "Categorías" msgctxt "#31149" msgid "Select genre fanart pack" -msgstr "Elegir pack de fanart para los géneros" +msgstr "Elegir paquete de fanart para géneros" msgctxt "#31150" msgid "Origin" @@ -564,7 +564,7 @@ msgstr "Vídeos musicales aleatorios" msgctxt "#31153" msgid "You do not have any add-ons of this type installed. Enter the add-on browser to download add-ons created by our community." -msgstr "No tiene ningun Add-on de este tipo instalado. Visite nuestro navegador de Add-ons para descargar alguno creado por nuestra comunidad." +msgstr "No tienes ningun Add-on de este tipo instalado. Visita nuestro explorador de Add-ons para descargar alguno creado por nuestra comunidad." msgctxt "#31154" msgid "Press OK to switch between locations" @@ -576,7 +576,7 @@ msgstr "No se han creado marcadores aún." msgctxt "#31156" msgid "Choose background pattern" -msgstr "Elegir patrón del fondo" +msgstr "Elegir el patrón de fondo" msgctxt "#31157" msgid "Edit categories" @@ -600,11 +600,11 @@ msgstr "Teclado numérico" msgctxt "#31162" msgid "Play your personal games or download one of the many game add-ons from the official repository." -msgstr "Juegue a sus juegos o descargue alguno de los múltiples add-ons del repositorio oficial." +msgstr "Juega a tus juegos o descarga alguno de los múltiples add-ons del repositorio oficial." msgctxt "#31163" msgid "Show Fanart background" -msgstr "Mostrar fondo fanart" +msgstr "Mostrar fanart de fondo" #. Choose profile identifier msgctxt "#31164" @@ -629,12 +629,12 @@ msgstr "Animar fondo" #. Label of a setting msgctxt "#31168" msgid "Show posters instead of thumbs for musicvideos" -msgstr "Mostrar carteles en lugar de miniaturas para vídeos musicales" +msgstr "Mostrar pósters en lugar de miniaturas para vídeos musicales" #. Description label for skin settings area msgctxt "#31169" msgid "Artwork related settings." -msgstr "Ajustes relacionados con el arte." +msgstr "Ajustes relacionados con las ilustraciones." #. Label for OSD settings category msgctxt "#31170" @@ -654,22 +654,22 @@ msgstr "Cerrar automáticamente OSD de vídeo" #. Setting auto close time for video osd msgctxt "#31173" msgid "Video OSD autoclose time (seconds)" -msgstr "Tiempo para cerrar OSD de Vídeo (segundos)" +msgstr "Tiempo para cierre de OSD de vídeo (segundos)" #. Setting to control what happens when clicking a music album on the home screen msgctxt "#31174" msgid "Default select action for albums on the home screen" -msgstr "Acción por defecto al elegir álbumes en la pantalla inicial" +msgstr "Acción por defecto al elegir álbumes en la pantalla de inicio" #. Setting to control what happens when clicking a TV show on the home screen msgctxt "#31175" msgid "Default select action for TV shows on the home screen" -msgstr "Acción por defecto al seleccionar series de TV en la pantalla inicial" +msgstr "Acción por defecto al seleccionar series de TV en la pantalla de inicio" #. Setting to control what happens when clicking a movie set on the home screen msgctxt "#31176" msgid "Default select action for movie sets on the home screen" -msgstr "Acción por defecto al seleccionar películas en la pantalla inicial" +msgstr "Acción por defecto al seleccionar películas en la pantalla de inicio" # empty strings from id 31170 to 31599 #. Label to show the video codec name @@ -680,7 +680,7 @@ msgstr "Códec de vídeo" #. Label to show the video resolution msgctxt "#31601" msgid "Video resolution" -msgstr "Resolución de Vídeo" +msgstr "Resolución de vídeo" #. Label to show the video aspect msgctxt "#31602" diff --git a/addons/skin.estuary/language/resource.language.nl_nl/strings.po b/addons/skin.estuary/language/resource.language.nl_nl/strings.po index 220d977634b1c..ac838f82d46b8 100644 --- a/addons/skin.estuary/language/resource.language.nl_nl/strings.po +++ b/addons/skin.estuary/language/resource.language.nl_nl/strings.po @@ -5,17 +5,17 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-12-04 09:42+0000\n" -"Last-Translator: Christian Gade \n" +"PO-Revision-Date: 2024-05-20 15:13+0000\n" +"Last-Translator: Mark Peters \n" "Language-Team: Dutch \n" "Language: nl_nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.2.1\n" +"X-Generator: Weblate 5.5.4\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -55,11 +55,11 @@ msgstr "Bekijk als 2D" msgctxt "#31006" msgid "Random movies" -msgstr "Willekeurige films" +msgstr "Willekeurige speelfilms" msgctxt "#31007" msgid "Unwatched movies" -msgstr "Niet-bekeken films" +msgstr "Niet-bekeken speelfilms" msgctxt "#31008" msgid "Enable category widgets" @@ -67,11 +67,11 @@ msgstr "Activeer categoriewidgets" msgctxt "#31009" msgid "Download icons" -msgstr "Download pictogrammen" +msgstr "Download iconen" msgctxt "#31010" msgid "In progress movies" -msgstr "Gedeeltelijk bekeken films" +msgstr "Gedeeltelijk bekeken speelfilms" msgctxt "#31011" msgid "Most played albums" @@ -126,7 +126,7 @@ msgstr "Sorteer op" msgctxt "#31023" msgid "Viewtype" -msgstr "Viewtype" +msgstr "Weergavetype" msgctxt "#31024" msgid "Choose rating to display for media items" @@ -271,7 +271,7 @@ msgstr "Muziekafspeellijst" msgctxt "#31067" msgid "Event log" -msgstr "Gebeurtenisregistratie" +msgstr "Gebeurtenislogboek" msgctxt "#31068" msgid "Choose presets" @@ -408,7 +408,7 @@ msgstr "Zoek in lokale bibliotheek" msgctxt "#31114" msgid "Search YouTube" -msgstr "Zoek in YouTube" +msgstr "Zoek op YouTube" msgctxt "#31115" msgid "Toggle subtitle" @@ -468,7 +468,7 @@ msgstr "Algemene instellingen die van toepassing zijn op alle onderdelen van de msgctxt "#31130" msgid "Main menu-related settings: Configure the home screen to your likings." -msgstr "Hoofdmenu gerelateerde instellingen: Configureer het homescreen naar wens." +msgstr "Hoofdmenu gerelateerde instellingen: Configureer het startscherm naar wens." msgctxt "#31131" msgid "Choose skin fanart pack" @@ -524,7 +524,7 @@ msgstr "Afspeelsnelheid" msgctxt "#31143" msgid "You did not set up PVR yet. In order to use PVR, choose a PVR client addon and configure it. Please visit http://kodi.wiki/view/PVR to learn more." -msgstr "U heeft nog geen PVR ingesteld. Om gebruikt te kinen maken van PVR, kieze een PVR cliënt addon en configureer deze. Bezoek http://kodi.wiki/view/PVR om meer te weten tekomen." +msgstr "U heeft nog geen PVR ingesteld. Om gebruikt te kunnen maken van PVR, kies een PVR cliënt addon en configureer deze. Bezoek https://kodi.wiki/view/PVR om meer te weten te komen." msgctxt "#31144" msgid "Enter add-on browser" @@ -556,15 +556,15 @@ msgstr "Bron" msgctxt "#31151" msgid "Unwatched music videos" -msgstr "Niet bekeken muziekvideo`s" +msgstr "Niet bekeken muziekvideo's" msgctxt "#31152" msgid "Random music videos" -msgstr "Willekeurige muziekvideo`s" +msgstr "Willekeurige muziekvideo's" msgctxt "#31153" msgid "You do not have any add-ons of this type installed. Enter the add-on browser to download add-ons created by our community." -msgstr "U heeft nog geen enkele add-on van dit type geïnstalleerd. Bezoek de add-onverkenner om add-ons te downloaden gecreëerd door onze community." +msgstr "Je hebt nog geen enkele add-on van dit type geïnstalleerd. Bezoek de add-onverkenner om add-ons te downloaden gecreëerd door onze community." msgctxt "#31154" msgid "Press OK to switch between locations" @@ -634,7 +634,7 @@ msgstr "Laat posters zien in plaats van miniatuur voor muziekvideo's" #. Description label for skin settings area msgctxt "#31169" msgid "Artwork related settings." -msgstr "" +msgstr "Artwork gerelateerde instellingen" #. Label for OSD settings category msgctxt "#31170" @@ -654,22 +654,22 @@ msgstr "Video OSD automatisch sluiten" #. Setting auto close time for video osd msgctxt "#31173" msgid "Video OSD autoclose time (seconds)" -msgstr "" +msgstr "Video OSD verbergtijd (seconden)" #. Setting to control what happens when clicking a music album on the home screen msgctxt "#31174" msgid "Default select action for albums on the home screen" -msgstr "" +msgstr "Standaard selectie voor albums op het start scherm" #. Setting to control what happens when clicking a TV show on the home screen msgctxt "#31175" msgid "Default select action for TV shows on the home screen" -msgstr "" +msgstr "Standaard selectie voor series op het start scherm" #. Setting to control what happens when clicking a movie set on the home screen msgctxt "#31176" msgid "Default select action for movie sets on the home screen" -msgstr "" +msgstr "Standaard selectie voor speelfilm sets op het start scherm" # empty strings from id 31170 to 31599 #. Label to show the video codec name @@ -690,7 +690,7 @@ msgstr "Beeldverhouding" #. Label to show the video bitrate msgctxt "#31603" msgid "Video bitrate" -msgstr "" +msgstr "Video bitsnelheid" #. Label to show the audio codec name msgctxt "#31604" @@ -705,7 +705,7 @@ msgstr "Audiokanalen" #. Label to show the audio bitrate msgctxt "#31606" msgid "Audio bitrate" -msgstr "" +msgstr "Audio bitsnelheid" #. Label to show the screen resolution msgctxt "#31607" diff --git a/addons/skin.estuary/language/resource.language.sl_si/strings.po b/addons/skin.estuary/language/resource.language.sl_si/strings.po index f6d31480fba69..dbee3bd9434a2 100644 --- a/addons/skin.estuary/language/resource.language.sl_si/strings.po +++ b/addons/skin.estuary/language/resource.language.sl_si/strings.po @@ -5,29 +5,29 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-12-04 09:42+0000\n" -"Last-Translator: Christian Gade \n" +"PO-Revision-Date: 2024-05-15 07:51+0000\n" +"Last-Translator: Simon \n" "Language-Team: Slovenian \n" "Language: sl_si\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n" -"X-Generator: Weblate 5.2.1\n" +"X-Generator: Weblate 5.5.4\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" -msgstr "" +msgstr "Estuary preobleka od phil65. (Kodijeva privzeta preobleka)" msgctxt "Addon Description" msgid "Estuary is the default skin for Kodi 17.0 and above. It attempts to be easy for first time Kodi users to understand and use." -msgstr "" +msgstr "Estuary je privzeta preobleka za Kodi 17.0 in novejše. Poskuša biti enostaven za razumevanje in uporabo za prve uporabnike Kodija." msgctxt "Addon Disclaimer" msgid "Estuary is the default skin for Kodi, removing it may cause issues" -msgstr "" +msgstr "Estuary je privzeta preobleka za Kodi, če jo odstranite, lahko povzroči težave" msgctxt "#31000" msgid "Now playing" @@ -39,7 +39,7 @@ msgstr "Poišči …" msgctxt "#31002" msgid "Show media fanart as background" -msgstr "" +msgstr "Pokaži medijsko fanart kot ozadje" msgctxt "#31003" msgid "Cinema mode" @@ -63,7 +63,7 @@ msgstr "Neogledani filmi" msgctxt "#31008" msgid "Enable category widgets" -msgstr "" +msgstr "Omogoči gradnike kategorij" msgctxt "#31009" msgid "Download icons" @@ -101,7 +101,7 @@ msgstr "Nedavno predvajani kanali" msgctxt "#31017" msgid "Rated" -msgstr "" +msgstr "Ocenjeno" #. home screen channel widget: recently played radio channels. (please note that in some non-english languages #31018 and #31016 might not be equal) msgctxt "#31018" @@ -130,11 +130,11 @@ msgstr "Vrsta pogleda" msgctxt "#31024" msgid "Choose rating to display for media items" -msgstr "" +msgstr "Izberite oceno za prikaz medijskih elementov" msgctxt "#31025" msgid "No favourites found. You can add any item from media views to this list by using the context menu." -msgstr "" +msgstr "Ni priljubljenih. Na ta seznam lahko dodate poljuben element iz pogledov medijev s pomočjo kontekstnega menija." msgctxt "#31026" msgid "Timeshift" @@ -142,11 +142,11 @@ msgstr "Časovni zamik" msgctxt "#31027" msgid "Next aired" -msgstr "" +msgstr "Naslednje predvajanje" msgctxt "#31028" msgid "Show fanart" -msgstr "" +msgstr "Prikaži fanart" msgctxt "#31029" msgid "Last logged in" @@ -155,7 +155,7 @@ msgstr "Zadnja prijava" #. Label to show the system memory usage msgctxt "#31030" msgid "System memory usage" -msgstr "" +msgstr "Poraba sistemskega pomnilnika" msgctxt "#31031" msgid "Version info" @@ -163,11 +163,11 @@ msgstr "Podrobnosti različice" msgctxt "#31032" msgid "Order" -msgstr "" +msgstr "Red" msgctxt "#31033" msgid "Your rating" -msgstr "" +msgstr "Vaša ocena" msgctxt "#31034" msgid "Extended info" @@ -179,7 +179,7 @@ msgstr "Strani" msgctxt "#31036" msgid "items" -msgstr "" +msgstr "predmete" msgctxt "#31037" msgid "Selected track" @@ -196,7 +196,7 @@ msgstr "Naprej" # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" -msgstr "" +msgstr "Proizvajalec fotoaparata" msgctxt "#31042" msgid "Playlist options" @@ -204,7 +204,7 @@ msgstr "Možnosti seznama predvajanja" msgctxt "#31043" msgid "Set the type and add rules to create a smart playlist. These playlists are dynamic and include all media items from your database which apply to your chosen rules." -msgstr "" +msgstr "Nastavite vrsto in dodajte pravila, da ustvarite pametni seznam predvajanja. Ti seznami predvajanja so dinamični in vključujejo vse predstavnostne elemente iz vaše zbirke podatkov, ki veljajo za vaša izbrana pravila." msgctxt "#31044" msgid "Add group" @@ -226,7 +226,7 @@ msgstr "Na voljo" # empty strings from id 31049 to 31051 msgctxt "#31052" msgid "filtered" -msgstr "" +msgstr "filtrirano" msgctxt "#31053" msgid "Arial based" @@ -234,31 +234,31 @@ msgstr "Arial" msgctxt "#31054" msgid "Press [B]Left[/B] to step back, or [B]Right[/B] to step forward" -msgstr "" +msgstr "Pritisnite [B]Levo[/B], da stopite nazaj, ali [B]Desno[/B], da stopite naprej" msgctxt "#31055" msgid "Press [B]Right[/B] to frame advance" -msgstr "" +msgstr "Pritisnite [B]Desno[/B] za napredovanje po sliki" msgctxt "#31056" msgid "Go to playlist" -msgstr "" +msgstr "Pojdi na seznam predvajanja" msgctxt "#31057" msgid "Show login screen on startup" -msgstr "" +msgstr "Ob zagonu prikaži prijavni zaslon" msgctxt "#31058" msgid "Automatic Login on startup" -msgstr "" +msgstr "Samodejna prijava ob zagonu" msgctxt "#31061" msgid "Main menu items" -msgstr "" +msgstr "Elementi glavnega menija" msgctxt "#31062" msgid "Choose weather fanart pack" -msgstr "" +msgstr "Izberite vremenski fanart paket" msgctxt "#31063" msgid "Sections" @@ -288,7 +288,7 @@ msgstr "Zadnja posodobitev" # empty string with id 31070 msgctxt "#31071" msgid "by" -msgstr "" +msgstr "od" msgctxt "#31072" msgid "Power Options" @@ -304,7 +304,7 @@ msgstr "Skupno trajanje" msgctxt "#31075" msgid "Movie sets" -msgstr "" +msgstr "Filmski kompleti" msgctxt "#31079" msgid "Cast not available" @@ -312,7 +312,7 @@ msgstr "Igralska zasedba ni na voljo" msgctxt "#31080" msgid "Ends at" -msgstr "" +msgstr "Konec ob" msgctxt "#31082" msgid "Lyrics add-on" @@ -342,7 +342,7 @@ msgstr "Pokaži podrobnosti vremena v vrhnji vrstici" # empty string with id 31094 msgctxt "#31095" msgid "Use slide animations" -msgstr "" +msgstr "Uporabite animacije diapozitivov" msgctxt "#31096" msgid "Local subtitle available" @@ -359,7 +359,7 @@ msgstr "Izberite uporabniški profil Kodi[CR]za prijavo in nadaljevanje" #. viewtype name msgctxt "#31099" msgid "IconWall" -msgstr "" +msgstr "Stena z ikonami" #. viewtype name msgctxt "#31100" @@ -369,7 +369,7 @@ msgstr "Zamik" #. viewtype name msgctxt "#31101" msgid "InfoWall" -msgstr "" +msgstr "Info zid" #. viewtype name msgctxt "#31102" @@ -382,11 +382,11 @@ msgstr "Vpis besedila ..." msgctxt "#31104" msgid "Your library is currently empty. In order to populate it with your personal media, enter \"Files\" section, add a media source and configure it. After the source has been added and indexed you will be able to browse your library." -msgstr "" +msgstr "Vaša knjižnica je trenutno prazna. Če ga želite zapolniti s svojim osebnim medijem, odprite razdelek »Datoteke«, dodajte vir predstavnosti in ga konfigurirajte. Ko bo vir dodan in indeksiran, boste lahko brskali po knjižnici." msgctxt "#31105" msgid "Add video sources and set the appropriate content type in order to populate your video libraries." -msgstr "" +msgstr "Dodajte video vire in nastavite ustrezno vrsto vsebine, da napolnite svoje video knjižnice." msgctxt "#31106" msgid "Teletext" @@ -395,24 +395,24 @@ msgstr "Teletekst" #. viewtype name msgctxt "#31107" msgid "WideList" -msgstr "" +msgstr "Širok seznam" # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" -msgstr "" +msgstr "Vnesite razdelek datotek" msgctxt "#31111" msgid "View your personal pictures or download one of the many image add-ons from the official repository." -msgstr "" +msgstr "Oglejte si svoje osebne slike ali prenesite enega od številnih slikovnih dodatkov iz uradnega skladišča." msgctxt "#31112" msgid "Toggle audio stream" -msgstr "" +msgstr "Preklop zvočnega toka" msgctxt "#31113" msgid "Search local library" -msgstr "" +msgstr "Iskanje po lokalni knjižnici" msgctxt "#31114" msgid "Search YouTube" @@ -424,11 +424,11 @@ msgstr "Preklopi podnapise" msgctxt "#31116" msgid "Remove this main menu item" -msgstr "" +msgstr "Odstranite ta element glavnega menija" msgctxt "#31117" msgid "Edit nodes" -msgstr "" +msgstr "Uredite vozlišča" msgctxt "#31118" msgid "Enter add-on browser" @@ -436,11 +436,11 @@ msgstr "Vnesi brskalnik dodatkov" msgctxt "#31119" msgid "You do not have any add-ons installed yet. Visit our add-on browser to browse through our collection and improve your Kodi experience." -msgstr "" +msgstr "Nimate še nameščenih dodatkov. Obiščite naš brskalnik z dodatki, da brskate po naši zbirki in izboljšate svojo izkušnjo Kodi." msgctxt "#31120" msgid "You did not set up a weather provider yet. In order to view weather information, choose a weather provider and set up your location." -msgstr "" +msgstr "Niste še nastavili ponudnika vremena. Za ogled vremenskih informacij izberite ponudnika vremena in nastavite svojo lokacijo." msgctxt "#31121" msgid "Set weather provider" @@ -452,16 +452,16 @@ msgstr "Neogledane TV serije" msgctxt "#31123" msgid "Same director" -msgstr "" +msgstr "Isti režiser" # empty string with id 31124 msgctxt "#31125" msgid "Press up for actor info" -msgstr "" +msgstr "Pritisnite gor za informacije o igralcu" msgctxt "#31126" msgid "Press OK to read plot" -msgstr "" +msgstr "Pritisnite OK za branje risbe" msgctxt "#31127" msgid "Show icons" @@ -469,19 +469,19 @@ msgstr "Pokaži ikone" msgctxt "#31128" msgid "Contributors" -msgstr "" +msgstr "Sodelujoči" msgctxt "#31129" msgid "General settings applying to all areas of the skin." -msgstr "" +msgstr "Splošne nastavitve za vsa področja vmesnika." msgctxt "#31130" msgid "Main menu-related settings: Configure the home screen to your likings." -msgstr "" +msgstr "Nastavitve, povezane z glavnim menijem: konfigurirajte začetni zaslon po svojih željah." msgctxt "#31131" msgid "Choose skin fanart pack" -msgstr "" +msgstr "Izberite paket fanart za vmesnik" msgctxt "#31132" msgid "Select Program" @@ -493,7 +493,7 @@ msgstr "Izbor ločljivosti" msgctxt "#31134" msgid "Remaining" -msgstr "" +msgstr "Preostalo" msgctxt "#31135" msgid "Binary" @@ -501,7 +501,7 @@ msgstr "Dvojiško" msgctxt "#31136" msgid "Click here to see latest changes..." -msgstr "" +msgstr "Kliknite tukaj za ogled zadnjih sprememb..." #. Label to show PVR info page msgctxt "#31137" @@ -533,7 +533,7 @@ msgstr "Hitrost predvajanja" msgctxt "#31143" msgid "You did not set up PVR yet. In order to use PVR, choose a PVR client addon and configure it. Please visit http://kodi.wiki/view/PVR to learn more." -msgstr "" +msgstr "Niste še nastavili PVR. Če želite uporabljati PVR, izberite dodatek odjemalca PVR in ga konfigurirajte. Za več informacij obiščite http://kodi.wiki/view/PVR." msgctxt "#31144" msgid "Enter add-on browser" @@ -557,7 +557,7 @@ msgstr "Kategorije" msgctxt "#31149" msgid "Select genre fanart pack" -msgstr "" +msgstr "Izberite žanr fanart paketa" msgctxt "#31150" msgid "Origin" @@ -573,15 +573,15 @@ msgstr "Naključni videospoti" msgctxt "#31153" msgid "You do not have any add-ons of this type installed. Enter the add-on browser to download add-ons created by our community." -msgstr "" +msgstr "Nimate nameščenih nobenih dodatkov te vrste. Vstopite v brskalnik dodatkov, da prenesete dodatke, ki jih je ustvarila naša skupnost." msgctxt "#31154" msgid "Press OK to switch between locations" -msgstr "" +msgstr "Pritisnite OK za preklop med lokacijami" msgctxt "#31155" msgid "No bookmarks created yet." -msgstr "" +msgstr "Ustvarjen ni še noben zaznamek." msgctxt "#31156" msgid "Choose background pattern" @@ -593,7 +593,7 @@ msgstr "Uredi kategorije" msgctxt "#31158" msgid "Touch mode" -msgstr "" +msgstr "Način na dotik" msgctxt "#31159" msgid "Artwork" @@ -609,16 +609,16 @@ msgstr "Številčnica" msgctxt "#31162" msgid "Play your personal games or download one of the many game add-ons from the official repository." -msgstr "" +msgstr "Igrajte svoje osebne igre ali prenesite enega od številnih dodatkov za igre iz uradnega skladišča." msgctxt "#31163" msgid "Show Fanart background" -msgstr "" +msgstr "Pokaži ozadje Fanart" #. Choose profile identifier msgctxt "#31164" msgid "Choose kind of profile identification" -msgstr "" +msgstr "Izberite vrsto identifikacije profila" #. Label for the kind of profile identification msgctxt "#31165" @@ -638,7 +638,7 @@ msgstr "Animirano ozadje" #. Label of a setting msgctxt "#31168" msgid "Show posters instead of thumbs for musicvideos" -msgstr "" +msgstr "Pokažite plakate namesto palcev za glasbene videoposnetke" #. Description label for skin settings area msgctxt "#31169" @@ -648,37 +648,37 @@ msgstr "Nastavitve grafične podobe" #. Label for OSD settings category msgctxt "#31170" msgid "On screen display" -msgstr "" +msgstr "Prikaz na zaslonu" #. Helper text for the label of OSD settings category msgctxt "#31171" msgid "On screen display (OSD) related settings" -msgstr "" +msgstr "Nastavitve, povezane z zaslonskim prikazom (OSD)" #. Setting Automatically close video OSD msgctxt "#31172" msgid "Automatically close video OSD" -msgstr "" +msgstr "Samodejno zapri video OSD" #. Setting auto close time for video osd msgctxt "#31173" msgid "Video OSD autoclose time (seconds)" -msgstr "" +msgstr "Čas samodejnega zapiranja video OSD (sekunde)" #. Setting to control what happens when clicking a music album on the home screen msgctxt "#31174" msgid "Default select action for albums on the home screen" -msgstr "" +msgstr "Privzeta izbira dejanja za albume na začetnem zaslonu" #. Setting to control what happens when clicking a TV show on the home screen msgctxt "#31175" msgid "Default select action for TV shows on the home screen" -msgstr "" +msgstr "Privzeto izberite dejanje za TV-oddaje na začetnem zaslonu" #. Setting to control what happens when clicking a movie set on the home screen msgctxt "#31176" msgid "Default select action for movie sets on the home screen" -msgstr "" +msgstr "Privzeta izbira dejanja za nize filmov na začetnem zaslonu" # empty strings from id 31170 to 31599 #. Label to show the video codec name From 2a0010a92fe80ec2e37227effee2d06839417c7b Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sat, 22 Jun 2024 10:41:55 +0200 Subject: [PATCH 189/651] [VideoPlayer] Add GUI settings for audio/video playback queues --- .../resources/strings.po | 64 ++++++++++++++++++- system/settings/settings.xml | 20 ++++++ xbmc/cores/VideoPlayer/VideoPlayer.cpp | 11 ++-- xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp | 7 +- xbmc/settings/CMakeLists.txt | 2 + xbmc/settings/PlayerSettings.cpp | 46 +++++++++++++ xbmc/settings/PlayerSettings.h | 27 ++++++++ xbmc/settings/Settings.cpp | 7 ++ xbmc/settings/Settings.h | 2 + 9 files changed, 175 insertions(+), 11 deletions(-) create mode 100644 xbmc/settings/PlayerSettings.cpp create mode 100644 xbmc/settings/PlayerSettings.h diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index c81cd35e55148..ba8fe579f8f34 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -8398,7 +8398,19 @@ msgctxt "#14128" msgid "Allow double refresh rates" msgstr "" -#empty strings from id 14129 to 14199 +#empty strings from id 14129 to 14186 + +#: system/settings/settings.xml +msgctxt "#14187" +msgid "Advanced" +msgstr "" + +#: system/settings/settings.xml +msgctxt "#14188" +msgid "Player audio/video queues" +msgstr "" + +#empty strings from id 14189 to 14199 #: system/settings/settings.xml msgctxt "#14200" @@ -22496,17 +22508,57 @@ msgstr "" #. Value of setting - MByte #: xbmc/settings/SevicesSettings.cpp +#: xbmc/settings/PlayerSettings.cpp msgctxt "#37122" msgid "{0:d} MB" msgstr "" #. Value of setting - GByte #: xbmc/settings/SevicesSettings.cpp +#: xbmc/settings/PlayerSettings.cpp msgctxt "#37123" msgid "{0:d} GB" msgstr "" -#empty strings from id 37124 to 38010 +#empty strings from id 37124 to 37127 + +#. Value of setting - second +#: xbmc/settings/PlayerSettings.cpp +msgctxt "#37128" +msgid "{0:d} second" +msgstr "" + +#. Value of setting - seconds +#: xbmc/settings/PlayerSettings.cpp +msgctxt "#37129" +msgid "{0:d} seconds" +msgstr "" + +#. Setting "Audio/video queue time" +#: system/settings/settings.xml +msgctxt "#37130" +msgid "Audio/video queue time" +msgstr "" + +#. Description of setting with label #37130 "Audio/video queue time" +#: system/settings/settings.xml +msgctxt "#37131" +msgid "Duration in seconds of audio/video queues. The duration determines the amount of data stored in memory." +msgstr "" + +#. Setting "Video queue maximum size" +#: system/settings/settings.xml +msgctxt "#37132" +msgid "Video queue maximum size" +msgstr "" + +#. Description of setting with label #37132 "Video queue maximum size" +#: system/settings/settings.xml +msgctxt "#37133" +msgid "Limits the maximum memory usage for the video queue. The amount of memory used depends on the video bitrate and the length of the queue. If the memory limit is reached, the queue time is reduced as necessary." +msgstr "" + +#empty strings from id 37134 to 38010 #. Setting #38011 "Show All Items entry" #: system/settings/settings.xml @@ -22918,7 +22970,13 @@ msgctxt "#38112" msgid "Automatically go to the visualisation window when audio playback starts" msgstr "" -#empty strings from id 38113 to 38189 +#. Description of category #14187 "Advanced" +#: system/settings/settings.xml +msgctxt "#38113" +msgid "This category contains the advanced settings for video playback" +msgstr "" + +#empty strings from id 38114 to 38189 msgctxt "#38190" msgid "Extract thumbnails from video files" diff --git a/system/settings/settings.xml b/system/settings/settings.xml index 86841efbed0fd..bd47c128f4250 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -891,6 +891,26 @@
+ + + + 2 + 40 + + playerqueuetimesizes + + + + + 2 + 256 + + playerqueuedatasizes + + + + +
diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index be3d3a030b503..1b800342f91a6 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -73,11 +73,6 @@ using namespace KODI; using namespace std::chrono_literals; -namespace -{ -constexpr double VP_MESSAGE_QUEUE_TIME_SIZE = 8.0; -} - //------------------------------------------------------------------------------ // selection streams //------------------------------------------------------------------------------ @@ -644,7 +639,11 @@ CVideoPlayer::CVideoPlayer(IPlayerCallback& callback) m_HasVideo = false; m_HasAudio = false; m_UpdateStreamDetails = false; - m_messageQueueTimeSize = VP_MESSAGE_QUEUE_TIME_SIZE; + + const int tenthsSeconds = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_VIDEOPLAYER_QUEUETIMESIZE); + + m_messageQueueTimeSize = static_cast(tenthsSeconds) / 10.0; m_SkipCommercials = true; diff --git a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp index 6588c070013cc..1b22464d49871 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp @@ -16,6 +16,7 @@ #include "cores/VideoPlayer/Interface/DemuxPacket.h" #include "cores/VideoPlayer/Interface/TimingConstants.h" #include "settings/AdvancedSettings.h" +#include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/MathUtils.h" #include "utils/log.h" @@ -68,8 +69,10 @@ CVideoPlayerVideo::CVideoPlayerVideo(CDVDClock* pClock, m_iDroppedRequest = 0; m_fForcedAspectRatio = 0; - // allows max bitrate of 128 Mbit/s (e.g. UHD Blu-Ray) during m_messageQueueTimeSize seconds - m_messageQueue.SetMaxDataSize(128 * m_messageQueueTimeSize / 8 * 1024 * 1024); + const int sizeMB = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_VIDEOPLAYER_QUEUEDATASIZE); + + m_messageQueue.SetMaxDataSize(sizeMB * 1024 * 1024); m_messageQueue.SetMaxTimeSize(m_messageQueueTimeSize); m_iDroppedFrames = 0; diff --git a/xbmc/settings/CMakeLists.txt b/xbmc/settings/CMakeLists.txt index d477f51d5d91f..26a31568475c1 100644 --- a/xbmc/settings/CMakeLists.txt +++ b/xbmc/settings/CMakeLists.txt @@ -4,6 +4,7 @@ set(SOURCES AdvancedSettings.cpp LibExportSettings.cpp MediaSettings.cpp MediaSourceSettings.cpp + PlayerSettings.cpp ServicesSettings.cpp SettingAddon.cpp SettingConditions.cpp @@ -28,6 +29,7 @@ set(HEADERS AdvancedSettings.h LibExportSettings.h MediaSettings.h MediaSourceSettings.h + PlayerSettings.h ServicesSettings.h SettingAddon.h SettingConditions.h diff --git a/xbmc/settings/PlayerSettings.cpp b/xbmc/settings/PlayerSettings.cpp new file mode 100644 index 0000000000000..581c63220e896 --- /dev/null +++ b/xbmc/settings/PlayerSettings.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 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 "PlayerSettings.h" + +#include "guilib/LocalizeStrings.h" +#include "utils/StringUtils.h" + +void CPlayerSettings::SettingOptionsQueueTimeSizesFiller(const SettingConstPtr& setting, + std::vector& list, + int& current, + void* data) +{ + const auto& secFloat = g_localizeStrings.Get(13553); + const auto& seconds = g_localizeStrings.Get(37129); + const auto& second = g_localizeStrings.Get(37128); + + list.emplace_back(StringUtils::Format(secFloat, 0.5), 5); + list.emplace_back(StringUtils::Format(second, 1), 10); + list.emplace_back(StringUtils::Format(seconds, 2), 20); + list.emplace_back(StringUtils::Format(seconds, 4), 40); + list.emplace_back(StringUtils::Format(seconds, 8), 80); + list.emplace_back(StringUtils::Format(seconds, 16), 160); +} + +void CPlayerSettings::SettingOptionsQueueDataSizesFiller(const SettingConstPtr& setting, + std::vector& list, + int& current, + void* data) +{ + const auto& mb = g_localizeStrings.Get(37122); + const auto& gb = g_localizeStrings.Get(37123); + + list.emplace_back(StringUtils::Format(mb, 16), 16); + list.emplace_back(StringUtils::Format(mb, 32), 32); + list.emplace_back(StringUtils::Format(mb, 64), 64); + list.emplace_back(StringUtils::Format(mb, 128), 128); + list.emplace_back(StringUtils::Format(mb, 256), 256); + list.emplace_back(StringUtils::Format(mb, 512), 512); + list.emplace_back(StringUtils::Format(gb, 1), 1024); +} diff --git a/xbmc/settings/PlayerSettings.h b/xbmc/settings/PlayerSettings.h new file mode 100644 index 0000000000000..683e110a508dc --- /dev/null +++ b/xbmc/settings/PlayerSettings.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 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/ISubSettings.h" +#include "settings/lib/Setting.h" + +#include + +class CPlayerSettings : public ISubSettings +{ +public: + static void SettingOptionsQueueTimeSizesFiller(const SettingConstPtr& setting, + std::vector& list, + int& current, + void* data); + static void SettingOptionsQueueDataSizesFiller(const SettingConstPtr& setting, + std::vector& list, + int& current, + void* data); +}; diff --git a/xbmc/settings/Settings.cpp b/xbmc/settings/Settings.cpp index 5d940f674c091..a32f5f17bcf6d 100644 --- a/xbmc/settings/Settings.cpp +++ b/xbmc/settings/Settings.cpp @@ -40,6 +40,7 @@ #include "settings/DisplaySettings.h" #include "settings/MediaSettings.h" #include "settings/MediaSourceSettings.h" +#include "settings/PlayerSettings.h" #include "settings/ServicesSettings.h" #include "settings/SettingConditions.h" #include "settings/SettingsComponent.h" @@ -419,6 +420,10 @@ void CSettings::InitializeOptionFillers() "filecachereadfactors", CServicesSettings::SettingOptionsReadFactorsFiller); GetSettingsManager()->RegisterSettingOptionsFiller( "filecachechunksizes", CServicesSettings::SettingOptionsCacheChunkSizesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller( + "playerqueuetimesizes", CPlayerSettings::SettingOptionsQueueTimeSizesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller( + "playerqueuedatasizes", CPlayerSettings::SettingOptionsQueueDataSizesFiller); } void CSettings::UninitializeOptionFillers() @@ -470,6 +475,8 @@ void CSettings::UninitializeOptionFillers() GetSettingsManager()->UnregisterSettingOptionsFiller("filecachememorysizes"); GetSettingsManager()->UnregisterSettingOptionsFiller("filecachereadfactors"); GetSettingsManager()->UnregisterSettingOptionsFiller("filecachechunksizes"); + GetSettingsManager()->UnregisterSettingOptionsFiller("playerqueuetimesizes"); + GetSettingsManager()->UnregisterSettingOptionsFiller("playerqueuedatasizes"); } void CSettings::InitializeConditions() diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h index b1124afac1657..354756d133475 100644 --- a/xbmc/settings/Settings.h +++ b/xbmc/settings/Settings.h @@ -136,6 +136,8 @@ class CSettings : public CSettingsBase, public CSettingCreator, public CSettingC static constexpr auto SETTING_VIDEOPLAYER_SUPPORTMVC = "videoplayer.supportmvc"; static constexpr auto SETTING_VIDEOPLAYER_CONVERTDOVI = "videoplayer.convertdovi"; static constexpr auto SETTING_VIDEOPLAYER_ALLOWEDHDRFORMATS = "videoplayer.allowedhdrformats"; + static constexpr auto SETTING_VIDEOPLAYER_QUEUETIMESIZE = "videoplayer.queuetimesize"; + static constexpr auto SETTING_VIDEOPLAYER_QUEUEDATASIZE = "videoplayer.queuedatasize"; static constexpr auto SETTING_MYVIDEOS_SELECTACTION = "myvideos.selectaction"; static constexpr auto SETTING_MYVIDEOS_SELECTDEFAULTVERSION = "myvideos.selectdefaultversion"; static constexpr auto SETTING_MYVIDEOS_PLAYACTION = "myvideos.playaction"; From 76b9e112b2b2e1209e10054aeef4a746b04e3314 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sat, 22 Jun 2024 18:56:06 +0200 Subject: [PATCH 190/651] [VideoPlayer] Improves audio/video queues time calculation Before calculated only proportionally to level but when level is e.g. 99%, playback time is not always 99% of max queue time because data amount (depending on bitrate) not always provides same playback time. Queues are data AND time based: this means queue can be full because has reached maximum data capacity OR has reached maximum playback time with e.g. half of data. In two cases level is 100%. --- xbmc/cores/VideoPlayer/DVDMessageQueue.cpp | 6 +++--- xbmc/cores/VideoPlayer/DVDMessageQueue.h | 2 +- xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp | 12 +++++------- xbmc/cores/VideoPlayer/VideoPlayerAudio.h | 1 - xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp | 10 ++++------ xbmc/cores/VideoPlayer/VideoPlayerVideo.h | 2 -- 6 files changed, 13 insertions(+), 20 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDMessageQueue.cpp b/xbmc/cores/VideoPlayer/DVDMessageQueue.cpp index 34886cc8a309b..20d1caece6ab0 100644 --- a/xbmc/cores/VideoPlayer/DVDMessageQueue.cpp +++ b/xbmc/cores/VideoPlayer/DVDMessageQueue.cpp @@ -333,14 +333,14 @@ int CDVDMessageQueue::GetLevel() const return level; } -int CDVDMessageQueue::GetTimeSize() const +double CDVDMessageQueue::GetTimeSize() const { std::unique_lock lock(m_section); if (IsDataBased()) - return 0; + return 0.0; else - return (int)((m_TimeFront - m_TimeBack) / DVD_TIME_BASE); + return (m_TimeFront - m_TimeBack) / DVD_TIME_BASE; } bool CDVDMessageQueue::IsDataBased() const diff --git a/xbmc/cores/VideoPlayer/DVDMessageQueue.h b/xbmc/cores/VideoPlayer/DVDMessageQueue.h index 77342d4ce783d..0e745512ac3c7 100644 --- a/xbmc/cores/VideoPlayer/DVDMessageQueue.h +++ b/xbmc/cores/VideoPlayer/DVDMessageQueue.h @@ -74,7 +74,7 @@ class CDVDMessageQueue } int GetDataSize() const { return m_iDataSize; } - int GetTimeSize() const; + double GetTimeSize() const; unsigned GetPacketCount(CDVDMsg::Message type); bool ReceivedAbortRequest() { return m_bAbortRequest; } void WaitUntilEmpty(); diff --git a/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp b/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp index 91220f92deada..6017bf5c260b0 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp @@ -51,8 +51,7 @@ CVideoPlayerAudio::CVideoPlayerAudio(CDVDClock* pClock, IDVDStreamPlayerAudio(processInfo), m_messageQueue("audio"), m_messageParent(parent), - m_audioSink(pClock), - m_messageQueueTimeSize(messageQueueTimeSize) + m_audioSink(pClock) { m_pClock = pClock; m_audioClock = 0; @@ -66,8 +65,8 @@ CVideoPlayerAudio::CVideoPlayerAudio(CDVDClock* pClock, m_maxspeedadjust = 0.0; // allows max bitrate of 18 Mbit/s (TrueHD max peak) during m_messageQueueTimeSize seconds - m_messageQueue.SetMaxDataSize(18 * m_messageQueueTimeSize / 8 * 1024 * 1024); - m_messageQueue.SetMaxTimeSize(m_messageQueueTimeSize); + m_messageQueue.SetMaxDataSize(18 * messageQueueTimeSize / 8 * 1024 * 1024); + m_messageQueue.SetMaxTimeSize(messageQueueTimeSize); m_disconAdjustTimeMs = processInfo.GetMaxPassthroughOffSyncDuration(); } @@ -201,10 +200,9 @@ void CVideoPlayerAudio::OnStartup() void CVideoPlayerAudio::UpdatePlayerInfo() { - const int level = m_messageQueue.GetLevel(); std::ostringstream s; - s << "aq:" << std::setw(2) << std::min(99, level); - s << "% " << std::fixed << std::setprecision(3) << m_messageQueueTimeSize * level / 100.0; + s << "aq:" << std::setw(2) << std::min(99, m_messageQueue.GetLevel()); + s << "% " << std::fixed << std::setprecision(3) << m_messageQueue.GetTimeSize(); s << "s, Kb/s:" << std::fixed << std::setprecision(2) << m_audioStats.GetBitrate() / 1024.0; // print a/v discontinuity adjustments counter when audio is not resampled (passthrough mode) diff --git a/xbmc/cores/VideoPlayer/VideoPlayerAudio.h b/xbmc/cores/VideoPlayer/VideoPlayerAudio.h index 24c394c52c086..d52fbd5b70312 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerAudio.h +++ b/xbmc/cores/VideoPlayer/VideoPlayerAudio.h @@ -120,6 +120,5 @@ class CVideoPlayerAudio : public CThread, public IDVDStreamPlayerAudio bool m_displayReset = false; unsigned int m_disconAdjustTimeMs = 50; // maximum sync-off before adjusting int m_disconAdjustCounter = 0; - double m_messageQueueTimeSize{0.0}; }; diff --git a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp index 1b22464d49871..9020467c88470 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp @@ -54,8 +54,7 @@ CVideoPlayerVideo::CVideoPlayerVideo(CDVDClock* pClock, IDVDStreamPlayerVideo(processInfo), m_messageQueue("video"), m_messageParent(parent), - m_renderManager(renderManager), - m_messageQueueTimeSize(messageQueueTimeSize) + m_renderManager(renderManager) { m_pClock = pClock; m_pOverlayContainer = pOverlayContainer; @@ -73,7 +72,7 @@ CVideoPlayerVideo::CVideoPlayerVideo(CDVDClock* pClock, CSettings::SETTING_VIDEOPLAYER_QUEUEDATASIZE); m_messageQueue.SetMaxDataSize(sizeMB * 1024 * 1024); - m_messageQueue.SetMaxTimeSize(m_messageQueueTimeSize); + m_messageQueue.SetMaxTimeSize(messageQueueTimeSize); m_iDroppedFrames = 0; m_fFrameRate = 25; @@ -956,10 +955,9 @@ CVideoPlayerVideo::EOutputState CVideoPlayerVideo::OutputPicture(const VideoPict std::string CVideoPlayerVideo::GetPlayerInfo() { - const int level = m_processInfo.GetLevelVQ(); std::ostringstream s; - s << "vq:" << std::setw(2) << std::min(99, level); - s << "% " << std::fixed << std::setprecision(3) << m_messageQueueTimeSize * level / 100.0; + s << "vq:" << std::setw(2) << std::min(99, m_processInfo.GetLevelVQ()); + s << "% " << std::fixed << std::setprecision(3) << m_messageQueue.GetTimeSize(); s << "s, Mb/s:" << std::fixed << std::setprecision(2) << static_cast(GetVideoBitrate()) / (1024.0 * 1024.0); s << ", fr:" << std::fixed << std::setprecision(3) << m_fFrameRate; diff --git a/xbmc/cores/VideoPlayer/VideoPlayerVideo.h b/xbmc/cores/VideoPlayer/VideoPlayerVideo.h index f763334301f9d..298b132659cb6 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerVideo.h +++ b/xbmc/cores/VideoPlayer/VideoPlayerVideo.h @@ -142,6 +142,4 @@ class CVideoPlayerVideo : public CThread, public IDVDStreamPlayerVideo VideoPicture m_picture; EOutputState m_outputSate{OUTPUT_NORMAL}; - - double m_messageQueueTimeSize{0.0}; }; From 9566182e7176f94ce470d62fb7454e74ce15e6b1 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Tue, 25 Jun 2024 06:37:11 +0200 Subject: [PATCH 191/651] changed: move implementation of LoadTracksFromCueDocument to CCueDocument --- xbmc/CueDocument.cpp | 59 ++++++++++++++++++++++++ xbmc/CueDocument.h | 5 ++ xbmc/FileItem.cpp | 55 +--------------------- xbmc/test/CMakeLists.txt | 1 + xbmc/test/TestCueDocument.cpp | 87 +++++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 53 deletions(-) create mode 100644 xbmc/test/TestCueDocument.cpp diff --git a/xbmc/CueDocument.cpp b/xbmc/CueDocument.cpp index 2d09d07f905fa..2094b02492eac 100644 --- a/xbmc/CueDocument.cpp +++ b/xbmc/CueDocument.cpp @@ -48,6 +48,7 @@ #include "Util.h" #include "filesystem/Directory.h" #include "filesystem/File.h" +#include "music/tags/MusicInfoTag.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" #include "utils/CharsetConverter.h" @@ -482,3 +483,61 @@ bool CCueDocument::ResolvePath(std::string &strPath, const std::string &strBase) return true; } +bool CCueDocument::LoadTracks(CFileItemList& scannedItems, const CFileItem& item) +{ + const auto& tag = *item.GetMusicInfoTag(); + + VECSONGS tracks; + this->GetSongs(tracks); + + bool oneFilePerTrack = this->IsOneFilePerTrack(); + + int tracksFound = 0; + for (auto& song : tracks) + { + if (song.strFileName == item.GetPath()) + { + if (tag.Loaded()) + { + if (song.strAlbum.empty() && !tag.GetAlbum().empty()) + song.strAlbum = tag.GetAlbum(); + //Pass album artist to final MusicInfoTag object via setting song album artist vector. + if (song.GetAlbumArtist().empty() && !tag.GetAlbumArtist().empty()) + song.SetAlbumArtist(tag.GetAlbumArtist()); + if (song.genre.empty() && !tag.GetGenre().empty()) + song.genre = tag.GetGenre(); + //Pass artist to final MusicInfoTag object via setting song artist description string only. + //Artist credits not used during loading from cue sheet. + if (song.strArtistDesc.empty() && !tag.GetArtistString().empty()) + song.strArtistDesc = tag.GetArtistString(); + if (tag.GetDiscNumber()) + song.iTrack |= (tag.GetDiscNumber() << 16); // see CMusicInfoTag::GetDiscNumber() + if (!tag.GetCueSheet().empty()) + song.strCueSheet = tag.GetCueSheet(); + + if (tag.GetYear()) + song.strReleaseDate = tag.GetReleaseDate(); + if (song.embeddedArt.Empty() && !tag.GetCoverArtInfo().Empty()) + song.embeddedArt = tag.GetCoverArtInfo(); + } + + if (!song.iDuration && tag.GetDuration() > 0) + { // must be the last song + song.iDuration = CUtil::ConvertMilliSecsToSecsIntRounded( + CUtil::ConvertSecsToMilliSecs(tag.GetDuration()) - song.iStartOffset); + } + if (tag.Loaded() && oneFilePerTrack && + !(tag.GetAlbum().empty() || tag.GetArtist().empty() || tag.GetTitle().empty())) + { + // If there are multiple files in a cue file, the tags from the files should be preferred if they exist. + scannedItems.Add(std::make_shared(song, tag)); + } + else + { + scannedItems.Add(std::make_shared(song)); + } + ++tracksFound; + } + } + return tracksFound != 0; +} diff --git a/xbmc/CueDocument.h b/xbmc/CueDocument.h index 7218a9fe2d308..22fa733de2c3b 100644 --- a/xbmc/CueDocument.h +++ b/xbmc/CueDocument.h @@ -15,6 +15,8 @@ #define MAX_PATH_SIZE 1024 +class CFileItem; +class CFileItemList; class CueReader; class CCueDocument @@ -42,6 +44,9 @@ class CCueDocument void UpdateMediaFile(const std::string& oldMediaFile, const std::string& mediaFile); bool IsOneFilePerTrack() const; bool IsLoaded() const; + + bool LoadTracks(CFileItemList& scannedItems, const CFileItem& item); + private: void Clear(); bool Parse(CueReader& reader, const std::string& strFile = std::string()); diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 69a23f7ee747d..f3fe30b778221 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -1908,61 +1908,10 @@ bool CFileItem::LoadTracksFromCueDocument(CFileItemList& scannedItems) if (!m_cueDocument) return false; - const CMusicInfoTag& tag = *GetMusicInfoTag(); - - VECSONGS tracks; - m_cueDocument->GetSongs(tracks); - - bool oneFilePerTrack = m_cueDocument->IsOneFilePerTrack(); + bool result = m_cueDocument->LoadTracks(scannedItems, *this); m_cueDocument.reset(); - int tracksFound = 0; - for (VECSONGS::iterator it = tracks.begin(); it != tracks.end(); ++it) - { - CSong& song = *it; - if (song.strFileName == GetPath()) - { - if (tag.Loaded()) - { - if (song.strAlbum.empty() && !tag.GetAlbum().empty()) - song.strAlbum = tag.GetAlbum(); - //Pass album artist to final MusicInfoTag object via setting song album artist vector. - if (song.GetAlbumArtist().empty() && !tag.GetAlbumArtist().empty()) - song.SetAlbumArtist(tag.GetAlbumArtist()); - if (song.genre.empty() && !tag.GetGenre().empty()) - song.genre = tag.GetGenre(); - //Pass artist to final MusicInfoTag object via setting song artist description string only. - //Artist credits not used during loading from cue sheet. - if (song.strArtistDesc.empty() && !tag.GetArtistString().empty()) - song.strArtistDesc = tag.GetArtistString(); - if (tag.GetDiscNumber()) - song.iTrack |= (tag.GetDiscNumber() << 16); // see CMusicInfoTag::GetDiscNumber() - if (!tag.GetCueSheet().empty()) - song.strCueSheet = tag.GetCueSheet(); - - if (tag.GetYear()) - song.strReleaseDate = tag.GetReleaseDate(); - if (song.embeddedArt.Empty() && !tag.GetCoverArtInfo().Empty()) - song.embeddedArt = tag.GetCoverArtInfo(); - } - - if (!song.iDuration && tag.GetDuration() > 0) - { // must be the last song - song.iDuration = CUtil::ConvertMilliSecsToSecsIntRounded(CUtil::ConvertSecsToMilliSecs(tag.GetDuration()) - song.iStartOffset); - } - if ( tag.Loaded() && oneFilePerTrack && ! ( tag.GetAlbum().empty() || tag.GetArtist().empty() || tag.GetTitle().empty() ) ) - { - // If there are multiple files in a cue file, the tags from the files should be preferred if they exist. - scannedItems.Add(std::make_shared(song, tag)); - } - else - { - scannedItems.Add(std::make_shared(song)); - } - ++tracksFound; - } - } - return tracksFound != 0; + return result; } std::string CFileItem::GetUserMusicThumb(bool alwaysCheckRemote /* = false */, bool fallbackToFolder /* = false */) const diff --git a/xbmc/test/CMakeLists.txt b/xbmc/test/CMakeLists.txt index 2ee6066aff5e6..41c1b4e21e6ad 100644 --- a/xbmc/test/CMakeLists.txt +++ b/xbmc/test/CMakeLists.txt @@ -1,4 +1,5 @@ set(SOURCES TestBasicEnvironment.cpp + TestCueDocument.cpp TestFileItem.cpp TestURL.cpp TestUtil.cpp diff --git a/xbmc/test/TestCueDocument.cpp b/xbmc/test/TestCueDocument.cpp new file mode 100644 index 0000000000000..e3ce24a0a5e6c --- /dev/null +++ b/xbmc/test/TestCueDocument.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2024 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 "CueDocument.h" +#include "FileItem.h" +#include "FileItemList.h" +#include "music/tags/MusicInfoTag.h" + +#include +#include + +#include + +using namespace KODI; + +namespace +{ + +const std::string input = + R"(PERFORMER "Pink Floyd" +TITLE "The Dark Side Of The Moon" + FILE "The Dark Side Of The Moon.mp3" WAVE + TRACK 01 AUDIO + TITLE "Speak To Me / Breathe" + PERFORMER "Pink Floyd" + INDEX 00 00:00:00 + INDEX 01 00:00:32 + TRACK 02 AUDIO + TITLE "On The Run" + PERFORMER "Pink Floyd" + INDEX 00 03:58:72 + INDEX 01 04:00:72 + TRACK 03 AUDIO + TITLE "Time" + PERFORMER "Pink Floyd" + INDEX 00 07:31:70 + INDEX 01 07:33:70)"; + +} + +TEST(TestCueDocument, LoadTracks) +{ + CCueDocument doc; + doc.ParseTag(input); + + using namespace std::string_literals; + + CFileItem item("The Dark Side Of The Moon.mp3", false); + auto& tag = *item.GetMusicInfoTag(); + tag.SetAlbum("TestAlbum"s); + tag.SetLoaded(true); + tag.SetAlbumArtist("TestAlbumArtist"s); + tag.SetGenre("TestGenre"s); + tag.SetArtist({"TestArtist1"s, "TestArtist2"s}); + tag.SetCueSheet("TestCueSheet"s); + tag.SetYear(2005); + tag.SetDuration(554); + + CFileItemList scannedItems; + doc.LoadTracks(scannedItems, item); + + ASSERT_EQ(scannedItems.Size(), 3U); + + static const auto trackNames = std::array{ + "Speak To Me / Breathe"s, + "On The Run"s, + "Time"s, + }; + static const auto duration = std::array{241, 213, 100}; + for (size_t i = 0; i < 3; ++i) + { + EXPECT_EQ(scannedItems[i]->GetPath(), "The Dark Side Of The Moon.mp3"); + ASSERT_TRUE(scannedItems[i]->GetMusicInfoTag() != nullptr); + EXPECT_EQ(scannedItems[i]->GetMusicInfoTag()->GetArtist(), std::vector{"Pink Floyd"s}); + EXPECT_EQ(scannedItems[i]->GetMusicInfoTag()->GetAlbumArtist(), std::vector{"Pink Floyd"s}); + EXPECT_EQ(scannedItems[i]->GetMusicInfoTag()->GetCueSheet(), "TestCueSheet"s); + EXPECT_EQ(scannedItems[i]->GetMusicInfoTag()->GetGenre(), std::vector{"TestGenre"s}); + EXPECT_EQ(scannedItems[i]->GetMusicInfoTag()->GetTitle(), trackNames[i]); + EXPECT_EQ(scannedItems[i]->GetMusicInfoTag()->GetTrackNumber(), i + 1); + EXPECT_EQ(scannedItems[i]->GetMusicInfoTag()->GetDuration(), duration[i]); + } +} From 97a6b4fce58250a048ffb81e510412dce69fe5af Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 25 Jun 2024 10:59:04 +0200 Subject: [PATCH 192/651] libUPnP: Fix memory allocation of size 0 In member function 'ReallocateBuffer', inlined from 'SetBufferSize' at ../../kodi-e495e26f477d4de8a7e6c2fac4acbe1a15e22242/.aarch64-libreelec-linux-gnu/../lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp:172:32, inlined from 'Load' at ../../kodi-e495e26f477d4de8a7e6c2fac4acbe1a15e22242/.aarch64-libreelec-linux-gnu/../lib/libUPnP/Neptune/Source/Core/NptStreams.cpp:106:33: ../../kodi-e495e26f477d4de8a7e6c2fac4acbe1a15e22242/.aarch64-libreelec-linux-gnu/../lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp:245:23: warning: '__builtin_memcpy' writing between 1 and 4294967295 bytes into a region of size 0 [-Wstringop-overflow=] ../../kodi-e495e26f477d4de8a7e6c2fac4acbe1a15e22242/.aarch64-libreelec-linux-gnu/../lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp:241:44: note: destination object of size 0 allocated by 'operator new []' --- .../Neptune/Source/Core/NptDataBuffer.cpp | 4 ++- ...UPnP-Fix-memory-allocation-of-size-0.patch | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 lib/libUPnP/patches/0054-libUPnP-Fix-memory-allocation-of-size-0.patch diff --git a/lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp b/lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp index f5ab03ced3a28..c0b1c80a3d218 100644 --- a/lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp +++ b/lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp @@ -237,7 +237,9 @@ NPT_DataBuffer::ReallocateBuffer(NPT_Size size) // check that the existing data fits if (m_DataSize > size) return NPT_ERROR_INVALID_PARAMETERS; - // allocate a new buffer + // allocate a new buffer only if size is not zero + if (!size) return NPT_ERROR_INVALID_PARAMETERS; + NPT_Byte* newBuffer = new NPT_Byte[size]; // copy the contents of the previous buffer, if any diff --git a/lib/libUPnP/patches/0054-libUPnP-Fix-memory-allocation-of-size-0.patch b/lib/libUPnP/patches/0054-libUPnP-Fix-memory-allocation-of-size-0.patch new file mode 100644 index 0000000000000..9146ac0161b66 --- /dev/null +++ b/lib/libUPnP/patches/0054-libUPnP-Fix-memory-allocation-of-size-0.patch @@ -0,0 +1,33 @@ +From 667922032fa22607697ca4b5eb81c1f8c96e2161 Mon Sep 17 00:00:00 2001 +From: Peter +Date: Tue, 25 Jun 2024 10:57:31 +0200 +Subject: [PATCH] libUPnP: Fix memory allocation of size 0 + +In member function 'ReallocateBuffer', + inlined from 'SetBufferSize' at ../../kodi-e495e26f477d4de8a7e6c2fac4acbe1a15e22242/.aarch64-libreelec-linux-gnu/../lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp:172:32, + inlined from 'Load' at ../../kodi-e495e26f477d4de8a7e6c2fac4acbe1a15e22242/.aarch64-libreelec-linux-gnu/../lib/libUPnP/Neptune/Source/Core/NptStreams.cpp:106:33: +../../kodi-e495e26f477d4de8a7e6c2fac4acbe1a15e22242/.aarch64-libreelec-linux-gnu/../lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp:245:23: warning: '__builtin_memcpy' writing between 1 and 4294967295 bytes into a region of size 0 [-Wstringop-overflow=] +../../kodi-e495e26f477d4de8a7e6c2fac4acbe1a15e22242/.aarch64-libreelec-linux-gnu/../lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp:241:44: note: destination object of size 0 allocated by 'operator new []' + +--- + lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp b/lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp +index f5ab03c..c0b1c80 100644 +--- a/lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp ++++ b/lib/libUPnP/Neptune/Source/Core/NptDataBuffer.cpp +@@ -237,7 +237,9 @@ NPT_DataBuffer::ReallocateBuffer(NPT_Size size) + // check that the existing data fits + if (m_DataSize > size) return NPT_ERROR_INVALID_PARAMETERS; + +- // allocate a new buffer ++ // allocate a new buffer only if size is not zero ++ if (!size) return NPT_ERROR_INVALID_PARAMETERS; ++ + NPT_Byte* newBuffer = new NPT_Byte[size]; + + // copy the contents of the previous buffer, if any +-- +2.41.0.dirty + From 4c793a36dfd84ed102422162f40aedf76b84fe5d Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 25 Jun 2024 14:22:01 +0200 Subject: [PATCH 193/651] Tuple: fix warning maybe-uninitialized In member function '__ct ', inlined from '__ct ' at ../xbmc/interfaces/legacy/Tuple.h:60:64, inlined from '__ct ' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_pair.h:882:35, inlined from 'construct' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/new_allocator.h:191:4, inlined from 'construct' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/alloc_traits.h:534:17, inlined from '_M_construct_node' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_tree.h:593:32, inlined from '_M_create_node' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_tree.h:610:21, inlined from '__ct ' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_tree.h:1633:32, inlined from '_M_emplace_hint_unique' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_tree.h:2458:13, inlined from 'emplace_hint' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_map.h:640:38, inlined from 'emplace' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_map.h:601:22, inlined from 'xbmc_XBMCAddon_xbmc_InfoTagVideo_setRatings' at build/swig/AddonModuleXbmc.i.cpp:13516:24: ../xbmc/interfaces/legacy/Tuple.h:42:54: warning: 'MEM[(float &)&value + 4]' may be used uninitialized [-Wmaybe-uninitialized] 42 | inline Tuple(const Tuple& o) : TupleBase(o), v1(o.v1) {} | ^ build/swig/AddonModuleXbmc.i.cpp: In function 'xbmc_XBMCAddon_xbmc_InfoTagVideo_setRatings': build/swig/AddonModuleXbmc.i.cpp:13486:27: note: 'MEM[(float &)&value + 4]' was declared here 13486 | Tuple value; | ^ In member function '__ct ', inlined from '__ct ' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_pair.h:882:35, inlined from 'construct' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/new_allocator.h:191:4, inlined from 'construct' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/alloc_traits.h:534:17, inlined from '_M_construct_node' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_tree.h:593:32, inlined from '_M_create_node' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_tree.h:610:21, inlined from '__ct ' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_tree.h:1633:32, inlined from '_M_emplace_hint_unique' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_tree.h:2458:13, inlined from 'emplace_hint' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_map.h:640:38, inlined from 'emplace' at /data/LibreELEC.tv/build.LibreELEC-Generic.x86_64-13.0-devel/toolchain/x86_64-libreelec-linux-gnu/include/c++/14.1.0/bits/stl_map.h:601:22, inlined from 'xbmc_XBMCAddon_xbmc_InfoTagVideo_setRatings' at build/swig/AddonModuleXbmc.i.cpp:13516:24: ../xbmc/interfaces/legacy/Tuple.h:60:57: warning: 'MEM[(int &)&value + 8]' may be used uninitialized [-Wmaybe-uninitialized] 60 | inline Tuple(const Tuple& o) : Tuple(o), v2(o.v2) {} | ^ build/swig/AddonModuleXbmc.i.cpp: In function 'xbmc_XBMCAddon_xbmc_InfoTagVideo_setRatings': build/swig/AddonModuleXbmc.i.cpp:13486:27: note: 'MEM[(int &)&value + 8]' was declared here 13486 | Tuple value; | --- xbmc/interfaces/legacy/Tuple.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/xbmc/interfaces/legacy/Tuple.h b/xbmc/interfaces/legacy/Tuple.h index 116f307b93b49..812855e0c0fac 100644 --- a/xbmc/interfaces/legacy/Tuple.h +++ b/xbmc/interfaces/legacy/Tuple.h @@ -35,7 +35,8 @@ namespace XBMCAddon template class Tuple : public TupleBase { private: - T1 v1; + T1 v1{}; + public: explicit inline Tuple(T1 p1) : TupleBase(1), v1(p1) {} inline Tuple() : TupleBase(0) {} @@ -49,8 +50,8 @@ namespace XBMCAddon // Tuple that holds two values template class Tuple : public Tuple { - protected: - T2 v2; + private: + T2 v2{}; public: inline Tuple(T1 p1, T2 p2) : Tuple(p1), v2(p2) { TupleBase::nvs(2); } @@ -67,7 +68,8 @@ namespace XBMCAddon template class Tuple : public Tuple { private: - T3 v3; + T3 v3{}; + public: inline Tuple(T1 p1, T2 p2, T3 p3) : Tuple(p1,p2), v3(p3) { TupleBase::nvs(3); } inline Tuple(T1 p1, T2 p2) : Tuple(p1,p2) {} @@ -83,7 +85,8 @@ namespace XBMCAddon template class Tuple : public Tuple { private: - T4 v4; + T4 v4{}; + public: inline Tuple(T1 p1, T2 p2, T3 p3, T4 p4) : Tuple(p1,p2,p3), v4(p4) { TupleBase::nvs(4); } inline Tuple(T1 p1, T2 p2, T3 p3) : Tuple(p1,p2,p3) {} From 3420eccbe1a5ca36024f0609f1c93d88b5889e7e Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sun, 23 Jun 2024 18:51:51 +0200 Subject: [PATCH 194/651] [Audio] PackerMAT: simplify code - remove non-essential code - Remove parsing code to obtain output timing. - Remove log of stream discontinuities. - Simplify parse of ratebits. - Remove track of MAT samples and samples offset. --- xbmc/cores/AudioEngine/Utils/PackerMAT.cpp | 122 ++------------------- xbmc/cores/AudioEngine/Utils/PackerMAT.h | 52 +-------- 2 files changed, 8 insertions(+), 166 deletions(-) diff --git a/xbmc/cores/AudioEngine/Utils/PackerMAT.cpp b/xbmc/cores/AudioEngine/Utils/PackerMAT.cpp index 22811b80dcb18..e6a5d621b3236 100644 --- a/xbmc/cores/AudioEngine/Utils/PackerMAT.cpp +++ b/xbmc/cores/AudioEngine/Utils/PackerMAT.cpp @@ -57,42 +57,24 @@ CPackerMAT::CPackerMAT() // high-bitrate streams can overshoot this size and therefor require proper handling of dynamic padding. bool CPackerMAT::PackTrueHD(const uint8_t* data, int size) { - TrueHDMajorSyncInfo info; + // discard too small packets (cannot be valid) + if (size < 10) + return false; - // get the ratebits and output timing from the sync frame + // get the ratebits from the major sync frame if (AV_RB32(data + 4) == FORMAT_MAJOR_SYNC) { - info = ParseTrueHDMajorSyncHeaders(data, size); - - if (!info.valid) - return false; - - m_state.ratebits = info.ratebits; + // read audio_sampling_frequency (high nibble after format major sync) + m_state.ratebits = data[8] >> 4; } - else if (m_state.prevFrametimeValid == false) + else if (!m_state.prevFrametimeValid) { // only start streaming on a major sync frame - m_state.numberOfSamplesOffset = 0; return false; } const uint16_t frameTime = AV_RB16(data + 2); uint32_t spaceSize = 0; - const uint16_t frameSamples = 40 << (m_state.ratebits & 7); - m_state.outputTiming += frameSamples; - - if (info.outputTimingPresent) - { - if (m_state.outputTimingValid && (info.outputTiming != m_state.outputTiming)) - { - CLog::Log(LOGWARNING, - "CPackerMAT::PackTrueHD: detected a stream discontinuity -> output timing " - "expected: {}, found: {}", - m_state.outputTiming, info.outputTiming); - } - m_state.outputTiming = info.outputTiming; - m_state.outputTimingValid = true; - } // compute final padded size for the previous frame, if any if (m_state.prevFrametimeValid) @@ -153,9 +135,6 @@ bool CPackerMAT::PackTrueHD(const uint8_t* data, int size) } } - // count the number of samples in this frame - m_state.samples += frameSamples; - // write actual audio data to the buffer int remaining = FillDataBuffer(data, size, Type::DATA); @@ -334,96 +313,9 @@ void CPackerMAT::FlushPacket() assert(GetCount() == MAT_BUFFER_SIZE); - // normal number of samples per frame - const uint16_t frameSamples = 40 << (m_state.ratebits & 7); - const uint32_t MATSamples = (frameSamples * 24); - // push MAT packet to output queue m_outputQueue.emplace_back(std::move(m_buffer)); - // we expect 24 frames per MAT frame, so calculate an offset from that - // this is done after delivery, because it modifies the duration of the frame, - // eg. the start of the next frame - if (MATSamples != m_state.samples) - m_state.numberOfSamplesOffset += m_state.samples - MATSamples; - - m_state.samples = 0; - m_buffer.clear(); m_bufferCount = 0; } - -TrueHDMajorSyncInfo CPackerMAT::ParseTrueHDMajorSyncHeaders(const uint8_t* p, int buffsize) const -{ - TrueHDMajorSyncInfo info; - - if (buffsize < 32) - return {}; - - // parse major sync and look for a restart header - int majorSyncSize = 28; - if (p[29] & 1) // restart header exists - { - int extensionSize = p[30] >> 4; // calculate headers size - majorSyncSize += 2 + extensionSize * 2; - } - - CBitStream bs(p + 4, buffsize - 4); - - bs.SkipBits(32); // format_sync - - info.ratebits = bs.ReadBits(4); // ratebits - info.valid = true; - - // (1) 6ch_multichannel_type - // (1) 8ch_multichannel_type - // (2) reserved - // (2) 2ch_presentation_channel_modifier - // (2) 6ch_presentation_channel_modifier - // (5) 6ch_presentation_channel_assignment - // (2) 8ch_presentation_channel_modifier - // (13) 8ch_presentation_channel_assignment - // (16) signature - // (16) flags - // (16) reserved - // (1) variable_rate - // (15) peak_data_rate - bs.SkipBits(1 + 1 + 2 + 2 + 2 + 5 + 2 + 13 + 16 + 16 + 16 + 1 + 15); - - const int numSubstreams = bs.ReadBits(4); - - bs.SkipBits(4 + (majorSyncSize - 17) * 8); - - // substream directory - for (int i = 0; i < numSubstreams; i++) - { - int extraSubstreamWord = bs.ReadBits(1); - // (1) restart_nonexistent - // (1) crc_present - // (1) reserved - // (12) substream_end_ptr - bs.SkipBits(15); - if (extraSubstreamWord) - bs.SkipBits(16); // drc_gain_update, drc_time_update, reserved - } - - // substream segments - for (int i = 0; i < numSubstreams; i++) - { - if (bs.ReadBits(1)) - { // block_header_exists - if (bs.ReadBits(1)) - { // restart_header_exists - bs.SkipBits(14); // restart_sync_word - info.outputTiming = bs.ReadBits(16); - info.outputTimingPresent = true; - // XXX: restart header - } - // XXX: Block header - } - // XXX: All blocks, all substreams? - break; - } - - return info; -} diff --git a/xbmc/cores/AudioEngine/Utils/PackerMAT.h b/xbmc/cores/AudioEngine/Utils/PackerMAT.h index 27cf1fcb64561..476f049487d3c 100644 --- a/xbmc/cores/AudioEngine/Utils/PackerMAT.h +++ b/xbmc/cores/AudioEngine/Utils/PackerMAT.h @@ -12,14 +12,6 @@ #include #include -struct TrueHDMajorSyncInfo -{ - int ratebits{0}; - uint16_t outputTiming{0}; - bool outputTimingPresent{false}; - bool valid{false}; -}; - enum class Type { PADDING, @@ -49,13 +41,7 @@ class CPackerMAT // 10 -> 176.4 kHz int ratebits; - // Output timing obtained parsing TrueHD major sync headers (when available) or - // inferred increasing a counter the rest of the time. - uint16_t outputTiming; - bool outputTimingValid; - - // Input timing of audio unit (obtained of each audio unit) and used to calculate padding - // bytes. On the contrary of outputTiming, frametime is present in all audio units. + // input timing of previous audio unit used to calculate padding bytes uint16_t prevFrametime; bool prevFrametimeValid; @@ -63,8 +49,6 @@ class CPackerMAT uint32_t prevMatFramesize; // size in bytes of previous MAT frame uint32_t padding; // padding bytes pending to write - uint32_t samples; // number of samples accumulated in current MAT frame - int numberOfSamplesOffset; // offset respect number of samples in a standard MAT frame (40 * 24) }; void WriteHeader(); @@ -73,7 +57,6 @@ class CPackerMAT uint32_t GetCount() const { return m_bufferCount; } int FillDataBuffer(const uint8_t* data, int size, Type type); void FlushPacket(); - TrueHDMajorSyncInfo ParseTrueHDMajorSyncHeaders(const uint8_t* p, int buffsize) const; MATState m_state{}; @@ -81,36 +64,3 @@ class CPackerMAT std::vector m_buffer; std::deque> m_outputQueue; }; - -class CBitStream -{ -public: - // opens an existing byte array as bitstream - CBitStream(const uint8_t* bytes, int _size) - { - data = bytes; - size = _size; - } - - // reads bits from bitstream - int ReadBits(int bits) - { - int dat = 0; - for (int i = index; i < index + bits; i++) - { - dat = dat * 2 + getbit(data[i / 8], i % 8); - } - index += bits; - return dat; - } - - // skip bits from bitstream - void SkipBits(int bits) { index += bits; } - -private: - uint8_t getbit(uint8_t x, int y) { return (x >> (7 - y)) & 1; } - - const uint8_t* data{nullptr}; - int size{0}; - int index{0}; -}; From b0f1f8816b950ecc529bb516e9e1345b23433fab Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Tue, 25 Jun 2024 21:11:10 +0200 Subject: [PATCH 195/651] fixed: need to check for playlists before music/video/pictures files the playlist extensions are in the music/video/pictures extension list --- xbmc/FileItem.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 69a23f7ee747d..0e7046cd76439 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -1253,6 +1253,10 @@ void CFileItem::FillInDefaultIcon() // PVR deleted recording SetArt("icon", "DefaultVideoDeleted.png"); } + else if (PLAYLIST::IsPlayList(*this) || PLAYLIST::IsSmartPlayList(*this)) + { + SetArt("icon", "DefaultPlaylist.png"); + } else if (MUSIC::IsAudio(*this)) { // audio @@ -1272,10 +1276,6 @@ void CFileItem::FillInDefaultIcon() // picture SetArt("icon", "DefaultPicture.png"); } - else if (PLAYLIST::IsPlayList(*this) || PLAYLIST::IsSmartPlayList(*this)) - { - SetArt("icon", "DefaultPlaylist.png"); - } else if ( IsPythonScript() ) { SetArt("icon", "DefaultScript.png"); From b30106b41c95d98e92be57e17bda3d7b9fc5c883 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 25 Jun 2024 18:45:35 +1000 Subject: [PATCH 196/651] [cmake][macros] Enable buildtools to function after target migration of core/optional deps --- cmake/scripts/common/Macros.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index fcb9659de9ca5..3eec6c8474c80 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -429,7 +429,7 @@ function(core_optional_dep) set(_required True) endif() - if(TARGET kodi::${dep}) + if(TARGET kodi::${dep} OR (${depup}_FOUND AND ${depspec} IN_LIST optional_buildtools)) set(final_message ${final_message} "${depup} enabled: Yes") # We dont want to add a build tool From bee2116d580b1db5760b4d79a0dd4531caa0a78f Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 26 Jun 2024 06:09:16 +1000 Subject: [PATCH 197/651] [cmake][macro] dont hardcode kodi --- cmake/scripts/common/Macros.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index 3eec6c8474c80..d9b1f590f209c 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -429,7 +429,7 @@ function(core_optional_dep) set(_required True) endif() - if(TARGET kodi::${dep} OR (${depup}_FOUND AND ${depspec} IN_LIST optional_buildtools)) + if(TARGET ${APP_NAME_LC}::${dep} OR (${depup}_FOUND AND ${depspec} IN_LIST optional_buildtools)) set(final_message ${final_message} "${depup} enabled: Yes") # We dont want to add a build tool From 9cb351848fcf60a688bf80878711ba86c20386ea Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 26 Jun 2024 06:12:51 +1000 Subject: [PATCH 198/651] [cmake][macro] core_optional_dep dont check required_buildtools --- cmake/scripts/common/Macros.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index d9b1f590f209c..e3ae5ed9b17cf 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -433,7 +433,7 @@ function(core_optional_dep) set(final_message ${final_message} "${depup} enabled: Yes") # We dont want to add a build tool - if (NOT ${depspec} IN_LIST optional_buildtools AND NOT ${depspec} IN_LIST required_buildtools) + if (NOT ${depspec} IN_LIST optional_buildtools) # If dependency is found and is not in the list (eg mariadb/mysql) add to list if (NOT ${depspec} IN_LIST optional_deps) set(optional_deps ${optional_deps} ${depspec} PARENT_SCOPE) From 0bddf4965a1c0e70365c7715c4cefc7d00217f5b Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 26 Jun 2024 06:26:18 +1000 Subject: [PATCH 199/651] [cmake] Silence policy warning CMP0074 https://cmake.org/cmake/help/latest/policy/CMP0074.html --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68abd45abf5e9..f6ba1e9097482 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,13 @@ if(POLICY CMP0069) cmake_policy(SET CMP0069 NEW) endif() +# https://cmake.org/cmake/help/latest/policy/CMP0074.html +# find_package() uses _ROOT variables +if(POLICY CMP0074) + set(CMAKE_POLICY_DEFAULT_CMP0074 NEW) + cmake_policy(SET CMP0074 NEW) +endif() + if(POLICY CMP0079) set(CMAKE_POLICY_DEFAULT_CMP0079 NEW) cmake_policy(SET CMP0079 NEW) From f4fafc5bf2d2a1c5db04ed2253b392a61741bf0b Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 26 Jun 2024 06:27:16 +1000 Subject: [PATCH 200/651] [cmake][module] FindCurl minor ordering alphabetisation --- cmake/modules/FindCurl.cmake | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cmake/modules/FindCurl.cmake b/cmake/modules/FindCurl.cmake index 7870b11031739..a26682395c853 100644 --- a/cmake/modules/FindCurl.cmake +++ b/cmake/modules/FindCurl.cmake @@ -11,10 +11,9 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) include(cmake/scripts/common/ModuleHelpers.cmake) macro(buildCurl) - + find_package(Brotli REQUIRED QUIET) find_package(NGHttp2 REQUIRED QUIET) find_package(OpenSSL REQUIRED QUIET) - find_package(Brotli REQUIRED QUIET) # Darwin platforms link against toolchain provided zlib regardless # They will fail when searching for static. All other platforms, prefer static @@ -23,7 +22,7 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) if(NOT CMAKE_SYSTEM_NAME MATCHES "Darwin" AND NOT (WIN32 OR WINDOWS_STORE)) set(ZLIB_USE_STATIC_LIBS ON) endif() - find_package(ZLIB REQUIRED) + find_package(Zlib REQUIRED) unset(ZLIB_USE_STATIC_LIBS) set(CURL_VERSION ${${MODULE}_VER}) @@ -61,14 +60,14 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) BUILD_DEP_TARGET() # Link libraries for target interface - set(PC_CURL_LINK_LIBRARIES Brotli::Brotli OpenSSL::Crypto OpenSSL::SSL NGHttp2::NGHttp2 ZLIB::ZLIB ${PLATFORM_LINK_LIBS}) + set(PC_CURL_LINK_LIBRARIES Brotli::Brotli NGHttp2::NGHttp2 OpenSSL::Crypto OpenSSL::SSL ZLIB::ZLIB ${PLATFORM_LINK_LIBS}) # Add dependencies to build target + add_dependencies(${MODULE_LC} Brotli::Brotli) add_dependencies(${MODULE_LC} NGHttp2::NGHttp2) add_dependencies(${MODULE_LC} OpenSSL::SSL) add_dependencies(${MODULE_LC} OpenSSL::Crypto) add_dependencies(${MODULE_LC} ZLIB::ZLIB) - add_dependencies(${MODULE_LC} Brotli::Brotli) endmacro() set(MODULE_LC curl) From 5506f4fccb00bc3bb87f84d4657d4e22056c52c6 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 25 Jun 2024 00:06:28 +0200 Subject: [PATCH 201/651] [PVR] Cleanup: clang-format GUIDialogPVRGroupManager.(cpp|h). --- xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp | 58 ++++++----- xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h | 98 +++++++++---------- 2 files changed, 77 insertions(+), 79 deletions(-) diff --git a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp index 1a5f33644b9f2..b5b9da79c054c 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp @@ -42,19 +42,19 @@ using namespace KODI::MESSAGING; using namespace PVR; -#define CONTROL_LIST_CHANNELS_LEFT 11 -#define CONTROL_LIST_CHANNELS_RIGHT 12 -#define CONTROL_LIST_CHANNEL_GROUPS 13 -#define CONTROL_CURRENT_GROUP_LABEL 20 -#define CONTROL_UNGROUPED_LABEL 21 -#define CONTROL_IN_GROUP_LABEL 22 -#define BUTTON_HIDE_GROUP 25 -#define BUTTON_NEWGROUP 26 -#define BUTTON_RENAMEGROUP 27 -#define BUTTON_DELGROUP 28 -#define BUTTON_OK 29 -#define BUTTON_TOGGLE_RADIO_TV 34 -#define BUTTON_RECREATE_GROUP_THUMB 35 +#define CONTROL_LIST_CHANNELS_LEFT 11 +#define CONTROL_LIST_CHANNELS_RIGHT 12 +#define CONTROL_LIST_CHANNEL_GROUPS 13 +#define CONTROL_CURRENT_GROUP_LABEL 20 +#define CONTROL_UNGROUPED_LABEL 21 +#define CONTROL_IN_GROUP_LABEL 22 +#define BUTTON_HIDE_GROUP 25 +#define BUTTON_NEWGROUP 26 +#define BUTTON_RENAMEGROUP 27 +#define BUTTON_DELGROUP 28 +#define BUTTON_OK 29 +#define BUTTON_TOGGLE_RADIO_TV 34 +#define BUTTON_RECREATE_GROUP_THUMB 35 namespace { @@ -62,8 +62,8 @@ constexpr const char* PROPERTY_CLIENT_NAME = "ClientName"; } // namespace -CGUIDialogPVRGroupManager::CGUIDialogPVRGroupManager() : - CGUIDialog(WINDOW_DIALOG_PVR_GROUP_MANAGER, "DialogPVRGroupManager.xml") +CGUIDialogPVRGroupManager::CGUIDialogPVRGroupManager() + : CGUIDialog(WINDOW_DIALOG_PVR_GROUP_MANAGER, "DialogPVRGroupManager.xml") { m_ungroupedChannels = new CFileItemList; m_groupMembers = new CFileItemList; @@ -160,7 +160,8 @@ bool CGUIDialogPVRGroupManager::ActionButtonNewGroup(const CGUIMessage& message) if (iControl == BUTTON_NEWGROUP) { std::string strGroupName; - if (CGUIKeyboardFactory::ShowAndGetInput(strGroupName, CVariant{g_localizeStrings.Get(19139)}, false)) + if (CGUIKeyboardFactory::ShowAndGetInput(strGroupName, CVariant{g_localizeStrings.Get(19139)}, + false)) { if (!strGroupName.empty()) { @@ -191,7 +192,9 @@ bool CGUIDialogPVRGroupManager::ActionButtonDeleteGroup(const CGUIMessage& messa if (!m_selectedGroup) return bReturn; - CGUIDialogYesNo* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_YES_NO); + CGUIDialogYesNo* pDialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow( + WINDOW_DIALOG_YES_NO); if (!pDialog) return bReturn; @@ -371,7 +374,8 @@ bool CGUIDialogPVRGroupManager::ActionButtonHideGroup(const CGUIMessage& message if (message.GetSenderId() == BUTTON_HIDE_GROUP && m_selectedGroup) { - CGUIRadioButtonControl* button = static_cast(GetControl(message.GetSenderId())); + CGUIRadioButtonControl* button = + static_cast(GetControl(message.GetSenderId())); if (button) { CServiceBroker::GetPVRManager() @@ -418,16 +422,11 @@ bool CGUIDialogPVRGroupManager::ActionButtonRecreateThumbnail(const CGUIMessage& bool CGUIDialogPVRGroupManager::OnMessageClick(const CGUIMessage& message) { - return ActionButtonOk(message) || - ActionButtonNewGroup(message) || - ActionButtonDeleteGroup(message) || - ActionButtonRenameGroup(message) || - ActionButtonUngroupedChannels(message) || - ActionButtonGroupMembers(message) || - ActionButtonChannelGroups(message) || - ActionButtonHideGroup(message) || - ActionButtonToggleRadioTV(message) || - ActionButtonRecreateThumbnail(message); + return ActionButtonOk(message) || ActionButtonNewGroup(message) || + ActionButtonDeleteGroup(message) || ActionButtonRenameGroup(message) || + ActionButtonUngroupedChannels(message) || ActionButtonGroupMembers(message) || + ActionButtonChannelGroups(message) || ActionButtonHideGroup(message) || + ActionButtonToggleRadioTV(message) || ActionButtonRecreateThumbnail(message); } bool CGUIDialogPVRGroupManager::OnMessage(CGUIMessage& message) @@ -519,8 +518,7 @@ bool CGUIDialogPVRGroupManager::OnActionMove(const CAction& action) bool CGUIDialogPVRGroupManager::OnAction(const CAction& action) { - return OnActionMove(action) || - CGUIDialog::OnAction(action); + return OnActionMove(action) || CGUIDialog::OnAction(action); } void CGUIDialogPVRGroupManager::OnInitWindow() diff --git a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h index 3c5716635c9c3..2c8ef4fc820ef 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h +++ b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h @@ -19,61 +19,61 @@ class CGUIMessage; namespace PVR { - class CPVRChannelGroup; +class CPVRChannelGroup; - class CGUIDialogPVRGroupManager : public CGUIDialog - { - public: - CGUIDialogPVRGroupManager(); - ~CGUIDialogPVRGroupManager() override; - bool OnMessage(CGUIMessage& message) override; - bool OnAction(const CAction& action) override; - void OnWindowLoaded() override; - void OnWindowUnload() override; +class CGUIDialogPVRGroupManager : public CGUIDialog +{ +public: + CGUIDialogPVRGroupManager(); + ~CGUIDialogPVRGroupManager() override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction& action) override; + void OnWindowLoaded() override; + void OnWindowUnload() override; - void SetRadio(bool bIsRadio); + void SetRadio(bool bIsRadio); - protected: - void OnInitWindow() override; - void OnDeinitWindow(int nextWindowID) override; +protected: + void OnInitWindow() override; + void OnDeinitWindow(int nextWindowID) override; - private: - void Clear(); - void ClearSelectedGroupsThumbnail(); - void Update(); - bool PersistChanges(); - bool ActionButtonOk(const CGUIMessage& message); - bool ActionButtonNewGroup(const CGUIMessage& message); - bool ActionButtonDeleteGroup(const CGUIMessage& message); - bool ActionButtonRenameGroup(const CGUIMessage& message); - bool ActionButtonUngroupedChannels(const CGUIMessage& message); - bool ActionButtonGroupMembers(const CGUIMessage& message); - bool ActionButtonChannelGroups(const CGUIMessage& message); - bool ActionButtonHideGroup(const CGUIMessage& message); - bool ActionButtonToggleRadioTV(const CGUIMessage& message); - bool ActionButtonRecreateThumbnail(const CGUIMessage& message); - bool OnMessageClick(const CGUIMessage& message); - bool OnPopupMenu(int itemNumber); - bool OnContextButton(int itemNumber, int button); - bool OnActionMove(const CAction& action); +private: + void Clear(); + void ClearSelectedGroupsThumbnail(); + void Update(); + bool PersistChanges(); + bool ActionButtonOk(const CGUIMessage& message); + bool ActionButtonNewGroup(const CGUIMessage& message); + bool ActionButtonDeleteGroup(const CGUIMessage& message); + bool ActionButtonRenameGroup(const CGUIMessage& message); + bool ActionButtonUngroupedChannels(const CGUIMessage& message); + bool ActionButtonGroupMembers(const CGUIMessage& message); + bool ActionButtonChannelGroups(const CGUIMessage& message); + bool ActionButtonHideGroup(const CGUIMessage& message); + bool ActionButtonToggleRadioTV(const CGUIMessage& message); + bool ActionButtonRecreateThumbnail(const CGUIMessage& message); + bool OnMessageClick(const CGUIMessage& message); + bool OnPopupMenu(int itemNumber); + bool OnContextButton(int itemNumber, int button); + bool OnActionMove(const CAction& action); - std::shared_ptr m_selectedGroup; - bool m_bIsRadio; - bool m_movingItem{false}; - bool m_allowReorder{false}; + std::shared_ptr m_selectedGroup; + bool m_bIsRadio; + bool m_movingItem{false}; + bool m_allowReorder{false}; - int m_iSelectedUngroupedChannel = 0; - int m_iSelectedGroupMember = 0; - int m_iSelectedChannelGroup = 0; + int m_iSelectedUngroupedChannel = 0; + int m_iSelectedGroupMember = 0; + int m_iSelectedChannelGroup = 0; - CFileItemList * m_ungroupedChannels; - CFileItemList * m_groupMembers; - CFileItemList * m_channelGroups; + CFileItemList* m_ungroupedChannels; + CFileItemList* m_groupMembers; + CFileItemList* m_channelGroups; - CGUIViewControl m_viewUngroupedChannels; - CGUIViewControl m_viewGroupMembers; - CGUIViewControl m_viewChannelGroups; + CGUIViewControl m_viewUngroupedChannels; + CGUIViewControl m_viewGroupMembers; + CGUIViewControl m_viewChannelGroups; - CPVRThumbLoader m_thumbLoader; - }; -} + CPVRThumbLoader m_thumbLoader; +}; +} // namespace PVR From 5ed5e7d5f3af714952e93b0434edeca7b79b9e63 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 25 Jun 2024 00:07:01 +0200 Subject: [PATCH 202/651] [PVR] Cleanup: clang-format GUIDialogPVRChannelManager.(cpp|h). --- .../dialogs/GUIDialogPVRChannelManager.cpp | 179 ++++++++++-------- xbmc/pvr/dialogs/GUIDialogPVRChannelManager.h | 146 +++++++------- 2 files changed, 178 insertions(+), 147 deletions(-) diff --git a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp index 8bc352d0ff51d..f60ac12f23e60 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp @@ -48,20 +48,20 @@ #include #include -#define BUTTON_OK 4 -#define BUTTON_APPLY 5 -#define BUTTON_CANCEL 6 -#define RADIOBUTTON_ACTIVE 7 -#define EDIT_NAME 8 -#define BUTTON_CHANNEL_LOGO 9 -#define IMAGE_CHANNEL_LOGO 10 -#define RADIOBUTTON_USEEPG 12 -#define SPIN_EPGSOURCE_SELECTION 13 +#define BUTTON_OK 4 +#define BUTTON_APPLY 5 +#define BUTTON_CANCEL 6 +#define RADIOBUTTON_ACTIVE 7 +#define EDIT_NAME 8 +#define BUTTON_CHANNEL_LOGO 9 +#define IMAGE_CHANNEL_LOGO 10 +#define RADIOBUTTON_USEEPG 12 +#define SPIN_EPGSOURCE_SELECTION 13 #define RADIOBUTTON_PARENTAL_LOCK 14 -#define CONTROL_LIST_CHANNELS 20 -#define BUTTON_GROUP_MANAGER 30 -#define BUTTON_NEW_CHANNEL 31 -#define BUTTON_RADIO_TV 34 +#define CONTROL_LIST_CHANNELS 20 +#define BUTTON_GROUP_MANAGER 30 +#define BUTTON_NEW_CHANNEL 31 +#define BUTTON_RADIO_TV 34 #define BUTTON_REFRESH_LOGOS 35 namespace @@ -88,8 +88,8 @@ constexpr const char* PROPERTY_ITEM_CHANGED = "Changed"; using namespace PVR; using namespace KODI::MESSAGING; -CGUIDialogPVRChannelManager::CGUIDialogPVRChannelManager() : - CGUIDialog(WINDOW_DIALOG_PVR_CHANNEL_MANAGER, "DialogPVRChannelManager.xml"), +CGUIDialogPVRChannelManager::CGUIDialogPVRChannelManager() + : CGUIDialog(WINDOW_DIALOG_PVR_CHANNEL_MANAGER, "DialogPVRChannelManager.xml"), m_channelItems(new CFileItemList) { SetRadio(false); @@ -142,9 +142,10 @@ bool CGUIDialogPVRChannelManager::OnActionMove(const CAction& action) } else { - bool bMoveUp = iActionId == ACTION_PAGE_UP || iActionId == ACTION_MOVE_UP || iActionId == ACTION_FIRST_PAGE; + bool bMoveUp = iActionId == ACTION_PAGE_UP || iActionId == ACTION_MOVE_UP || + iActionId == ACTION_FIRST_PAGE; unsigned int iLines = bMoveUp ? abs(m_iSelected - iSelected) : 1; - bool bOutOfBounds = bMoveUp ? m_iSelected <= 0 : m_iSelected >= m_channelItems->Size() - 1; + bool bOutOfBounds = bMoveUp ? m_iSelected <= 0 : m_iSelected >= m_channelItems->Size() - 1; if (bOutOfBounds) { bMoveUp = !bMoveUp; @@ -183,8 +184,7 @@ bool CGUIDialogPVRChannelManager::OnActionMove(const CAction& action) bool CGUIDialogPVRChannelManager::OnAction(const CAction& action) { - return OnActionMove(action) || - CGUIDialog::OnAction(action); + return OnActionMove(action) || CGUIDialog::OnAction(action); } void CGUIDialogPVRChannelManager::OnInitWindow() @@ -251,10 +251,12 @@ bool CGUIDialogPVRChannelManager::OnClickListChannels(const CGUIMessage& message int iItem = m_viewControl.GetSelectedItem(); /* Check file item is in list range and get his pointer */ - if (iItem < 0 || iItem >= m_channelItems->Size()) return true; + if (iItem < 0 || iItem >= m_channelItems->Size()) + return true; /* Process actions */ - if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK) + if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_CONTEXT_MENU || + iAction == ACTION_MOUSE_RIGHT_CLICK) { /* Show Contextmenu */ OnPopupMenu(iItem); @@ -391,9 +393,11 @@ bool CGUIDialogPVRChannelManager::OnClickButtonChannelLogo() if (!pItem) return false; - const std::shared_ptr profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + const std::shared_ptr profileManager = + CServiceBroker::GetSettingsComponent()->GetProfileManager(); - if (profileManager->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked()) + if (profileManager->GetCurrentProfile().canWriteSources() && + !g_passwordManager.IsProfileLockUnlocked()) return false; // setup our thumb list @@ -432,7 +436,8 @@ bool CGUIDialogPVRChannelManager::OnClickButtonChannelLogo() shares.push_back(share1); } CServiceBroker::GetMediaManager().GetLocalDrives(shares); - if (!CGUIDialogFileBrowser::ShowAndGetImage(items, shares, g_localizeStrings.Get(19285), strThumb, NULL, 19285)) + if (!CGUIDialogFileBrowser::ShowAndGetImage(items, shares, g_localizeStrings.Get(19285), strThumb, + NULL, 19285)) return false; if (strThumb == "thumb://Current") @@ -497,7 +502,9 @@ bool CGUIDialogPVRChannelManager::OnClickButtonGroupManager() PromptAndSaveList(); /* Load group manager dialog */ - CGUIDialogPVRGroupManager* pDlgInfo = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_PVR_GROUP_MANAGER); + CGUIDialogPVRGroupManager* pDlgInfo = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow( + WINDOW_DIALOG_PVR_GROUP_MANAGER); if (!pDlgInfo) return false; @@ -517,7 +524,9 @@ bool CGUIDialogPVRChannelManager::OnClickButtonNewChannel() int iSelection = 0; if (m_clientsWithSettingsList.size() > 1) { - CGUIDialogSelect* pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT); + CGUIDialogSelect* pDlgSelect = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow( + WINDOW_DIALOG_SELECT); if (!pDlgSelect) return false; @@ -565,9 +574,13 @@ bool CGUIDialogPVRChannelManager::OnClickButtonNewChannel() } } else if (ret == PVR_ERROR_NOT_IMPLEMENTED) - HELPERS::ShowOKDialogText(CVariant{19033}, CVariant{19038}); // "Information", "Not supported by the PVR backend." + HELPERS::ShowOKDialogText( + CVariant{19033}, CVariant{19038}); // "Information", "Not supported by the PVR backend." else - HELPERS::ShowOKDialogText(CVariant{2103}, CVariant{16029}); // "Add-on error", "Check the log for more information about this message." + HELPERS::ShowOKDialogText( + CVariant{2103}, + CVariant{ + 16029}); // "Add-on error", "Check the log for more information about this message." } return true; } @@ -597,38 +610,38 @@ bool CGUIDialogPVRChannelManager::OnClickButtonRefreshChannelLogos() bool CGUIDialogPVRChannelManager::OnMessageClick(const CGUIMessage& message) { int iControl = message.GetSenderId(); - switch(iControl) + switch (iControl) { - case CONTROL_LIST_CHANNELS: - return OnClickListChannels(message); - case BUTTON_OK: - return OnClickButtonOK(); - case BUTTON_APPLY: - return OnClickButtonApply(); - case BUTTON_CANCEL: - return OnClickButtonCancel(); - case BUTTON_RADIO_TV: - return OnClickButtonRadioTV(); - case RADIOBUTTON_ACTIVE: - return OnClickButtonRadioActive(); - case RADIOBUTTON_PARENTAL_LOCK: - return OnClickButtonRadioParentalLocked(); - case EDIT_NAME: - return OnClickButtonEditName(); - case BUTTON_CHANNEL_LOGO: - return OnClickButtonChannelLogo(); - case RADIOBUTTON_USEEPG: - return OnClickButtonUseEPG(); - case SPIN_EPGSOURCE_SELECTION: - return OnClickEPGSourceSpin(); - case BUTTON_GROUP_MANAGER: - return OnClickButtonGroupManager(); - case BUTTON_NEW_CHANNEL: - return OnClickButtonNewChannel(); - case BUTTON_REFRESH_LOGOS: - return OnClickButtonRefreshChannelLogos(); - default: - return false; + case CONTROL_LIST_CHANNELS: + return OnClickListChannels(message); + case BUTTON_OK: + return OnClickButtonOK(); + case BUTTON_APPLY: + return OnClickButtonApply(); + case BUTTON_CANCEL: + return OnClickButtonCancel(); + case BUTTON_RADIO_TV: + return OnClickButtonRadioTV(); + case RADIOBUTTON_ACTIVE: + return OnClickButtonRadioActive(); + case RADIOBUTTON_PARENTAL_LOCK: + return OnClickButtonRadioParentalLocked(); + case EDIT_NAME: + return OnClickButtonEditName(); + case BUTTON_CHANNEL_LOGO: + return OnClickButtonChannelLogo(); + case RADIOBUTTON_USEEPG: + return OnClickButtonUseEPG(); + case SPIN_EPGSOURCE_SELECTION: + return OnClickEPGSourceSpin(); + case BUTTON_GROUP_MANAGER: + return OnClickButtonGroupManager(); + case BUTTON_NEW_CHANNEL: + return OnClickButtonNewChannel(); + case BUTTON_REFRESH_LOGOS: + return OnClickButtonRefreshChannelLogos(); + default: + return false; } } @@ -706,7 +719,8 @@ bool CGUIDialogPVRChannelManager::OnPopupMenu(int iItem) bool CGUIDialogPVRChannelManager::OnContextButton(int itemNumber, CONTEXT_BUTTON button) { /* Check file item is in list range and get his pointer */ - if (itemNumber < 0 || itemNumber >= m_channelItems->Size()) return false; + if (itemNumber < 0 || itemNumber >= m_channelItems->Size()) + return false; CFileItemPtr pItem = m_channelItems->Get(itemNumber); if (!pItem) @@ -732,13 +746,19 @@ bool CGUIDialogPVRChannelManager::OnContextButton(int itemNumber, CONTEXT_BUTTON SetData(m_iSelected); } else if (ret == PVR_ERROR_NOT_IMPLEMENTED) - HELPERS::ShowOKDialogText(CVariant{19033}, CVariant{19038}); // "Information", "Not supported by the PVR backend." + HELPERS::ShowOKDialogText( + CVariant{19033}, CVariant{19038}); // "Information", "Not supported by the PVR backend." else - HELPERS::ShowOKDialogText(CVariant{2103}, CVariant{16029}); // "Add-on error", "Check the log for more information about this message." + HELPERS::ShowOKDialogText( + CVariant{2103}, + CVariant{ + 16029}); // "Add-on error", "Check the log for more information about this message." } else if (button == CONTEXT_BUTTON_DELETE) { - CGUIDialogYesNo* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_YES_NO); + CGUIDialogYesNo* pDialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow( + WINDOW_DIALOG_YES_NO); if (!pDialog) return true; @@ -764,9 +784,14 @@ bool CGUIDialogPVRChannelManager::OnContextButton(int itemNumber, CONTEXT_BUTTON } } else if (ret == PVR_ERROR_NOT_IMPLEMENTED) - HELPERS::ShowOKDialogText(CVariant{19033}, CVariant{19038}); // "Information", "Not supported by the PVR backend." + HELPERS::ShowOKDialogText( + CVariant{19033}, + CVariant{19038}); // "Information", "Not supported by the PVR backend." else - HELPERS::ShowOKDialogText(CVariant{2103}, CVariant{16029}); // "Add-on error", "Check the log for more information about this message." + HELPERS::ShowOKDialogText( + CVariant{2103}, + CVariant{ + 16029}); // "Add-on error", "Check the log for more information about this message." } } } @@ -807,7 +832,8 @@ void CGUIDialogPVRChannelManager::Update() // empty the lists ready for population Clear(); - std::shared_ptr channels = CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(m_bIsRadio); + std::shared_ptr channels = + CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(m_bIsRadio); // No channels available, nothing to do. if (!channels) @@ -849,13 +875,14 @@ void CGUIDialogPVRChannelManager::Update() } { - std::vector< std::pair > labels; + std::vector> labels; labels.emplace_back(g_localizeStrings.Get(19210), 0); //! @todo Add Labels for EPG scrapers here SET_CONTROL_LABELS(SPIN_EPGSOURCE_SELECTION, 0, &labels); } - m_clientsWithSettingsList = CServiceBroker::GetPVRManager().Clients()->GetClientsSupportingChannelSettings(m_bIsRadio); + m_clientsWithSettingsList = + CServiceBroker::GetPVRManager().Clients()->GetClientsSupportingChannelSettings(m_bIsRadio); if (!m_clientsWithSettingsList.empty()) m_bAllowNewChannel = true; @@ -930,7 +957,8 @@ void CGUIDialogPVRChannelManager::RenameChannel(const CFileItemPtr& pItem) const std::shared_ptr client = CServiceBroker::GetPVRManager().GetClient(*pItem); if (!client || (client->RenameChannel(channel) != PVR_ERROR_NO_ERROR)) - HELPERS::ShowOKDialogText(CVariant{2103}, CVariant{16029}); // Add-on error;Check the log file for details. + HELPERS::ShowOKDialogText(CVariant{2103}, + CVariant{16029}); // Add-on error;Check the log file for details. } } @@ -1006,7 +1034,9 @@ void CGUIDialogPVRChannelManager::SaveList() return; /* display the progress dialog */ - CGUIDialogProgress* pDlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_PROGRESS); + CGUIDialogProgress* pDlgProgress = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow( + WINDOW_DIALOG_PROGRESS); pDlgProgress->SetHeading(CVariant{190}); pDlgProgress->SetLine(0, CVariant{""}); pDlgProgress->SetLine(1, CVariant{328}); @@ -1016,7 +1046,8 @@ void CGUIDialogPVRChannelManager::SaveList() pDlgProgress->SetPercentage(0); /* persist all channels */ - std::shared_ptr group = CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(m_bIsRadio); + std::shared_ptr group = + CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(m_bIsRadio); if (!group) return; @@ -1060,9 +1091,9 @@ void CGUIDialogPVRChannelManager::SaveList() bool CGUIDialogPVRChannelManager::HasChangedItems() const { - return std::any_of(m_channelItems->cbegin(), m_channelItems->cend(), [](const auto& item) { - return item && item->GetProperty(PROPERTY_ITEM_CHANGED).asBoolean(); - }); + return std::any_of(m_channelItems->cbegin(), m_channelItems->cend(), + [](const auto& item) + { return item && item->GetProperty(PROPERTY_ITEM_CHANGED).asBoolean(); }); } namespace diff --git a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.h b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.h index 7dfc88a236617..c1fd416a6df85 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.h +++ b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.h @@ -21,76 +21,76 @@ class CGUIMessage; namespace PVR { - class CPVRChannelGroup; - class CPVRClient; - - class CGUIDialogPVRChannelManager : public CGUIDialog - { - public: - CGUIDialogPVRChannelManager(); - ~CGUIDialogPVRChannelManager() override; - bool OnMessage(CGUIMessage& message) override; - bool OnAction(const CAction& action) override; - void OnWindowLoaded() override; - void OnWindowUnload() override; - bool HasListItems() const override{ return true; } - CFileItemPtr GetCurrentListItem(int offset = 0) override; - - void Open(const std::shared_ptr& initialSelection); - void SetRadio(bool bIsRadio); - - protected: - void OnInitWindow() override; - void OnDeinitWindow(int nextWindowID) override; - - private: - void Clear(); - void Update(); - void PromptAndSaveList(); - void SaveList(); - void Renumber(); - void SetData(int iItem); - void RenameChannel(const CFileItemPtr& pItem); - - void ClearChannelOptions(); - void EnableChannelOptions(bool bEnable); - - bool OnPopupMenu(int iItem); - bool OnContextButton(int itemNumber, CONTEXT_BUTTON button); - bool OnActionMove(const CAction& action); - bool OnMessageClick(const CGUIMessage& message); - bool OnClickListChannels(const CGUIMessage& message); - bool OnClickButtonOK(); - bool OnClickButtonApply(); - bool OnClickButtonCancel(); - bool OnClickButtonRadioTV(); - bool OnClickButtonRadioActive(); - bool OnClickButtonRadioParentalLocked(); - bool OnClickButtonEditName(); - bool OnClickButtonChannelLogo(); - bool OnClickButtonUseEPG(); - bool OnClickEPGSourceSpin(); - bool OnClickButtonGroupManager(); - bool OnClickButtonNewChannel(); - bool OnClickButtonRefreshChannelLogos(); - - bool UpdateChannelData(const std::shared_ptr& pItem, - const std::shared_ptr& group); - - bool HasChangedItems() const; - void SetItemChanged(const CFileItemPtr& pItem); - - bool m_bIsRadio = false; - bool m_bMovingMode = false; - bool m_bAllowNewChannel = false; - bool m_bAllowRenumber = false; - bool m_bAllowReorder = false; - - std::shared_ptr m_initialSelection; - int m_iSelected = 0; - CFileItemList* m_channelItems; - CGUIViewControl m_viewControl; - - std::vector> m_clientsWithSettingsList; - }; -} +class CPVRChannelGroup; +class CPVRClient; + +class CGUIDialogPVRChannelManager : public CGUIDialog +{ +public: + CGUIDialogPVRChannelManager(); + ~CGUIDialogPVRChannelManager() override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction& action) override; + void OnWindowLoaded() override; + void OnWindowUnload() override; + bool HasListItems() const override { return true; } + CFileItemPtr GetCurrentListItem(int offset = 0) override; + + void Open(const std::shared_ptr& initialSelection); + void SetRadio(bool bIsRadio); + +protected: + void OnInitWindow() override; + void OnDeinitWindow(int nextWindowID) override; + +private: + void Clear(); + void Update(); + void PromptAndSaveList(); + void SaveList(); + void Renumber(); + void SetData(int iItem); + void RenameChannel(const CFileItemPtr& pItem); + + void ClearChannelOptions(); + void EnableChannelOptions(bool bEnable); + + bool OnPopupMenu(int iItem); + bool OnContextButton(int itemNumber, CONTEXT_BUTTON button); + bool OnActionMove(const CAction& action); + bool OnMessageClick(const CGUIMessage& message); + bool OnClickListChannels(const CGUIMessage& message); + bool OnClickButtonOK(); + bool OnClickButtonApply(); + bool OnClickButtonCancel(); + bool OnClickButtonRadioTV(); + bool OnClickButtonRadioActive(); + bool OnClickButtonRadioParentalLocked(); + bool OnClickButtonEditName(); + bool OnClickButtonChannelLogo(); + bool OnClickButtonUseEPG(); + bool OnClickEPGSourceSpin(); + bool OnClickButtonGroupManager(); + bool OnClickButtonNewChannel(); + bool OnClickButtonRefreshChannelLogos(); + + bool UpdateChannelData(const std::shared_ptr& pItem, + const std::shared_ptr& group); + + bool HasChangedItems() const; + void SetItemChanged(const CFileItemPtr& pItem); + + bool m_bIsRadio = false; + bool m_bMovingMode = false; + bool m_bAllowNewChannel = false; + bool m_bAllowRenumber = false; + bool m_bAllowReorder = false; + + std::shared_ptr m_initialSelection; + int m_iSelected = 0; + CFileItemList* m_channelItems; + CGUIViewControl m_viewControl; + + std::vector> m_clientsWithSettingsList; +}; +} // namespace PVR From c493b9b5a262d19b0b6771cc23fb249ed42ca0b5 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 16 Jun 2024 23:36:44 +0200 Subject: [PATCH 203/651] [PVR] Group Manager: Fix persistence issues. --- xbmc/pvr/channels/PVRChannelGroup.cpp | 20 ++- xbmc/pvr/channels/PVRChannelGroup.h | 22 ++- .../channels/PVRChannelGroupAllChannels.cpp | 5 +- xbmc/pvr/channels/PVRChannelGroups.cpp | 135 +++++++++++++++--- xbmc/pvr/channels/PVRChannelGroups.h | 50 ++++++- .../dialogs/GUIDialogPVRChannelManager.cpp | 10 +- xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp | 67 +++++---- xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h | 1 - xbmc/pvr/guilib/PVRGUIActionsChannels.cpp | 7 +- 9 files changed, 249 insertions(+), 68 deletions(-) diff --git a/xbmc/pvr/channels/PVRChannelGroup.cpp b/xbmc/pvr/channels/PVRChannelGroup.cpp index af4a04d29c695..cc4bd649fae91 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.cpp +++ b/xbmc/pvr/channels/PVRChannelGroup.cpp @@ -886,6 +886,11 @@ void CPVRChannelGroup::Delete() } } +void CPVRChannelGroup::DeleteGroupMember(const std::shared_ptr& member) +{ + DeleteGroupMembersFromDb({member}); +} + bool CPVRChannelGroup::Renumber(RenumberMode mode /* = NORMAL */) { bool bReturn(false); @@ -1022,7 +1027,7 @@ std::string CPVRChannelGroup::GroupName() const return m_path.GetGroupName(); } -void CPVRChannelGroup::SetGroupName(const std::string& strGroupName, +bool CPVRChannelGroup::SetGroupName(const std::string& strGroupName, bool isUserSetName /* = false */) { std::unique_lock lock(m_critSection); @@ -1038,11 +1043,10 @@ void CPVRChannelGroup::SetGroupName(const std::string& strGroupName, } if (m_bLoaded) - { m_bChanged = true; - Persist(); //! @todo why must we persist immediately? - } + return true; } + return false; } std::string CPVRChannelGroup::ClientGroupName() const @@ -1140,9 +1144,9 @@ bool CPVRChannelGroup::SetHidden(bool bHidden) m_bHidden = bHidden; if (m_bLoaded) m_bChanged = true; + return true; } - - return m_bChanged; + return false; } bool CPVRChannelGroup::IsHidden() const @@ -1157,7 +1161,7 @@ int CPVRChannelGroup::GetPosition() const return m_iPosition; } -void CPVRChannelGroup::SetPosition(int iPosition) +bool CPVRChannelGroup::SetPosition(int iPosition) { std::unique_lock lock(m_critSection); @@ -1166,7 +1170,9 @@ void CPVRChannelGroup::SetPosition(int iPosition) m_iPosition = iPosition; if (m_bLoaded) m_bChanged = true; + return true; } + return false; } int CPVRChannelGroup::GetClientPosition() const diff --git a/xbmc/pvr/channels/PVRChannelGroup.h b/xbmc/pvr/channels/PVRChannelGroup.h index d660f14ec00cf..4582a273a3c46 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.h +++ b/xbmc/pvr/channels/PVRChannelGroup.h @@ -156,8 +156,9 @@ class CPVRChannelGroup : public IChannelGroupSettingsCallback * @brief Change the name of this group. * @param strGroupName The new group name. * @param isUserSetName Whether the name was set by the user. + * @return True if the group name was changed, false otherwise. */ - void SetGroupName(const std::string& strGroupName, bool isUserSetName = false); + bool SetGroupName(const std::string& strGroupName, bool isUserSetName = false); /*! * @brief Set the name this group has on the client. @@ -381,7 +382,17 @@ class CPVRChannelGroup : public IChannelGroupSettingsCallback */ std::shared_ptr GetByUniqueID(const std::pair& id) const; + /*! + * @brief Set the hidden state of this group. + * @param bHidden True to set hidden state, false to unhide the group. + * @return True if hidden state was changed, false otherwise. + */ bool SetHidden(bool bHidden); + + /*! + * @brief Check whether this group is hidden. + * @return True if group is hidden, false otherwise. + */ bool IsHidden() const; /*! @@ -393,8 +404,9 @@ class CPVRChannelGroup : public IChannelGroupSettingsCallback /*! * @brief Set the local position of this group. * @param iPosition The new local group position. + * @return True if position has changed, false otherwise. */ - void SetPosition(int iPosition); + bool SetPosition(int iPosition); /*! * @brief Get the position of this group as supplied by the PVR client. @@ -435,6 +447,12 @@ class CPVRChannelGroup : public IChannelGroupSettingsCallback */ void Delete(); + /*! + * @brief Remove the given group member from the database. + * @param member The member to remove from the database. + */ + void DeleteGroupMember(const std::shared_ptr& member); + /*! * @brief Whether this group is deleted. * @return True, if deleted, false otherwise. diff --git a/xbmc/pvr/channels/PVRChannelGroupAllChannels.cpp b/xbmc/pvr/channels/PVRChannelGroupAllChannels.cpp index fceaf3f7dc242..708577c2d9482 100644 --- a/xbmc/pvr/channels/PVRChannelGroupAllChannels.cpp +++ b/xbmc/pvr/channels/PVRChannelGroupAllChannels.cpp @@ -49,7 +49,10 @@ void CPVRChannelGroupAllChannels::CheckGroupName() // Ensure the group name is still correct, or channels may fail to load after a locale change if (!IsUserSetName()) - SetGroupName(g_localizeStrings.Get(19287)); + { + if (SetGroupName(g_localizeStrings.Get(19287))) + Persist(); + } } bool CPVRChannelGroupAllChannels::UpdateFromClients( diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp index add597cf103e8..79a5283eb9749 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.cpp +++ b/xbmc/pvr/channels/PVRChannelGroups.cpp @@ -605,9 +605,23 @@ std::shared_ptr CPVRChannelGroups::GetNextGroup( return GetFirstGroup(); } +void CPVRChannelGroups::GroupStateChanged(const std::shared_ptr& group, + GroupState state /* = GroupState::CHANGED */) +{ + if (state == GroupState::DELETED) + { + if (group->GroupID() > 0) + group->Delete(); // delete the group from the database + } + else + group->Persist(); + + CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated); +} + std::shared_ptr CPVRChannelGroups::AddGroup(const std::string& strName) { - bool bPersist(false); + bool changed{false}; std::shared_ptr group; { @@ -615,22 +629,18 @@ std::shared_ptr CPVRChannelGroups::AddGroup(const std::string& // check if there's another local group with the same name already group = GetGroupByName(strName, PVR_GROUP_CLIENT_ID_LOCAL, Exclude::NONE); - if (!group) + if (!group || group->GetOrigin() != CPVRChannelGroup::Origin::USER) { // create a new local group group = GetGroupFactory()->CreateUserGroup(IsRadio(), strName, GetGroupAll()); m_groups.emplace_back(group); - SortGroups(); - bPersist = true; - - CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated); + changed = true; } } - // persist in the db if a new group was added - if (bPersist) - group->Persist(); + if (changed) + GroupStateChanged(group); return group; } @@ -643,7 +653,7 @@ bool CPVRChannelGroups::DeleteGroup(const std::shared_ptr& gro return false; } - bool bFound(false); + bool changed{false}; // delete the group in this container { @@ -653,35 +663,116 @@ bool CPVRChannelGroups::DeleteGroup(const std::shared_ptr& gro if (*it == group || (group->GroupID() > 0 && (*it)->GroupID() == group->GroupID())) { m_groups.erase(it); - bFound = true; + changed = true; break; } } } - if (bFound && group->GroupID() > 0) + if (changed) + GroupStateChanged(group, GroupState::DELETED); + + return changed; +} + +bool CPVRChannelGroups::HideGroup(const std::shared_ptr& group, bool bHide) +{ + if (group && group->SetHidden(bHide)) { - // delete the group from the database - group->Delete(); - CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated); + GroupStateChanged(group); + return true; } - return bFound; + return false; } -bool CPVRChannelGroups::HideGroup(const std::shared_ptr& group, bool bHide) +bool CPVRChannelGroups::SetGroupName(const std::shared_ptr& group, + const std::string& newGroupName, + bool isUserSetName) +{ + if (group && group->SetGroupName(newGroupName, isUserSetName)) + { + GroupStateChanged(group); + return true; + } + return false; +} + +bool CPVRChannelGroups::AppendToGroup( + const std::shared_ptr& group, + const std::shared_ptr& groupMember) { - bool bReturn = false; + if (group) + { + if (!group->SupportsMemberAdd()) + { + CLog::LogF(LOGERROR, "Channel group {} does not support adding members", group->GroupName()); + return false; + } + + if (group->AppendToGroup(groupMember)) + { + GroupStateChanged(group); + return true; + } + } + return false; +} +bool CPVRChannelGroups::RemoveFromGroup(const std::shared_ptr& group, + const std::shared_ptr& groupMember) +{ if (group) { - if (group->SetHidden(bHide)) + if (!group->SupportsMemberRemove()) + { + CLog::LogF(LOGERROR, "Channel group {} does not support removing members", + group->GroupName()); + return false; + } + + if (group->RemoveFromGroup(groupMember)) + { + group->DeleteGroupMember(groupMember); + GroupStateChanged(group); + return true; + } + } + return false; +} + +bool CPVRChannelGroups::ResetGroupPositions(const std::vector& sortedGroupPaths) +{ + static constexpr int START_POSITION{1}; + int pos{START_POSITION}; + + bool success{true}; + bool changed{false}; + + for (const auto& path : sortedGroupPaths) + { + const auto group{GetGroupByPath(path)}; + if (!group) + { + CLog::LogFC(LOGERROR, LOGPVR, "Unable to obtain group with path '{}‘, Skipping it.", path); + success = false; + continue; + } + + if (group->SetPosition(pos++)) { // state changed - CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated); + changed = true; } - bReturn = true; } - return bReturn; + + if (changed) + { + PersistAll(); + SortGroups(); + CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated); + } + + return success; } int CPVRChannelGroups::CleanupCachedImages() diff --git a/xbmc/pvr/channels/PVRChannelGroups.h b/xbmc/pvr/channels/PVRChannelGroups.h index b787c62d6fdfb..a3fc1fd754ea7 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.h +++ b/xbmc/pvr/channels/PVRChannelGroups.h @@ -191,6 +191,42 @@ class CPVRChannelGroups : public ISettingCallback */ bool HideGroup(const std::shared_ptr& group, bool bHide); + /*! + * @brief Change the name of the given group. + * @param group The group. + * @param newGroupName The new group name. + * @param isUserSetName Whether the name was set by the user. + * @return True if the group name was changed, false otherwise. + */ + bool SetGroupName(const std::shared_ptr& group, + const std::string& newGroupName, + bool isUserSetName); + + /*! + * @brief Append a channel group member to the given group. + * @param group The group. + * @param groupMember The channel group member to append. + * @return True if the channel group member was appended, false otherwise. + */ + bool AppendToGroup(const std::shared_ptr& group, + const std::shared_ptr& groupMember); + + /*! + * @brief Remove a channel group member from the given group. + * @param group The group. + * @param groupMember The channel group member to remove. + * @return @return True if the channel group member was removed, false otherwise. + */ + bool RemoveFromGroup(const std::shared_ptr& group, + const std::shared_ptr& groupMember); + + /*! + * @brief Reset the position of the given groups, then resort groups. + * @param sortedGroupPaths The paths of the groups to re-position. + * @return True if any group position was changed, false otherwise. + */ + bool ResetGroupPositions(const std::vector& sortedGroupPaths); + /*! * @brief Persist all changes in channel groups. * @return True if everything was persisted, false otherwise. @@ -223,12 +259,8 @@ class CPVRChannelGroups : public ISettingCallback */ int CleanupCachedImages(); - /*! - * @brief Sort the groups. - */ - void SortGroups(); - private: + void SortGroups(); void SortGroupsByBackendOrder(); void SortGroupsByLocalOrder(); @@ -254,6 +286,14 @@ class CPVRChannelGroups : public ISettingCallback Exclude exclude) const; std::shared_ptr GetGroupById(int groupId, Exclude exclude) const; + enum class GroupState + { + DELETED, + CHANGED, + }; + void GroupStateChanged(const std::shared_ptr& group, + GroupState state = GroupState::CHANGED); + bool m_bRadio{false}; std::vector> m_groups; mutable CCriticalSection m_critSection; diff --git a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp index f60ac12f23e60..b601c6398594c 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp @@ -979,12 +979,18 @@ bool CGUIDialogPVRChannelManager::UpdateChannelData(const std::shared_ptrAppendToGroup(groupMember); + CServiceBroker::GetPVRManager() + .ChannelGroups() + ->Get(m_bIsRadio) + ->AppendToGroup(group, groupMember); } else { // remove the hidden channel from the group - group->RemoveFromGroup(groupMember); + CServiceBroker::GetPVRManager() + .ChannelGroups() + ->Get(m_bIsRadio) + ->RemoveFromGroup(group, groupMember); } const auto channel = groupMember->Channel(); diff --git a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp index b5b9da79c054c..f04c7502aada9 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp @@ -85,11 +85,6 @@ void CGUIDialogPVRGroupManager::SetRadio(bool bIsRadio) SetProperty("IsRadio", m_bIsRadio ? "true" : ""); } -bool CGUIDialogPVRGroupManager::PersistChanges() -{ - return CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)->PersistAll(); -} - bool CGUIDialogPVRGroupManager::OnPopupMenu(int itemNumber) { // Currently, the only context menu item is "move". @@ -144,7 +139,6 @@ bool CGUIDialogPVRGroupManager::ActionButtonOk(const CGUIMessage& message) if (iControl == BUTTON_OK) { - PersistChanges(); Close(); bReturn = true; } @@ -211,7 +205,9 @@ bool CGUIDialogPVRGroupManager::ActionButtonDeleteGroup(const CGUIMessage& messa .ChannelGroups() ->Get(m_bIsRadio) ->DeleteGroup(m_selectedGroup)) + { Update(); + } } bReturn = true; @@ -242,8 +238,14 @@ bool CGUIDialogPVRGroupManager::ActionButtonRenameGroup(const CGUIMessage& messa if (!strGroupName.empty()) { ClearSelectedGroupsThumbnail(); - m_selectedGroup->SetGroupName(strGroupName, !resetName); - Update(); + if (CServiceBroker::GetPVRManager() + .ChannelGroups() + ->Get(m_bIsRadio) + ->SetGroupName(m_selectedGroup, strGroupName, !resetName)) + { + m_iSelectedChannelGroup = -1; // recalc index in Update() + Update(); + } } } @@ -275,9 +277,13 @@ bool CGUIDialogPVRGroupManager::ActionButtonUngroupedChannels(const CGUIMessage& { const auto itemChannel = m_ungroupedChannels->Get(m_iSelectedUngroupedChannel); - if (m_selectedGroup->AppendToGroup(itemChannel->GetPVRChannelGroupMemberInfoTag())) + if (CServiceBroker::GetPVRManager() + .ChannelGroups() + ->Get(m_bIsRadio) + ->AppendToGroup(m_selectedGroup, itemChannel->GetPVRChannelGroupMemberInfoTag())) { ClearSelectedGroupsThumbnail(); + m_iSelectedChannelGroup = -1; // recalc index in Update() Update(); } } @@ -306,9 +312,16 @@ bool CGUIDialogPVRGroupManager::ActionButtonGroupMembers(const CGUIMessage& mess if (m_selectedGroup && m_groupMembers->GetFileCount() > 0) { const auto itemChannel = m_groupMembers->Get(m_iSelectedGroupMember); - m_selectedGroup->RemoveFromGroup(itemChannel->GetPVRChannelGroupMemberInfoTag()); ClearSelectedGroupsThumbnail(); - Update(); + if (CServiceBroker::GetPVRManager() + .ChannelGroups() + ->Get(m_bIsRadio) + ->RemoveFromGroup(m_selectedGroup, + itemChannel->GetPVRChannelGroupMemberInfoTag())) + { + m_iSelectedChannelGroup = -1; // recalc index in Update() + Update(); + } } } } @@ -351,15 +364,20 @@ bool CGUIDialogPVRGroupManager::ActionButtonChannelGroups(const CGUIMessage& mes m_movingItem = false; // reset group positions - auto* groups = CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio); - int pos = 1; - for (auto& groupItem : *m_channelGroups) + std::vector paths; + for (const auto& groupItem : *m_channelGroups) + { + paths.emplace_back(groupItem->GetPath()); + } + + if (CServiceBroker::GetPVRManager() + .ChannelGroups() + ->Get(m_bIsRadio) + ->ResetGroupPositions(paths)) { - const auto group = groups->GetGroupByPath(groupItem->GetPath()); - if (group) - group->SetPosition(pos++); + Update(); } - groups->SortGroups(); + bReturn = true; } } @@ -378,11 +396,13 @@ bool CGUIDialogPVRGroupManager::ActionButtonHideGroup(const CGUIMessage& message static_cast(GetControl(message.GetSenderId())); if (button) { - CServiceBroker::GetPVRManager() - .ChannelGroups() - ->Get(m_bIsRadio) - ->HideGroup(m_selectedGroup, button->IsSelected()); - Update(); + if (CServiceBroker::GetPVRManager() + .ChannelGroups() + ->Get(m_bIsRadio) + ->HideGroup(m_selectedGroup, button->IsSelected())) + { + Update(); + } } bReturn = true; @@ -397,7 +417,6 @@ bool CGUIDialogPVRGroupManager::ActionButtonToggleRadioTV(const CGUIMessage& mes if (message.GetSenderId() == BUTTON_TOGGLE_RADIO_TV) { - PersistChanges(); SetRadio(!m_bIsRadio); Update(); bReturn = true; diff --git a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h index 2c8ef4fc820ef..b5a8aae14fd74 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h +++ b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h @@ -41,7 +41,6 @@ class CGUIDialogPVRGroupManager : public CGUIDialog void Clear(); void ClearSelectedGroupsThumbnail(); void Update(); - bool PersistChanges(); bool ActionButtonOk(const CGUIMessage& message); bool ActionButtonNewGroup(const CGUIMessage& message); bool ActionButtonDeleteGroup(const CGUIMessage& message); diff --git a/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp index f5b9831c8f8bb..262841be9a7ce 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp @@ -215,10 +215,9 @@ bool CPVRGUIActionsChannels::HideChannel(const CFileItem& item) const CVariant{""}, CVariant{channel->ChannelName()})) return false; - if (!CServiceBroker::GetPVRManager() - .ChannelGroups() - ->GetGroupAll(channel->IsRadio()) - ->RemoveFromGroup(groupMember)) + const auto groups{CServiceBroker::GetPVRManager().ChannelGroups()}; + if (!groups->Get(channel->IsRadio()) + ->RemoveFromGroup(groups->GetGroupAll(channel->IsRadio()), groupMember)) return false; CGUIWindowPVRBase* pvrWindow = From 39c3481a9c6ebe4e8a52f2cf5b41df6b6ab8a834 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Tue, 25 Jun 2024 20:53:36 -0700 Subject: [PATCH 204/651] Python: Restore close of WindowXML when application exits --- xbmc/interfaces/legacy/Window.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/xbmc/interfaces/legacy/Window.cpp b/xbmc/interfaces/legacy/Window.cpp index 05b6b74530b32..5107645cf6e1d 100644 --- a/xbmc/interfaces/legacy/Window.cpp +++ b/xbmc/interfaces/legacy/Window.cpp @@ -668,16 +668,6 @@ namespace XBMCAddon while (bModal && !g_application.m_bStop) { -//! @todo garbear added this code to the python window.cpp class and -//! commented in XBPyThread.cpp. I'm not sure how to handle this -//! in this native implementation. -// // Check if XBPyThread::stop() raised a SystemExit exception -// if (PyThreadState_Get()->async_exc == PyExc_SystemExit) -// { -// CLog::Log(LOGDEBUG, "PYTHON: doModal() encountered a SystemExit exception, closing window and returning"); -// Window_Close(self, NULL); -// break; -// } languageHook->MakePendingCalls(); // MakePendingCalls bool stillWaiting; @@ -687,6 +677,14 @@ namespace XBMCAddon DelayedCallGuard dcguard(languageHook); stillWaiting = WaitForActionEvent(100) ? false : true; } + + // If application has quit, close the window + if (bModal && g_application.m_bStop) + { + CLog::Log(LOGDEBUG, "PYTHON: Application quit inside doModal(), closing window"); + close(); + } + languageHook->MakePendingCalls(); } while (stillWaiting); } From a7b858b1c4ca0e4563924f06835ee6a0ee93cf95 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 24 Jun 2024 12:04:44 +1000 Subject: [PATCH 205/651] [cmake][modules] FindExiv2 cleanup and fix old exiv2 target name use exiv2 config files prior to 0.28 use an non-namespaced target, so we check for it as an optional --- cmake/modules/FindExiv2.cmake | 101 ++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 34 deletions(-) diff --git a/cmake/modules/FindExiv2.cmake b/cmake/modules/FindExiv2.cmake index 5e6c9e0fca1be..bb42f04c25022 100644 --- a/cmake/modules/FindExiv2.cmake +++ b/cmake/modules/FindExiv2.cmake @@ -10,8 +10,6 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) macro(buildexiv2) - - find_package(Patch MODULE REQUIRED) find_package(Iconv REQUIRED) # Note: Please drop once a release based on master is made. First 2 are already upstream. @@ -21,6 +19,17 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) generate_patchcommand("${patches}") + if(WIN32 OR WINDOWS_STORE) + # Exiv2 cant be built using /RTC1, so we alter and disable the auto addition of flags + # using WIN_DISABLE_PROJECT_FLAGS + string(REPLACE "/RTC1" "" EXIV2_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG} ) + + set(EXTRA_ARGS "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}$<$: ${EXIV2_CXX_FLAGS_DEBUG}>$<$: ${CMAKE_CXX_FLAGS_RELEASE}>" + "-DCMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS}$<$: ${CMAKE_EXE_LINKER_FLAGS_DEBUG}>$<$: ${CMAKE_EXE_LINKER_FLAGS_RELEASE}>") + + set(WIN_DISABLE_PROJECT_FLAGS ON) + endif() + set(CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DEXIV2_ENABLE_WEBREADY=OFF -DEXIV2_ENABLE_XMP=OFF @@ -33,7 +42,8 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) -DEXIV2_ENABLE_BROTLI=OFF -DEXIV2_ENABLE_INIH=OFF -DEXIV2_ENABLE_FILESYSTEM_ACCESS=OFF - -DEXIV2_BUILD_EXIV2_COMMAND=OFF) + -DEXIV2_BUILD_EXIV2_COMMAND=OFF + ${EXTRA_ARGS}) if(NOT CMAKE_CXX_COMPILER_LAUNCHER STREQUAL "") list(APPEND CMAKE_ARGS -DBUILD_WITH_CCACHE=ON) @@ -58,7 +68,36 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) buildexiv2() else() - if(NOT TARGET Exiv2::exiv2lib) + if(TARGET Exiv2::exiv2lib OR TARGET exiv2lib) + # We create variables based off TARGET data for use with FPHSA + # exiv2 < 0.28 uses a non namespaced target, but also has an alias. Prioritise + # namespaced target, and fallback to old target for < 0.28 + if(TARGET Exiv2::exiv2lib) + set(_exiv_target_name Exiv2::exiv2lib) + else() + set(_exiv_target_name exiv2lib) + endif() + + # This is for the case where a distro provides a non standard (Debug/Release) config type + # eg Debian's config file is exiv2Config-none.cmake + # convert this back to either DEBUG/RELEASE or just RELEASE + # we only do this because we use find_package_handle_standard_args for config time output + # and it isnt capable of handling TARGETS, so we have to extract the info + get_target_property(_EXIV2_CONFIGURATIONS ${_exiv_target_name} IMPORTED_CONFIGURATIONS) + foreach(_exiv2_config IN LISTS _EXIV2_CONFIGURATIONS) + # Some non standard config (eg None on Debian) + # Just set to RELEASE var so select_library_configurations can continue to work its magic + string(TOUPPER ${_exiv2_config} _exiv2_config_UPPER) + if((NOT ${_exiv2_config_UPPER} STREQUAL "RELEASE") AND + (NOT ${_exiv2_config_UPPER} STREQUAL "DEBUG")) + get_target_property(EXIV2_LIBRARY_RELEASE ${_exiv_target_name} IMPORTED_LOCATION_${_exiv2_config_UPPER}) + else() + get_target_property(EXIV2_LIBRARY_${_exiv2_config_UPPER} ${_exiv_target_name} IMPORTED_LOCATION_${_exiv2_config_UPPER}) + endif() + endforeach() + + get_target_property(EXIV2_INCLUDE_DIR ${_exiv_target_name} INTERFACE_INCLUDE_DIRECTORIES) + else() find_package(PkgConfig) # Fallback to pkg-config and individual lib/include file search if(PKG_CONFIG_FOUND) @@ -67,35 +106,12 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) endif() find_path(EXIV2_INCLUDE_DIR NAMES exiv2/exiv2.hpp - HINTS ${PC_EXIV2_INCLUDEDIR} NO_CACHE) - find_library(EXIV2_LIBRARY NAMES exiv2 - HINTS ${PC_EXIV2_LIBDIR} NO_CACHE) + HINTS ${PC_EXIV2_INCLUDEDIR}) + find_library(EXIV2_LIBRARY_RELEASE NAMES exiv2 + HINTS ${PC_EXIV2_LIBDIR}) endif() endif() - # We create variables based off TARGET data for use with FPHSA - if(TARGET Exiv2::exiv2lib AND NOT TARGET exiv2) - # This is for the case where a distro provides a non standard (Debug/Release) config type - # eg Debian's config file is exiv2Config-none.cmake - # convert this back to either DEBUG/RELEASE or just RELEASE - # we only do this because we use find_package_handle_standard_args for config time output - # and it isnt capable of handling TARGETS, so we have to extract the info - get_target_property(_FMT_CONFIGURATIONS Exiv2::exiv2lib IMPORTED_CONFIGURATIONS) - foreach(_exiv2_config IN LISTS _FMT_CONFIGURATIONS) - # Some non standard config (eg None on Debian) - # Just set to RELEASE var so select_library_configurations can continue to work its magic - string(TOUPPER ${_exiv2_config} _exiv2_config_UPPER) - if((NOT ${_exiv2_config_UPPER} STREQUAL "RELEASE") AND - (NOT ${_exiv2_config_UPPER} STREQUAL "DEBUG")) - get_target_property(EXIV2_LIBRARY_RELEASE Exiv2::exiv2lib IMPORTED_LOCATION_${_exiv2_config_UPPER}) - else() - get_target_property(EXIV2_LIBRARY_${_exiv2_config_UPPER} Exiv2::exiv2lib IMPORTED_LOCATION_${_exiv2_config_UPPER}) - endif() - endforeach() - - get_target_property(EXIV2_INCLUDE_DIR Exiv2::exiv2lib INTERFACE_INCLUDE_DIRECTORIES) - endif() - include(SelectLibraryConfigurations) select_library_configurations(EXIV2) unset(EXIV2_LIBRARIES) @@ -106,14 +122,31 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) VERSION_VAR EXIV2_VER) if(EXIV2_FOUND) - if(TARGET Exiv2::exiv2lib AND NOT TARGET exiv2) - # Exiv2 config found. Use it - add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS Exiv2::exiv2lib) + if((TARGET Exiv2::exiv2lib OR TARGET exiv2lib) AND NOT TARGET exiv2) + # Exiv alias exiv2lib in their latest cmake config. We test for the alias + # to workout what we need to point OUR alias at. + get_target_property(_EXIV2_ALIASTARGET exiv2lib ALIASED_TARGET) + if(_EXIV2_ALIASTARGET) + set(_exiv_target_name ${_EXIV2_ALIASTARGET}) + endif() + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS ${_exiv_target_name}) else() add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES - IMPORTED_LOCATION "${EXIV2_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${EXIV2_INCLUDE_DIR}") + + if(EXIV2_LIBRARY_RELEASE) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${EXIV2_LIBRARY_RELEASE}") + endif() + if(EXIV2_LIBRARY_DEBUG) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION_DEBUG "${EXIV2_LIBRARY_DEBUG}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + endif() + if(CORE_SYSTEM_NAME STREQUAL "freebsd") set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY INTERFACE_LINK_LIBRARIES procstat) From 0a110e007bc981fb3d01751b9352e12583d47a4c Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 25 Jun 2024 20:06:30 +0200 Subject: [PATCH 206/651] [addons] Global strings.po: Fix/add/remove PVR source file references. --- .../resources/strings.po | 237 +++++++++--------- 1 file changed, 123 insertions(+), 114 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index c81cd35e55148..df6abe2714dc3 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -1046,7 +1046,9 @@ msgstr "" #: xbmc/peripherals/dialogs/GUIDialogPeripheralSettings.cpp #: xbmc/profiles/dialogs/GUIDialogLockSettings.cpp #: xbmc/profiles/dialogs/GUIDialogProfileSettings.cpp -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPowerManagement.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp +#: xbmc/pvr/dialogs/GUIDialogPVRClientPriorities.cpp #: xbmc/pvr/dialogs/GUIDialogPVRRecordingSettings.cpp #: xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp #: xbmc/settings/dialogs/GUIDialogContentSettings.cpp @@ -1214,7 +1216,10 @@ msgstr "" #: xbmc/dialogs/GUIDialogFileBrowser.cpp #: xbmc/games/dialogs/GUIDialogSelectGameClient.cpp #: xbmc/network/NetworkServices.cpp -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsChannels.cpp +#: xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp +#. xbmc/pvr/timers/PVRTimerInfoTag.cpp #: xbmc/video/dialogs/GUIDialogVideoInfo.cpp #: xbmc/music/windows/GUIWindowMusicBase.cpp #: xbmc/video/windows/GUIWindowVideoNav.cpp @@ -1250,7 +1255,7 @@ msgstr "" #. Label for a context menu entry / button to start/schedule a recording #: xbmc/music/windows/GUIWindowMusicBase.cpp -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp #: xbmc/pvr/dialogs/GUIDialogPVRGuideInfo.cpp #: xbmc/pvr/windows/GUIWIndowPVRGuide.cpp #: xbmc/pvr/PVRContextMenus.cpp @@ -1504,7 +1509,7 @@ msgstr "" #. generic "cleaning database" label used in different places #: xbmc/music/MusicDatabase.cpp -#: xbmc/pvr/guilib/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp #: xbmc/settings/MediaSettings.cpp #: xbmc/video/VideoDatabase.cpp msgctxt "#313" @@ -2822,7 +2827,9 @@ msgstr "" #: xbmc/filesystem/AddonsDirectory.cpp #: xbmc/PlayListPlayer.cpp #: xbmc/profiles/dialogs/GUIDialogLockSettings.cpp -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp +#: xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp msgctxt "#593" msgid "All" msgstr "" @@ -3611,7 +3618,7 @@ msgstr "" #empty strings from id 800 to 801 #. Label of progress bar which shows the available/total space of pvr backend -#: xbmc/pvr/PVRGUIInfo.cpp +#: xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp msgctxt "#802" msgid "{0:s} of {1:s} available" msgstr "" @@ -3846,13 +3853,13 @@ msgid "Live" msgstr "" #. Message in a dialog when a user wants to delete a timer that was scheduled by a timer rule -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#840" msgid "Do you only want to delete this timer or also the timer rule that has scheduled it?" msgstr "" #. Label for No button in a dialog when a user wants to delete a timer that was scheduled by a timer rule -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#841" msgid "Only this" msgstr "" @@ -3876,25 +3883,25 @@ msgid "Deactivate" msgstr "" #. Message in a dialog when a user wants to delete a timer rule -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#845" msgid "Are you sure you want to delete this timer rule and all timers it has scheduled?" msgstr "" #. Message in a dialog when a user wants to delete a timer -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#846" msgid "Are you sure you want to delete this timer?" msgstr "" #. Heading for a dialog for confirming to stop an active recording -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#847" msgid "Confirm stop recording" msgstr "" #. Message in a dialog when a user wants to stop an active recording -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#848" msgid "Are you sure you want to stop this recording?" msgstr "" @@ -5802,7 +5809,7 @@ msgstr "" #: addons/skin.estuary/xml/Variables.xml #: xbmc/Autorun.cpp #: xbmc/favourites/ContextMenus.cpp -#: xbmc/pvr/PVRGUIActionsPlayback.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp #: xbmc/video/ContextMenus.cpp #: xbmc/video/guilib/VideoSelectActionProcessor.cpp #: xbmc/video/guilib/VideoPlayActionProcessor.cpp @@ -5811,7 +5818,7 @@ msgid "Play from beginning" msgstr "" #. Label of various controls for resuming playback from a certain point in time -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/video/VideoUtils.cpp #: xbmc/video/windows/GUIWindowVideoBase.cpp msgctxt "#12022" msgid "Resume from {0:s}" @@ -6614,7 +6621,7 @@ msgstr "" #: xbmc/pvr/addons/PVRClients.cpp #: xbmc/pvr/channels/PVRChannel.cpp #: xbmc/pvr/dialogs/GUIDialogPVRClientPriorities.cpp -#: xbmc/pvr/PVRGUIInfo.cpp +#: xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp #: xbmc/video/dialogs/GUIDialogAudioSubtitleSettings.cpp #: xbmc/video/PlayerController.cpp #: xbmc/video/dialogs/GUIDialogAudioSettings.cpp @@ -7992,6 +7999,7 @@ msgid "- Shutdown while playing" msgstr "" #: system/settings/settings.xml +#: xbmc/pvr/settings/PVRSettings.cpp msgctxt "#14044" msgid "{0:d} min" msgstr "" @@ -9139,7 +9147,6 @@ msgid "Enter value" msgstr "" #. PVR error messages dialog text. -#: xbmc/addons/PVRClients.cpp #: xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp msgctxt "#16029" msgid "Check the log for more information about this message." @@ -9625,8 +9632,8 @@ msgstr "" #: addons/skin.estuary/xml/Home.xml #: addons/skin.estuary/xml/Variables.xml #: xbmc/dialogs/GUIDialogMediaSource.cpp -#: xbmc/pvr/PVRGUIDirectory.cpp -#: xbmc/pvr/guilib/PVRGUIActions.cpp +#: xbmc/pvr/filesystem/PVRGUIDirectory.cpp +#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp msgctxt "#19017" msgid "Recordings" msgstr "" @@ -9643,8 +9650,8 @@ msgstr "" #: addons/skin.estuary/xml/DialogPVRChannelsOSD.xml #: addons/skin.estuary/xml/Variables.xml #: addons/skin.estuary/xml/Home.xml -#: xbmc/pvr/PVRGUIDirectory.cpp -#: xbmc/pvr/guilib/PVRGUIActions.cpp +#: xbmc/pvr/filesystem/PVRGUIDirectory.cpp +#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp #: xbmc/windows/GUIWindowSystemInfo.cpp msgctxt "#19019" msgid "Channels" @@ -9654,8 +9661,8 @@ msgstr "" #: addons/skin.estuary/xml/Home.xml #: addons/skin.estuary/xml/SkinSettings.xml: #: addons/skin.estuary/xml/Variables.xml -#: xbmc/pvr/PVRGUIActions.cpp -#: xbmc/pvr/PVRGUIDirectory.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp +#: xbmc/pvr/filesystem/PVRGUIDirectory.cpp msgctxt "#19020" msgid "TV" msgstr "" @@ -9664,8 +9671,8 @@ msgstr "" #: addons/skin.estuary/xml/Home.xml #: addons/skin.estuary/xml/SkinSettings.xml #: addons/skin.estuary/xml/Variables.xml -#: xbmc/pvr/PVRGUIActions.cpp -#: xbmc/pvr/PVRGUIDirectory.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp +#: xbmc/pvr/filesystem/PVRGUIDirectory.cpp msgctxt "#19021" msgid "Radio" msgstr "" @@ -9698,7 +9705,7 @@ msgstr "" #. label for "add timer..." list item used in pvr timers / timer rules window #: xbmc/pvr/timers/PVRTimerInfoTag.cpp -#: xbmc/pvr/PVRGUIDirectory.cpp +#: xbmc/pvr/filesystem/PVRGUIDirectory.cpp msgctxt "#19026" msgid "Add timer..." msgstr "" @@ -9718,7 +9725,7 @@ msgstr "" #: addons/skin.estuary/xml/DialogFullScreenInfo.xml #: xbmc/filesystem/PluginDirectory.cpp #: xbmc/pvr/channels/PVRChannel.cpp -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp #: xbmc/utils/SortUtils.cpp msgctxt "#19029" msgid "Channel" @@ -9749,10 +9756,8 @@ msgstr "" #: xbmc/favourites/ContextMenus.cpp #: xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp #: xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp -#: xbmc/pvr/dialogs/GUIDialogPVRGuideInfo.cpp -#: xbmc/pvr/timers/PVRTimerInfoTag.cpp -#: xbmc/pvr/PVRGUIActions.cpp -#: xbmc/pvr/PVRManager.cpp +#: xbmc/pvr/guilib/PVRGUIActionsChannels.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp #: xbmc/video/windows/GUIWindowVideoBase.cpp #: addons/skin.estuary/xml/MusicOSD.xml #: addons/skin.estuary/xml/VideoOSD.xml @@ -9761,21 +9766,18 @@ msgid "Information" msgstr "" #. message box text stating that a timer is already set for a given epg event. -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19034" msgid "There is already a timer set for this event" msgstr "" #. message box text stating that a pvr channel could not be played. -#: xbmc/pvr/PVRGUIActions.cpp -#: xbmc/pvr/PVRManager.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp msgctxt "#19035" msgid "{0:s} can't be played. Check the log for more information about this message." msgstr "" -#. message box text stating that a recording could not be played. -#: xbmc/pvr/PVRGUIActions.cpp -#: xbmc/video/windows/GUIWindowVideoBase.cpp +#: @@@ unused? msgctxt "#19036" msgid "This recording can't be played. Check the log for more information about this message." msgstr "" @@ -9793,7 +9795,7 @@ msgid "Not supported by the PVR backend." msgstr "" #. message box text asking for confirmation to hide a channel. -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsChannels.cpp msgctxt "#19039" msgid "Are you sure you want to hide this channel?" msgstr "" @@ -9877,7 +9879,7 @@ msgid "Recording information" msgstr "" #. header label for hide channel confirmation message box -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsChannels.cpp msgctxt "#19054" msgid "Hide channel" msgstr "" @@ -9892,9 +9894,9 @@ msgstr "" #: addons/skin.estuary/xml/MyVideoNav.xml #: addons/skin.estuary/xml/View_50_List.xml #: xbmc/pvr/epg/EpgInfoTag.cpp +#: xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp #: xbmc/FileItem.cpp #: xbmc/GUIInfoManager.cpp -#: xbmc/pvr/PVRManager.cpp #: xbmc/video/windows/GUIWindowVideoBase.cpp #: xbmc/utils/SystemInfo.cpp msgctxt "#19055" @@ -9986,7 +9988,8 @@ msgstr "" #. generic label for the electronic program guide (EPG) used in different places #: addons/skin.estuary/xml/Variables.xml -#: xbmc/pvr/guilib/PVRGUIActions.cpp +#: xbmc/pvr/filesystem/PVRGUIDirectory.cpp +#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp msgctxt "#19069" msgid "Guide" msgstr "" @@ -10089,7 +10092,7 @@ msgstr "" #. Label for "Instant recording action" setting #: system/settings/settings.xml -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19086" msgid "Instant recording action" msgstr "" @@ -10113,37 +10116,37 @@ msgid "Ask what to do" msgstr "" #. Label for "Instant recording action" dialog settings value -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19090" msgid "Record the next {0:d} minutes" msgstr "" #. Label for "Instant recording action" dialog settings value -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19091" msgid "Record current show ({0:s})" msgstr "" #. Label for "Instant recording action" dialog settings value -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19092" msgid "Record next show ({0:s})" msgstr "" #. Instant recording summary. Expands to "Instant recording: ", for example "Instant recording: 31/05/2016 from 9:00 to 11:00" -#: xbmc/pvr/PVRTimerInfoTag.cpp +#: xbmc/pvr/timers/PVRTimerInfoTag.cpp msgctxt "#19093" msgid "Instant recording: {0:s}" msgstr "" #. error message displayed in case adding a timer failed because no suitable epg-based timer type could be found. -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19094" msgid "Timer creation failed. Unsupported timer type." msgstr "" #. error message displayed in case adding a timer rule failed because no suitable epg-based timer rule type could be found. -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19095" msgid "Timer rule creation failed. Unsupported timer type." msgstr "" @@ -10165,8 +10168,7 @@ msgstr "" #: xbmc/addons/AddonSystemSettings.cpp #: xbmc/addons/gui/GUIDialogAddonSettings.cpp #: xbmc/network/NetworkServices.cpp -#: xbmc/pvr/channels/PVRChannelGroupInternal.cpp -#: xbmc/pvr/PVRManager.cpp +#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp msgctxt "#19098" msgid "Warning!" msgstr "" @@ -10189,8 +10191,7 @@ msgctxt "#19101" msgid "Provider" msgstr "" -#. message box text prompting user to switch to another channel -#: xbmc/pvr/channels/PVRChannelGroupInternal.cpp +#: @@@ unused? msgctxt "#19102" msgid "Please switch to another channel." msgstr "" @@ -10214,13 +10215,13 @@ msgid "Please select a channel" msgstr "" #. part of timer information text (e.g. "Next recording on 10/10/2016 at 9:00 AM") -#: xbmc/pvr/PVRGUITimerInfo.cpp +#: xbmc/pvr/guilib/guiinfo/PVRGUITimerInfo.cpp msgctxt "#19106" msgid "Next recording on" msgstr "" #. part of timer information text (e.g. "Next recording on 10/10/2016 at 9:00 AM") -#: xbmc/pvr/PVRGUITimerInfo.cpp +#: xbmc/pvr/guilib/guiinfo/PVRGUITimerInfo.cpp #: xbmc/pvr/timers/PVRTimerInfoTag.cpp msgctxt "#19107" msgid "at" @@ -10233,31 +10234,31 @@ msgid "Fallback framerate" msgstr "" #. message box text stating that a timer could not be saved -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19109" msgid "Could not save the timer. Check the log for more information about this message." msgstr "" #. message box text stating that a timer could not be deleted -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19110" msgid "Could not delete the timer. Check the log for more information about this message." msgstr "" #. message box text stating that a PVR backend error occurred -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp msgctxt "#19111" msgid "PVR backend error. Check the log for more information about this message." msgstr "" #. delete recordings confirmation message box text -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp msgctxt "#19112" msgid "Delete this recording?" msgstr "" #. delete multiple recordings confirmation message box text -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp msgctxt "#19113" msgid "Delete all recordings in this folder?" msgstr "" @@ -10292,7 +10293,7 @@ msgid "Can't use PVR functions while searching." msgstr "" #. channel scan backend selection dialog text -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsChannels.cpp msgctxt "#19119" msgid "On which backend do you want to search?" msgstr "" @@ -10447,7 +10448,8 @@ msgstr "" #. generic label for pvr channel groups used in different places #: addons/skin.estuary/xml/DialogPVRChannelsOSD.xml -#: xbmc/pvr/guilib/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp +#: xbmc/pvr/windows/GUIWindowPVRBase.cpp msgctxt "#19146" msgid "Groups" msgstr "" @@ -10554,14 +10556,14 @@ msgid "Recordings" msgstr "" #. error box text stating that recording could not be started -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19164" msgid "Could not start recording. Check the log for more information about this message." msgstr "" #. Label for "switch to channel" button"" #: addons/skin.estuary/xml/DialogPVRInfo.xml -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp #: xbmc/pvr/windows/GUIWIndowPVRGuide.cpp msgctxt "#19165" msgid "Switch" @@ -10569,8 +10571,8 @@ msgstr "" #. label for header of misc PVR GUI elements #: xbmc/windows/GUIWindowSystemInfo.cpp -#: xbmc/pvr/PVRManager.cpp -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp +#: xbmc/pvr/windows/GUIWIndowPVRChannels.cpp msgctxt "#19166" msgid "PVR information" msgstr "" @@ -10593,7 +10595,7 @@ msgid "Hide video information box" msgstr "" #. error box text stating that recording could not be stopped -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19170" msgid "Could not stop recording. Check the log for more information about this message." msgstr "" @@ -10683,7 +10685,7 @@ msgstr "" #. label for "deleted recordings" data source in media source window #: addons/skin.estuary/xml/Includes_MediaMenu.xml #: xbmc/dialogs/GUIDialogMediaSource.cpp -#: xbmc/pvr/PVRGUIDirectory.cpp +#: xbmc/pvr/filesystem/PVRGUIDirectory.cpp msgctxt "#19184" msgid "Deleted recordings" msgstr "" @@ -10695,25 +10697,25 @@ msgid "Clear data" msgstr "" #. message box text for pvr data reset confirmation -#: xbmc/pvr/guilib/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp msgctxt "#19186" msgid "All selected data will be cleared. Some information cannot be restored from the clients afterwards. Proceed anyway?" msgstr "" #. progress dialog text shown while purging pvr data -#: xbmc/pvr/guilib/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp msgctxt "#19187" msgid "Clearing data." msgstr "" #. message box text for epg data reset confirmation -#: xbmc/pvr/guilib/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp msgctxt "#19188" msgid "All guide data will be cleared. Are you sure?" msgstr "" #. message box text stating that the PVR backend forbids to record a given epg event. -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19189" msgid "The PVR backend does not allow to record this event." msgstr "" @@ -10731,13 +10733,13 @@ msgid "PVR service" msgstr "" #. info message box text stating that none of the available pvr clients does support channel scanning -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsChannels.cpp msgctxt "#19192" msgid "None of the connected PVR backends supports scanning for channels." msgstr "" #. error message box text stating that a given pvr channel could not be played -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsChannels.cpp msgctxt "#19193" msgid "The channel scan can't be started. Check the log for more information about this message." msgstr "" @@ -10754,19 +10756,19 @@ msgid "Delay mark last watched" msgstr "" #. value for "pvr client specific actions" dialog headers -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsClients.cpp msgctxt "#19196" msgid "PVR client specific actions" msgstr "" #. text for "recording started on " pvr client addon callback notification -#: xbmc/addons/binary/interfaces/api1/PVR/AddonCallbacksPVR.cpp +#: xbmc/pvr/addons/PVRClient.cpp msgctxt "#19197" msgid "Recording started on: {0:s}" msgstr "" #. text for "recording finished on " pvr client addon callback notification -#: xbmc/addons/binary/interfaces/api1/PVR/AddonCallbacksPVR.cpp +#: xbmc/pvr/addons/PVRClient.cpp msgctxt "#19198" msgid "Recording finished on: {0:s}" msgstr "" @@ -10864,7 +10866,7 @@ msgstr "" #. generic label for PVR reminders used in different places #: system/settings/settings.xml -#: xbmc/pvr/guilib/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp msgctxt "#19215" msgid "Reminders" msgstr "" @@ -11139,31 +11141,34 @@ msgid "Change PIN" msgstr "" #. generic 'parental control enter pin' label -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsParentalControl.cpp msgctxt "#19262" msgid "Parental control. Enter PIN:" msgstr "" #. message box text stating that a timer could not be updated -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19263" msgid "Could not update the timer. Check the log for more information about this message." msgstr "" #. label for 'incorrect pin' error dialog header -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsParentalControl.cpp msgctxt "#19264" msgid "Incorrect PIN" msgstr "" #. label for 'incorrect pin' error dialog text -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsParentalControlTimers.cpp +#: xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp msgctxt "#19265" msgid "The entered PIN was incorrect." msgstr "" #. label to use for epg tag title instead of actual event title if the respective channel is parental locked -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/FileItem.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp +#: xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp msgctxt "#19266" msgid "Parental locked" msgstr "" @@ -11279,13 +11284,13 @@ msgid "Browse for icon" msgstr "" #. Label for channel icon search progress dialog -#: xbmc/pvr/channels/PVRChannelGroup.cpp +#: xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp msgctxt "#19286" msgid "Searching for channel icons" msgstr "" -#. Label for the default pvr channel group -#: xbmc/pvr/channels/PVRChannelGroupInternal.cpp +#. Label for the pvr 'all chnanels' 'channel group +#: xbmc/pvr/channels/PVRChannelGroupAllChannels.cpp msgctxt "#19287" msgid "All channels" msgstr "" @@ -11316,19 +11321,20 @@ msgid "Delete permanently" msgstr "" #. "remove deleted recordings from trash" dialog header -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp +#: xbmc/pvr/windows/GUIWindowPVRRecordings.cpp msgctxt "#19292" msgid "Delete all permanently" msgstr "" #. "remove deleted recordings from trash" dialog text -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp msgctxt "#19293" msgid "Remove all deleted recordings from trash? This operation cannot be reverted." msgstr "" #. "remove deleted recording from trash" dialog text -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp msgctxt "#19294" msgid "Remove this deleted recording from trash? This operation cannot be reverted." msgstr "" @@ -11407,37 +11413,37 @@ msgid "Deleted missed PVR reminder for channel '{0:s}' at '{1:s}'" msgstr "" #. text for reminder announcement dialog -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19307" msgid "Reminder for [B]{0:s}[/B] on channel [B]{1:s}[/B] at [B]{2:s}[/B]." msgstr "" #. text for reminder announcement dialog -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19308" msgid "Reminder for channel [B]{0:s}[/B] at [B]{1:s}[/B]." msgstr "" #. additional text for reminder announcement dialog, used if a recording can be scheduled -#: xbmc/pvr/timers/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19309" msgid "(Auto-close of this reminder will schedule a recording...)" msgstr "" #. text for event log entry for logging auto scheduled recordings for auto-closed reminders -#: xbmc/pvr/timers/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19310" msgid "Scheduled recording for auto-closed PVR reminder for '{0:s}' on channel '{1:s}' at '{2:s}'" msgstr "" #. text for event log entry for logging auto scheduled recordings for auto-closed reminders -#: xbmc/pvr/timers/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19311" msgid "Scheduled recording for auto-closed PVR reminder for channel '{0:s}' at '{1:s}'" msgstr "" #. heading for pvr reminder notification dialog -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19312" msgid "PVR reminder" msgstr "" @@ -11455,7 +11461,7 @@ msgid "Schedule recording when auto-closing the reminder popup" msgstr "" #. generic 'backend order' label -#: xbmc/pvr/channels/PVRChannel.cpp +#: xbmc/pvr/windows/GUIViewStatePVR.cpp #: xbmc/utils/SortUtils.cpp msgctxt "#19315" msgid "Backend order" @@ -11542,7 +11548,7 @@ msgid "Delete watched" msgstr "" #. delete multiple recordings confirmation message box text -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp msgctxt "#19328" msgid "Delete all watched recordings in this folder?" msgstr "" @@ -11560,25 +11566,25 @@ msgid "Switch to channel when auto-closing the reminder popup" msgstr "" #. additional text for reminder announcement dialog, used if a recording can be scheduled -#: xbmc/pvr/timers/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19331" msgid "(Auto-close of this reminder will switch to channel...)" msgstr "" #. text for event log entry for logging auto channel switch for auto-closed reminders -#: xbmc/pvr/timers/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19332" msgid "Switched to channel for auto-closed PVR reminder for '{0:s}' on channel '{1:s}' at '{2:s}'" msgstr "" #. text for event log entry for logging auto scheduled recordings for auto-closed reminders -#: xbmc/pvr/timers/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19333" msgid "Switched to channel for auto-closed PVR reminder for channel '{0:s}' at '{1:s}'" msgstr "" #. label for PVR backend number of channel providers in system information's PVR section -#: xbmc/pvr/timers/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp #: xbmc/windows/GUIWindowSystemInfo.cpp msgctxt "#19334" msgid "Providers" @@ -11603,7 +11609,7 @@ msgid "Saved searches" msgstr "" #. delete saved search confirmation message box text -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsEPG.cpp msgctxt "#19338" msgid "Delete this saved search?" msgstr "" @@ -11657,12 +11663,13 @@ msgid "Display and manage available PVR client add-ons." msgstr "" #. info message box text stating that none of the available pvr clients does provide client-specific settings -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsClients.cpp msgctxt "#19347" msgid "None of the active PVR clients provide client-specific settings." msgstr "" #. label for 'by provider' sort method +#: xbmc/pvr/windows/GUIViewStatePVR.cpp #: xbmc/utils/SortUtils.cpp msgctxt "#19348" msgid "Provider" @@ -11699,7 +11706,7 @@ msgid "Play programmes from here" msgstr "" #. Label of a context menu entry to kick catchup / VOD playback of only the selected item, not auto playing any following programmes -#: xbmc/pvr/PVRContextMenuItem.cpp +#: xbmc/pvr/PVRContextMenus.cpp msgctxt "#19354" msgid "Play only this programme" msgstr "" @@ -12281,20 +12288,20 @@ msgid "Adult" msgstr "" #. Title for shutdown confirmation dialog -#: xbmc/pvr/PVRManager.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPowerManagement.cpp msgctxt "#19685" msgid "Confirm shutdown" msgstr "" -#: Label for buttons to open channel guide dialog +#. Label for buttons to open channel guide dialog #: addons/skin.estuary/xml/DialogPVRInfo.xml msgctxt "#19686" msgid "Channel guide" msgstr "" -#: Label for context menu entries, dialog heading, button to start playing a recording +#. Label for context menu entries, dialog heading, button to start playing a recording #: xbmc/pvr/PVRContextMenus.cpp -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp msgctxt "#19687" msgid "Play recording" msgstr "" @@ -12302,43 +12309,43 @@ msgstr "" #empty strings from id 19688 to 19689 #. Text for shutdown confirmation dialog -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPowerManagement.cpp msgctxt "#19690" msgid "PVR has scheduled a reminder for '{0:s}' on channel '{1:s}' in {2:s}." msgstr "" #. Text for shutdown confirmation dialog. -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPowerManagement.cpp msgctxt "#19691" msgid "PVR is currently recording '{0:s}' on channel '{1:s}'." msgstr "" #. Text for shutdown confirmation dialog -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPowerManagement.cpp msgctxt "#19692" msgid "PVR will start recording '{0:s}' on channel '{1:s}' in {2:s}." msgstr "" #. Text for shutdown confirmation dialog -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPowerManagement.cpp msgctxt "#19693" msgid "Daily wakeup is due in {0:s}." msgstr "" #. Text for shutdown confirmation dialog -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPowerManagement.cpp msgctxt "#19694" msgid "{0:d} minutes" msgstr "" #. Text for shutdown confirmation dialog -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPowerManagement.cpp msgctxt "#19695" msgid "about a minute" msgstr "" #. Yes button label for shutdown confirmation dialog -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsPowerManagement.cpp msgctxt "#19696" msgid "Shutdown anyway" msgstr "" @@ -13632,6 +13639,7 @@ msgstr "" #: xbmc/media/MediaTypes.cpp #: xbmc/playlists/SmartPlaylist.cpp +#: xbmc/pvr/windows/GUIViewStatePVR.cppp #: addons/skin.estuary/xml/DialogFullScreenInfo.xml #: addons/skin.estuary/xml/Variables.xml msgctxt "#20359" @@ -13664,6 +13672,7 @@ msgid "Remove TV show from library" msgstr "" #: xbmc/playlists/SmartPlaylist.cpp +#: xbmc/pvr/recordings/PVRRecording.cpp msgctxt "#20364" msgid "TV show" msgstr "" @@ -15744,7 +15753,7 @@ msgstr "" #. generic label to denote the addon type pvr clients, used in different places #: xbmc/addons/Addon.cpp -#: xbmc/pvr/guilib/PVRGUIActions.cpp +#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp msgctxt "#24019" msgid "PVR clients" msgstr "" From b0e549f806425eb15526373d3689866cf9852b78 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 25 Jun 2024 20:28:21 +0200 Subject: [PATCH 207/651] [addons] Global strings.po: Remove user unfriendly/not helpful/irritating 'Check log for more information about this message.' text from PVR error messages. --- .../resources/strings.po | 26 +++++----- .../dialogs/GUIDialogPVRChannelManager.cpp | 20 +++----- xbmc/pvr/guilib/PVRGUIActionsChannels.cpp | 6 +-- xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp | 9 ++-- xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp | 15 ++---- xbmc/pvr/guilib/PVRGUIActionsTimers.cpp | 48 +++++++------------ 6 files changed, 44 insertions(+), 80 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index df6abe2714dc3..d40e277e7bef2 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -9146,8 +9146,8 @@ msgctxt "#16028" msgid "Enter value" msgstr "" -#. PVR error messages dialog text. -#: xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp +#. General error messages dialog text. +#: xbmc/cores/VideoPlayer/VideoPlayer.cpp msgctxt "#16029" msgid "Check the log for more information about this message." msgstr "" @@ -9774,12 +9774,12 @@ msgstr "" #. message box text stating that a pvr channel could not be played. #: xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp msgctxt "#19035" -msgid "{0:s} can't be played. Check the log for more information about this message." +msgid "{0:s} can't be played." msgstr "" #: @@@ unused? msgctxt "#19036" -msgid "This recording can't be played. Check the log for more information about this message." +msgid "This recording can't be played." msgstr "" #. pvr settings "show signal quality" label @@ -9825,12 +9825,12 @@ msgstr "" #: @@@ unused? msgctxt "#19044" -msgid "Please check your configuration. Check the log for more information about this message." +msgid "Please check your configuration." msgstr "" #: @@@ unused? msgctxt "#19045" -msgid "No PVR clients have been started yet. Wait for the PVR clients to start up. Check the log for more information about this message." +msgid "No PVR clients have been started yet. Wait for the PVR clients to start up." msgstr "" #: @@@ unused? @@ -10236,19 +10236,19 @@ msgstr "" #. message box text stating that a timer could not be saved #: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19109" -msgid "Could not save the timer. Check the log for more information about this message." +msgid "Could not save the timer." msgstr "" #. message box text stating that a timer could not be deleted #: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19110" -msgid "Could not delete the timer. Check the log for more information about this message." +msgid "Could not delete the timer." msgstr "" #. message box text stating that a PVR backend error occurred #: xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp msgctxt "#19111" -msgid "PVR backend error. Check the log for more information about this message." +msgid "PVR backend error." msgstr "" #. delete recordings confirmation message box text @@ -10558,7 +10558,7 @@ msgstr "" #. error box text stating that recording could not be started #: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19164" -msgid "Could not start recording. Check the log for more information about this message." +msgid "Could not start recording." msgstr "" #. Label for "switch to channel" button"" @@ -10597,7 +10597,7 @@ msgstr "" #. error box text stating that recording could not be stopped #: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19170" -msgid "Could not stop recording. Check the log for more information about this message." +msgid "Could not stop recording." msgstr "" #. pvr settings "start playback full screen" setting label @@ -10741,7 +10741,7 @@ msgstr "" #. error message box text stating that a given pvr channel could not be played #: xbmc/pvr/guilib/PVRGUIActionsChannels.cpp msgctxt "#19193" -msgid "The channel scan can't be started. Check the log for more information about this message." +msgid "The channel scan can't be started." msgstr "" #. unused? @@ -11149,7 +11149,7 @@ msgstr "" #. message box text stating that a timer could not be updated #: xbmc/pvr/guilib/PVRGUIActionsTimers.cpp msgctxt "#19263" -msgid "Could not update the timer. Check the log for more information about this message." +msgid "Could not update the timer." msgstr "" #. label for 'incorrect pin' error dialog header diff --git a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp index b601c6398594c..d6ed495ab1669 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp @@ -577,10 +577,8 @@ bool CGUIDialogPVRChannelManager::OnClickButtonNewChannel() HELPERS::ShowOKDialogText( CVariant{19033}, CVariant{19038}); // "Information", "Not supported by the PVR backend." else - HELPERS::ShowOKDialogText( - CVariant{2103}, - CVariant{ - 16029}); // "Add-on error", "Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{2103}, + CVariant{19111}); // "Add-on error", "PVR backend error." } return true; } @@ -749,10 +747,8 @@ bool CGUIDialogPVRChannelManager::OnContextButton(int itemNumber, CONTEXT_BUTTON HELPERS::ShowOKDialogText( CVariant{19033}, CVariant{19038}); // "Information", "Not supported by the PVR backend." else - HELPERS::ShowOKDialogText( - CVariant{2103}, - CVariant{ - 16029}); // "Add-on error", "Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{2103}, + CVariant{19111}); // "Add-on error", "PVR backend error." } else if (button == CONTEXT_BUTTON_DELETE) { @@ -788,10 +784,8 @@ bool CGUIDialogPVRChannelManager::OnContextButton(int itemNumber, CONTEXT_BUTTON CVariant{19033}, CVariant{19038}); // "Information", "Not supported by the PVR backend." else - HELPERS::ShowOKDialogText( - CVariant{2103}, - CVariant{ - 16029}); // "Add-on error", "Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{2103}, + CVariant{19111}); // "Add-on error", "PVR backend error." } } } @@ -958,7 +952,7 @@ void CGUIDialogPVRChannelManager::RenameChannel(const CFileItemPtr& pItem) const std::shared_ptr client = CServiceBroker::GetPVRManager().GetClient(*pItem); if (!client || (client->RenameChannel(channel) != PVR_ERROR_NO_ERROR)) HELPERS::ShowOKDialogText(CVariant{2103}, - CVariant{16029}); // Add-on error;Check the log file for details. + CVariant{19111}); // "Add-on error", "PVR backend error." } } diff --git a/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp index 262841be9a7ce..f13d331318713 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp @@ -311,10 +311,8 @@ bool CPVRGUIActionsChannels::StartChannelScan(int clientId) /* do the scan */ if (scanClient->StartChannelScan() != PVR_ERROR_NO_ERROR) - HELPERS::ShowOKDialogText( - CVariant{257}, // "Error" - CVariant{ - 19193}); // "The channel scan can't be started. Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{257}, // "Error" + CVariant{19193}); // "The channel scan can't be started." auto end = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(end - start); diff --git a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp index a0bc877a604b4..954b46eb5ed55 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp @@ -321,9 +321,8 @@ bool CPVRGUIActionsPlayback::SwitchToChannel(const CFileItem& item, bool bCheckR { const std::string channelName = channel ? channel->ChannelName() : g_localizeStrings.Get(19029); // Channel - const std::string msg = StringUtils::Format( - g_localizeStrings.Get(19035), - channelName); // CHANNELNAME could not be played. Check the log for details. + const std::string msg = StringUtils::Format(g_localizeStrings.Get(19035), + channelName); // CHANNELNAME could not be played. CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(19166), msg); // PVR information @@ -407,9 +406,7 @@ bool CPVRGUIActionsPlayback::SwitchToChannel(PlaybackType type) const g_localizeStrings.Get(19166), // PVR information StringUtils::Format( g_localizeStrings.Get(19035), - g_localizeStrings.Get( - bIsRadio ? 19021 - : 19020))); // Radio/TV could not be played. Check the log for details. + g_localizeStrings.Get(bIsRadio ? 19021 : 19020))); // Radio/TV could not be played. return false; } diff --git a/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp b/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp index 9609617be9ac4..316b8a896bc4c 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp @@ -256,10 +256,7 @@ bool CPVRGUIActionsRecordings::DeleteRecording(const CFileItem& item) const if (!AsyncDeleteRecording().Execute(item)) { - HELPERS::ShowOKDialogText( - CVariant{257}, - CVariant{ - 19111}); // "Error", "PVR backend error. Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{257}, CVariant{19111}); // "Error", "PVR backend error." return false; } @@ -289,10 +286,7 @@ bool CPVRGUIActionsRecordings::DeleteWatchedRecordings(const CFileItem& item) co if (!AsyncDeleteRecording(true).Execute(item)) { - HELPERS::ShowOKDialogText( - CVariant{257}, - CVariant{ - 19111}); // "Error", "PVR backend error. Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{257}, CVariant{19111}); // "Error", "PVR backend error." return false; } @@ -333,10 +327,7 @@ bool CPVRGUIActionsRecordings::UndeleteRecording(const CFileItem& item) const if (!AsyncUndeleteRecording().Execute(item)) { - HELPERS::ShowOKDialogText( - CVariant{257}, - CVariant{ - 19111}); // "Error", "PVR backend error. Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{257}, CVariant{19111}); // "Error", "PVR backend error." return false; } diff --git a/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp b/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp index 9391cc62dfc3a..570249b12a628 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp @@ -82,10 +82,8 @@ class AsyncUpdateTimer : private IRunnable if (CServiceBroker::GetPVRManager().Timers()->UpdateTimer(m_newTimer)) return; - HELPERS::ShowOKDialogText( - CVariant{257}, - CVariant{ - 19263}); // "Error", "Could not update the timer. Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{257}, + CVariant{19263}); // "Error", "Could not update the timer." m_success = false; return; } @@ -266,10 +264,8 @@ bool CPVRGUIActionsTimers::AddTimer(const std::shared_ptr& ite if (!item->Channel() && !item->GetTimerType()->IsEpgBasedTimerRule()) { CLog::LogF(LOGERROR, "No channel given"); - HELPERS::ShowOKDialogText( - CVariant{257}, - CVariant{ - 19109}); // "Error", "Could not save the timer. Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{257}, + CVariant{19109}); // "Error", "Could not save the timer." return false; } @@ -287,10 +283,8 @@ bool CPVRGUIActionsTimers::AddTimer(const std::shared_ptr& ite if (!CServiceBroker::GetPVRManager().Timers()->AddTimer(item)) { - HELPERS::ShowOKDialogText( - CVariant{257}, - CVariant{ - 19109}); // "Error", "Could not save the timer. Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{257}, + CVariant{19109}); // "Error", "Could not save the timer" return false; } @@ -574,10 +568,8 @@ bool CPVRGUIActionsTimers::SetRecordingOnChannel(const std::shared_ptrAddTimer(newTimer); if (!bReturn) - HELPERS::ShowOKDialogText( - CVariant{257}, - CVariant{ - 19164}); // "Error", "Could not start recording. Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{257}, + CVariant{19164}); // "Error", "Could not start recording." } else if (!bOnOff && CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel)) { @@ -586,10 +578,8 @@ bool CPVRGUIActionsTimers::SetRecordingOnChannel(const std::shared_ptrDeleteTimersOnChannel(channel, true, true); if (!bReturn) - HELPERS::ShowOKDialogText( - CVariant{257}, - CVariant{ - 19170}); // "Error", "Could not stop recording. Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{257}, + CVariant{19170}); // "Error", "Could not stop recording." } } @@ -627,10 +617,8 @@ bool CPVRGUIActionsTimers::ToggleTimerState(const CFileItem& item) const if (CServiceBroker::GetPVRManager().Timers()->UpdateTimer(timer)) return true; - HELPERS::ShowOKDialogText( - CVariant{257}, - CVariant{ - 19263}); // "Error", "Could not update the timer. Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{257}, + CVariant{19263}); // "Error", "Could not update the timer." return false; } @@ -727,10 +715,8 @@ bool CPVRGUIActionsTimers::DeleteTimer(const CFileItem& item, TimerOperationResult::OK) return true; - HELPERS::ShowOKDialogText( - CVariant{257}, - CVariant{ - 19170}); // "Error", "Could not stop recording. Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{257}, + CVariant{19170}); // "Error", "Could not stop recording." return false; } } @@ -773,10 +759,8 @@ bool CPVRGUIActionsTimers::DeleteTimer(const std::shared_ptr& } case TimerOperationResult::FAILED: { - HELPERS::ShowOKDialogText( - CVariant{257}, - CVariant{ - 19110}); // "Error", "Could not delete the timer. Check the log for more information about this message." + HELPERS::ShowOKDialogText(CVariant{257}, + CVariant{19110}); // "Error", "Could not delete the timer." return false; } default: From 6b970eee2af6000cabbc2bd3b00ee6248f2a00c6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 24 Jun 2024 17:54:58 +1000 Subject: [PATCH 208/651] [pictures][metadata] Handle old libexiv2 API --- .../pictures/metadata/ImageMetadataParser.cpp | 49 +++++++++++-------- xbmc/pictures/metadata/ImageMetadataParser.h | 2 +- .../metadata/test/TestMetadataExtraction.cpp | 4 ++ 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/xbmc/pictures/metadata/ImageMetadataParser.cpp b/xbmc/pictures/metadata/ImageMetadataParser.cpp index 485fd8d97426c..aad8ade1b589d 100644 --- a/xbmc/pictures/metadata/ImageMetadataParser.cpp +++ b/xbmc/pictures/metadata/ImageMetadataParser.cpp @@ -16,6 +16,14 @@ #include #include +#if (EXIV2_MAJOR_VERSION == 0) && (EXIV2_MINOR_VERSION < 28) +#define EXIV_toUint32 toLong +#define EXIV_toInt64 toLong +#else +#define EXIV_toUint32 toUint32 +#define EXIV_toInt64 toInt64 +#endif + using namespace XFILE; namespace @@ -75,7 +83,7 @@ std::unique_ptr CImageMetadataParser::ExtractMetadata(const std:: CImageMetadataParser parser; // extract metadata - parser.ExtractCommonMetadata(image); + parser.ExtractCommonMetadata(*image); parser.ExtractExif(image->exifData()); parser.ExtractIPTC(image->iptcData()); @@ -83,19 +91,20 @@ std::unique_ptr CImageMetadataParser::ExtractMetadata(const std:: return std::move(parser.m_imageMetadata); } -void CImageMetadataParser::ExtractCommonMetadata(std::unique_ptr& image) +void CImageMetadataParser::ExtractCommonMetadata(Exiv2::Image& image) { //! TODO: all these elements are generic should be moved out of the exif struct - m_imageMetadata->height = image->pixelHeight(); - m_imageMetadata->width = image->pixelWidth(); - m_imageMetadata->fileComment = image->comment(); - - if (image->imageType() == Exiv2::ImageType::jpeg) + m_imageMetadata->height = image.pixelHeight(); + m_imageMetadata->width = image.pixelWidth(); + m_imageMetadata->fileComment = image.comment(); +#if (EXIV2_MAJOR_VERSION >= 0) && (EXIV2_MINOR_VERSION >= 28) && (EXIV2_PATCH_VERSION >= 2) + if (image.imageType() == Exiv2::ImageType::jpeg) { - auto jpegImage = dynamic_cast(image.get()); + auto jpegImage = dynamic_cast(&image); m_imageMetadata->isColor = jpegImage->numColorComponents() == 3; m_imageMetadata->encodingProcess = jpegImage->encodingProcess(); } +#endif } void CImageMetadataParser::ExtractExif(Exiv2::ExifData& exifData) @@ -121,7 +130,7 @@ void CImageMetadataParser::ExtractExif(Exiv2::ExifData& exifData) } else if (exifKey == "Exif.Image.Orientation") { - const int orientationValue = it->value().toUint32(); + const int orientationValue = it->value().EXIV_toUint32(); if (orientationValue < 0 || orientationValue > 8) { CLog::LogF(LOGWARNING, "Exif: Undefined rotation value {}", @@ -145,11 +154,11 @@ void CImageMetadataParser::ExtractExif(Exiv2::ExifData& exifData) } else if (exifKey == "Exif.Photo.ExposureProgram") { - m_imageMetadata->exifInfo.ExposureProgram = it->value().toUint32(); + m_imageMetadata->exifInfo.ExposureProgram = it->value().EXIV_toUint32(); } else if (exifKey == "Exif.Photo.ISOSpeedRatings") { - m_imageMetadata->exifInfo.ISOequivalent = it->value().toUint32(); + m_imageMetadata->exifInfo.ISOequivalent = it->value().EXIV_toUint32(); } else if (exifKey == "Exif.Photo.DateTimeOriginal") { @@ -184,11 +193,11 @@ void CImageMetadataParser::ExtractExif(Exiv2::ExifData& exifData) } else if (exifKey == "Exif.Photo.MeteringMode") { - m_imageMetadata->exifInfo.MeteringMode = it->value().toUint32(); + m_imageMetadata->exifInfo.MeteringMode = it->value().EXIV_toUint32(); } else if (exifKey == "Exif.Photo.Flash") { - m_imageMetadata->exifInfo.FlashUsed = it->value().toUint32(); + m_imageMetadata->exifInfo.FlashUsed = it->value().EXIV_toUint32(); } else if (exifKey == "Exif.Photo.FocalLength") { @@ -211,7 +220,7 @@ void CImageMetadataParser::ExtractExif(Exiv2::ExifData& exifData) // Use largest of height and width to deal with images that have been // rotated to portrait format. { - const int value = static_cast(it->value().toInt64()); + const int value = static_cast(it->value().EXIV_toInt64()); if (m_imageWidth < value) { m_imageWidth = value; @@ -224,7 +233,7 @@ void CImageMetadataParser::ExtractExif(Exiv2::ExifData& exifData) } else if (exifKey == "Exif.Photo.FocalPlaneResolutionUnit") { - const uint32_t value = it->value().toUint32(); + const uint32_t value = it->value().EXIV_toUint32(); // see: https://exiftool.org/TagNames/EXIF.html switch (value) { @@ -247,15 +256,15 @@ void CImageMetadataParser::ExtractExif(Exiv2::ExifData& exifData) } else if (exifKey == "Exif.Photo.ExposureMode") { - m_imageMetadata->exifInfo.ExposureMode = it->value().toUint32(); + m_imageMetadata->exifInfo.ExposureMode = it->value().EXIV_toUint32(); } else if (exifKey == "Exif.Photo.WhiteBalance") { - m_imageMetadata->exifInfo.Whitebalance = it->value().toUint32(); + m_imageMetadata->exifInfo.Whitebalance = it->value().EXIV_toUint32(); } else if (exifKey == "Exif.Photo.LightSource") { - m_imageMetadata->exifInfo.LightSource = it->value().toUint32(); + m_imageMetadata->exifInfo.LightSource = it->value().EXIV_toUint32(); } else if (exifKey == "Exif.Photo.DigitalZoomRatio") { @@ -266,7 +275,7 @@ void CImageMetadataParser::ExtractExif(Exiv2::ExifData& exifData) // The focal length equivalent 35 mm is a 2.2 tag (defined as of April 2002) // if its present, use it to compute equivalent focal length instead of // computing it from sensor geometry and actual focal length. - m_imageMetadata->exifInfo.FocalLength35mmEquiv = it->value().toUint32(); + m_imageMetadata->exifInfo.FocalLength35mmEquiv = it->value().EXIV_toUint32(); } else if (exifKey == "Exif.GPSInfo.GPSLatitudeRef") { @@ -308,7 +317,7 @@ void CImageMetadataParser::ExtractExif(Exiv2::ExifData& exifData) } else if (exifKey == "Exif.GPSInfo.GPSAltitudeRef") { - auto value = it->value().toUint32(); + auto value = it->value().EXIV_toUint32(); if (value == 1) // below sea level { m_imageMetadata->exifInfo.GpsAlt = "-" + m_imageMetadata->exifInfo.GpsAlt; diff --git a/xbmc/pictures/metadata/ImageMetadataParser.h b/xbmc/pictures/metadata/ImageMetadataParser.h index 513355cbb85ad..f7bdd5115f200 100644 --- a/xbmc/pictures/metadata/ImageMetadataParser.h +++ b/xbmc/pictures/metadata/ImageMetadataParser.h @@ -24,7 +24,7 @@ class CImageMetadataParser private: CImageMetadataParser(); - void ExtractCommonMetadata(std::unique_ptr& image); + void ExtractCommonMetadata(Exiv2::Image& image); void ExtractExif(Exiv2::ExifData& exifData); void ExtractIPTC(Exiv2::IptcData& iptcData); diff --git a/xbmc/pictures/metadata/test/TestMetadataExtraction.cpp b/xbmc/pictures/metadata/test/TestMetadataExtraction.cpp index 7b08118dfd7cf..4a0edcfd7cc5b 100644 --- a/xbmc/pictures/metadata/test/TestMetadataExtraction.cpp +++ b/xbmc/pictures/metadata/test/TestMetadataExtraction.cpp @@ -46,9 +46,11 @@ TEST_F(TestMetadataExtraction, TestGPSImage) EXPECT_EQ(metadata->width, 1); EXPECT_EQ(metadata->height, 1); EXPECT_TRUE(metadata->fileComment.empty()); +#if (EXIV2_MAJOR_VERSION >= 0) && (EXIV2_MINOR_VERSION >= 28) && (EXIV2_PATCH_VERSION >= 2) // format specific (but common) metadata EXPECT_TRUE(metadata->isColor); EXPECT_EQ(metadata->encodingProcess, "Baseline DCT, Huffman coding"); +#endif } TEST_F(TestMetadataExtraction, TestIPTC) @@ -74,7 +76,9 @@ TEST_F(TestMetadataExtraction, TestIPTC) EXPECT_EQ(metadata->width, 1); EXPECT_EQ(metadata->height, 1); EXPECT_TRUE(metadata->fileComment.empty()); +#if (EXIV2_MAJOR_VERSION >= 0) && (EXIV2_MINOR_VERSION >= 28) && (EXIV2_PATCH_VERSION >= 2) // format specific (but common) metadata EXPECT_TRUE(metadata->isColor); EXPECT_EQ(metadata->encodingProcess, "Baseline DCT, Huffman coding"); +#endif } From 4d216cc16fd10259961a2336d5322945b54a362c Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 24 Jun 2024 19:40:18 +1000 Subject: [PATCH 209/651] [cmake][module] FindExiv2 add patch for windows debug postfix --- cmake/modules/FindExiv2.cmake | 7 +++++-- tools/depends/target/exiv2/0004-WIN-lib-postfix.patch | 10 ++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 tools/depends/target/exiv2/0004-WIN-lib-postfix.patch diff --git a/cmake/modules/FindExiv2.cmake b/cmake/modules/FindExiv2.cmake index bb42f04c25022..8bd5df2543bd2 100644 --- a/cmake/modules/FindExiv2.cmake +++ b/cmake/modules/FindExiv2.cmake @@ -14,12 +14,15 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) # Note: Please drop once a release based on master is made. First 2 are already upstream. set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch" - "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch" - "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0003-UWP-Disable-getLoadedLibraries.patch") + "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch" + "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0003-UWP-Disable-getLoadedLibraries.patch" + "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0004-WIN-lib-postfix.patch") generate_patchcommand("${patches}") if(WIN32 OR WINDOWS_STORE) + set(EXIV2_DEBUG_POSTFIX d) + # Exiv2 cant be built using /RTC1, so we alter and disable the auto addition of flags # using WIN_DISABLE_PROJECT_FLAGS string(REPLACE "/RTC1" "" EXIV2_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG} ) diff --git a/tools/depends/target/exiv2/0004-WIN-lib-postfix.patch b/tools/depends/target/exiv2/0004-WIN-lib-postfix.patch new file mode 100644 index 0000000000000..a78dacec9f400 --- /dev/null +++ b/tools/depends/target/exiv2/0004-WIN-lib-postfix.patch @@ -0,0 +1,10 @@ +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -215,6 +215,7 @@ + target_compile_definitions(exiv2lib PRIVATE PSAPI_VERSION=1) # to be compatible with <= WinVista (#905) + # Since windows.h is included in some headers, we need to propagate this definition + target_compile_definitions(exiv2lib PUBLIC WIN32_LEAN_AND_MEAN) ++ set_target_properties(exiv2lib PROPERTIES DEBUG_POSTFIX d) + endif() + + if (NOT MSVC) From 495fbabca0de9a0d7b97475cae8346c04445f47c Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 26 Jun 2024 09:58:43 +0200 Subject: [PATCH 210/651] [dvdread] fix warning 'gcc_struct' attribute directive ignored this is because 'gcc_struct' being undefined for arm --- xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h index 4191b67922fc4..650838cbeabae 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h @@ -29,7 +29,8 @@ #if defined(__GNUC__) #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) -#if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) && !defined(__clang__) +#if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) && !defined(__clang__) && \ + !defined(__arm__) && !defined(__aarch64__) #define ATTRIBUTE_PACKED __attribute__((packed, gcc_struct)) #else #define ATTRIBUTE_PACKED __attribute__((packed)) From 82982380e51dc2cb31533f548a40720339c7cfc3 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Tue, 25 Jun 2024 23:14:59 -0700 Subject: [PATCH 211/651] Python: Fix hang at application shutdown --- xbmc/interfaces/python/XBPython.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xbmc/interfaces/python/XBPython.cpp b/xbmc/interfaces/python/XBPython.cpp index 7498c14ebbc75..5fdd17610fa7f 100644 --- a/xbmc/interfaces/python/XBPython.cpp +++ b/xbmc/interfaces/python/XBPython.cpp @@ -57,7 +57,13 @@ XBPython::~XBPython() #if PY_VERSION_HEX >= 0x03070000 if (Py_IsInitialized()) { + // Switch to the main interpreter thread before finalizing PyThreadState_Swap(PyInterpreterState_ThreadHead(PyInterpreterState_Main())); + + // Clear all loaded modules to prevent circular references + PyObject* modules = PyImport_GetModuleDict(); + PyDict_Clear(modules); + Py_Finalize(); } #endif From 32da6b6bb00875d3f4e8d58d8d31a33aea527ec2 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Tue, 30 Jan 2024 14:57:08 -0800 Subject: [PATCH 212/651] [Joysticks] Replace magic strings with constants --- xbmc/games/controllers/CMakeLists.txt | 8 +++- xbmc/games/controllers/DefaultController.cpp | 34 +++++++++++++ xbmc/games/controllers/DefaultController.h | 50 ++++++++++++++++++++ xbmc/input/joysticks/JoystickEasterEgg.cpp | 21 ++++---- 4 files changed, 101 insertions(+), 12 deletions(-) create mode 100644 xbmc/games/controllers/DefaultController.cpp create mode 100644 xbmc/games/controllers/DefaultController.h diff --git a/xbmc/games/controllers/CMakeLists.txt b/xbmc/games/controllers/CMakeLists.txt index b00e1caa4a238..b54f5c0604e7a 100644 --- a/xbmc/games/controllers/CMakeLists.txt +++ b/xbmc/games/controllers/CMakeLists.txt @@ -1,7 +1,9 @@ set(SOURCES Controller.cpp ControllerLayout.cpp ControllerManager.cpp - ControllerTranslator.cpp) + ControllerTranslator.cpp + DefaultController.cpp +) set(HEADERS Controller.h ControllerDefinitions.h @@ -9,6 +11,8 @@ set(HEADERS Controller.h ControllerLayout.h ControllerManager.h ControllerTranslator.h - ControllerTypes.h) + ControllerTypes.h + DefaultController.h +) core_add_library(games_controller) diff --git a/xbmc/games/controllers/DefaultController.cpp b/xbmc/games/controllers/DefaultController.cpp new file mode 100644 index 0000000000000..58bb464e3dd25 --- /dev/null +++ b/xbmc/games/controllers/DefaultController.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 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 "DefaultController.h" + +using namespace KODI; +using namespace GAME; + +const char* CDefaultController::FEATURE_A = "a"; +const char* CDefaultController::FEATURE_B = "b"; +const char* CDefaultController::FEATURE_X = "x"; +const char* CDefaultController::FEATURE_Y = "y"; +const char* CDefaultController::FEATURE_START = "start"; +const char* CDefaultController::FEATURE_BACK = "back"; +const char* CDefaultController::FEATURE_GUIDE = "guide"; +const char* CDefaultController::FEATURE_UP = "up"; +const char* CDefaultController::FEATURE_RIGHT = "right"; +const char* CDefaultController::FEATURE_DOWN = "down"; +const char* CDefaultController::FEATURE_LEFT = "left"; +const char* CDefaultController::FEATURE_LEFT_THUMB = "leftthumb"; +const char* CDefaultController::FEATURE_RIGHT_THUMB = "rightthumb"; +const char* CDefaultController::FEATURE_LEFT_BUMPER = "leftbumper"; +const char* CDefaultController::FEATURE_RIGHT_BUMPER = "rightbumper"; +const char* CDefaultController::FEATURE_LEFT_TRIGGER = "lefttrigger"; +const char* CDefaultController::FEATURE_RIGHT_TRIGGER = "righttrigger"; +const char* CDefaultController::FEATURE_LEFT_STICK = "leftstick"; +const char* CDefaultController::FEATURE_RIGHT_STICK = "rightstick"; +const char* CDefaultController::FEATURE_LEFT_MOTOR = "leftmotor"; +const char* CDefaultController::FEATURE_RIGHT_MOTOR = "rightmotor"; diff --git a/xbmc/games/controllers/DefaultController.h b/xbmc/games/controllers/DefaultController.h new file mode 100644 index 0000000000000..f82ce4974847a --- /dev/null +++ b/xbmc/games/controllers/DefaultController.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 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 + +namespace KODI +{ +namespace GAME +{ +class CDefaultController +{ +public: + // Face buttons + static const char* FEATURE_A; + static const char* FEATURE_B; + static const char* FEATURE_X; + static const char* FEATURE_Y; + static const char* FEATURE_START; + static const char* FEATURE_BACK; + static const char* FEATURE_GUIDE; + static const char* FEATURE_UP; + static const char* FEATURE_RIGHT; + static const char* FEATURE_DOWN; + static const char* FEATURE_LEFT; + static const char* FEATURE_LEFT_THUMB; + static const char* FEATURE_RIGHT_THUMB; + + // Shoulder buttons + static const char* FEATURE_LEFT_BUMPER; + static const char* FEATURE_RIGHT_BUMPER; + + // Triggers + static const char* FEATURE_LEFT_TRIGGER; + static const char* FEATURE_RIGHT_TRIGGER; + + // Analog sticks + static const char* FEATURE_LEFT_STICK; + static const char* FEATURE_RIGHT_STICK; + + // Haptics + static const char* FEATURE_LEFT_MOTOR; + static const char* FEATURE_RIGHT_MOTOR; +}; +} // namespace GAME +} // namespace KODI diff --git a/xbmc/input/joysticks/JoystickEasterEgg.cpp b/xbmc/input/joysticks/JoystickEasterEgg.cpp index ab02ba0bde5ff..a25c4baf78757 100644 --- a/xbmc/input/joysticks/JoystickEasterEgg.cpp +++ b/xbmc/input/joysticks/JoystickEasterEgg.cpp @@ -12,6 +12,7 @@ #include "games/GameServices.h" #include "games/GameSettings.h" #include "games/controllers/ControllerIDs.h" +#include "games/controllers/DefaultController.h" #include "guilib/GUIAudioManager.h" #include "guilib/WindowIDs.h" @@ -22,16 +23,16 @@ const std::map> CJoystickEasterEgg::m_sequ { DEFAULT_CONTROLLER_ID, { - "up", - "up", - "down", - "down", - "left", - "right", - "left", - "right", - "b", - "a", + GAME::CDefaultController::FEATURE_UP, + GAME::CDefaultController::FEATURE_UP, + GAME::CDefaultController::FEATURE_DOWN, + GAME::CDefaultController::FEATURE_DOWN, + GAME::CDefaultController::FEATURE_LEFT, + GAME::CDefaultController::FEATURE_RIGHT, + GAME::CDefaultController::FEATURE_LEFT, + GAME::CDefaultController::FEATURE_RIGHT, + GAME::CDefaultController::FEATURE_B, + GAME::CDefaultController::FEATURE_A, }, }, { From bf54230f0cf2e769a5133da69e10fe11a9f997e0 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Sun, 28 Jan 2024 19:47:51 -0800 Subject: [PATCH 213/651] [Android][Peripherals] Fix comment indentation Likely due to a past format with clang-format that didn't properly de-indent the comment blocks. --- .../peripherals/AndroidJoystickState.h | 22 +++++++++-------- .../peripherals/AndroidJoystickTranslator.h | 24 +++++++++---------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/xbmc/platform/android/peripherals/AndroidJoystickState.h b/xbmc/platform/android/peripherals/AndroidJoystickState.h index 33ff953a466f9..e146193541c6d 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickState.h +++ b/xbmc/platform/android/peripherals/AndroidJoystickState.h @@ -33,25 +33,27 @@ class CAndroidJoystickState unsigned int GetAxisCount() const { return static_cast(m_axes.size()); } /*! - * Initialize the joystick object. Joystick will be initialized before the - * first call to GetEvents(). - */ + * \brief Initialize the joystick object + * + * Joystick will be initialized before the first call to GetEvents(). + */ bool Initialize(const CJNIViewInputDevice& inputDevice); /*! - * Deinitialize the joystick object. GetEvents() will not be called after - * deinitialization. - */ + * \brief Deinitialize the joystick object + * + * GetEvents() will not be called after deinitialization. + */ void Deinitialize(); /*! - * Processes the given input event. - */ + * \brief Processes the given input event. + */ bool ProcessEvent(const AInputEvent* event); /*! - * Get events that have occurred since the last call to GetEvents() - */ + * \brief Get events that have occurred since the last call to GetEvents() + */ void GetEvents(std::vector& events); private: diff --git a/xbmc/platform/android/peripherals/AndroidJoystickTranslator.h b/xbmc/platform/android/peripherals/AndroidJoystickTranslator.h index 53a953f0ac35f..a5bb8e33d5c4d 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickTranslator.h +++ b/xbmc/platform/android/peripherals/AndroidJoystickTranslator.h @@ -14,21 +14,21 @@ class CAndroidJoystickTranslator { public: /*! - * \brief Translate an axis ID to an Android enum suitable for logging - * - * \param axisId The axis ID given in - * - * \return The translated enum label, or "unknown" if unknown - */ + * \brief Translate an axis ID to an Android enum suitable for logging + * + * \param axisId The axis ID given in + * + * \return The translated enum label, or "unknown" if unknown + */ static const char* TranslateAxis(int axisId); /*! - * \brief Translate a key code to an Android enum suitable for logging - * - * \param keyCode The key code given in - * - * \return The translated enum label, or "unknown" if unknown - */ + * \brief Translate a key code to an Android enum suitable for logging + * + * \param keyCode The key code given in + * + * \return The translated enum label, or "unknown" if unknown + */ static const char* TranslateKeyCode(int keyCode); }; } // namespace PERIPHERALS From 87ed582b5e06539957cf9a4666a7eadc325f6a80 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Sun, 28 Jan 2024 19:05:09 -0800 Subject: [PATCH 214/651] [Android][Peripherals] Change push_back to emplace_back --- .../peripherals/AndroidJoystickState.cpp | 58 +++++++++---------- .../peripherals/PeripheralBusAndroid.cpp | 4 +- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/xbmc/platform/android/peripherals/AndroidJoystickState.cpp b/xbmc/platform/android/peripherals/AndroidJoystickState.cpp index 18bbb557e03f8..1a8244fbc9ea4 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickState.cpp +++ b/xbmc/platform/android/peripherals/AndroidJoystickState.cpp @@ -54,15 +54,15 @@ static void MapAxisIds(int axisId, if (axisIds.empty()) { - axisIds.push_back(primaryAxisId); - axisIds.push_back(secondaryAxisId); + axisIds.emplace_back(primaryAxisId); + axisIds.emplace_back(secondaryAxisId); } if (axisIds.size() > 1) return; if (axisId == primaryAxisId) - axisIds.push_back(secondaryAxisId); + axisIds.emplace_back(secondaryAxisId); else if (axisId == secondaryAxisId) axisIds.insert(axisIds.begin(), primaryAxisId); } @@ -140,10 +140,10 @@ bool CAndroidJoystickState::Initialize(const CJNIViewInputDevice& inputDevice) MapAxisIds(axisId, AMOTION_EVENT_AXIS_LTRIGGER, AMOTION_EVENT_AXIS_BRAKE, axis.ids); MapAxisIds(axisId, AMOTION_EVENT_AXIS_RTRIGGER, AMOTION_EVENT_AXIS_GAS, axis.ids); - m_axes.push_back(axis); + m_axes.emplace_back(std::move(axis)); CLog::Log(LOGDEBUG, "CAndroidJoystickState: axis {} on input device \"{}\" with ID {} detected", - PrintAxisIds(axis.ids), deviceName, m_deviceId); + PrintAxisIds(m_axes.back().ids), deviceName, m_deviceId); } else CLog::Log(LOGWARNING, @@ -152,29 +152,29 @@ bool CAndroidJoystickState::Initialize(const CJNIViewInputDevice& inputDevice) } // add the usual suspects - m_buttons.push_back({{AKEYCODE_BUTTON_A}}); - m_buttons.push_back({{AKEYCODE_BUTTON_B}}); - m_buttons.push_back({{AKEYCODE_BUTTON_C}}); - m_buttons.push_back({{AKEYCODE_BUTTON_X}}); - m_buttons.push_back({{AKEYCODE_BUTTON_Y}}); - m_buttons.push_back({{AKEYCODE_BUTTON_Z}}); - m_buttons.push_back({{AKEYCODE_BACK}}); - m_buttons.push_back({{AKEYCODE_MENU}}); - m_buttons.push_back({{AKEYCODE_HOME}}); - m_buttons.push_back({{AKEYCODE_BUTTON_SELECT}}); - m_buttons.push_back({{AKEYCODE_BUTTON_MODE}}); - m_buttons.push_back({{AKEYCODE_BUTTON_START}}); - m_buttons.push_back({{AKEYCODE_BUTTON_L1}}); - m_buttons.push_back({{AKEYCODE_BUTTON_R1}}); - m_buttons.push_back({{AKEYCODE_BUTTON_L2}}); - m_buttons.push_back({{AKEYCODE_BUTTON_R2}}); - m_buttons.push_back({{AKEYCODE_BUTTON_THUMBL}}); - m_buttons.push_back({{AKEYCODE_BUTTON_THUMBR}}); - m_buttons.push_back({{AKEYCODE_DPAD_UP}}); - m_buttons.push_back({{AKEYCODE_DPAD_RIGHT}}); - m_buttons.push_back({{AKEYCODE_DPAD_DOWN}}); - m_buttons.push_back({{AKEYCODE_DPAD_LEFT}}); - m_buttons.push_back({{AKEYCODE_DPAD_CENTER}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_A}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_B}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_C}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_X}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_Y}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_Z}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BACK}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_MENU}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_HOME}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_SELECT}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_MODE}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_START}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_L1}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_R1}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_L2}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_R2}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_THUMBL}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_THUMBR}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_DPAD_UP}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_DPAD_RIGHT}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_DPAD_DOWN}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_DPAD_LEFT}}); + m_buttons.emplace_back(JoystickAxis{{AKEYCODE_DPAD_CENTER}}); // check if there are no buttons or axes at all if (GetButtonCount() == 0 && GetAxisCount() == 0) @@ -238,7 +238,7 @@ bool CAndroidJoystickState::ProcessEvent(const AInputEvent* event) std::vector values; values.reserve(axis.ids.size()); for (const auto& axisId : axis.ids) - values.push_back(AMotionEvent_getAxisValue(event, axisId, pointer)); + values.emplace_back(AMotionEvent_getAxisValue(event, axisId, pointer)); // remove all zero values values.erase(std::remove(values.begin(), values.end(), 0.0f), values.end()); diff --git a/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp b/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp index 753dc710998e9..4fef6fed69fc8 100644 --- a/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp +++ b/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp @@ -208,7 +208,7 @@ void CPeripheralBusAndroid::OnInputDeviceAdded(int deviceId) PeripheralScanResult result; if (!ConvertToPeripheralScanResult(device, result)) return; - m_scanResults.m_results.push_back(result); + m_scanResults.m_results.emplace_back(std::move(result)); } CLog::Log(LOGDEBUG, "CPeripheralBusAndroid: input device with ID {} added", deviceId); @@ -345,7 +345,7 @@ PeripheralScanResults CPeripheralBusAndroid::GetInputDevices() continue; CLog::Log(LOGINFO, "CPeripheralBusAndroid: added input device"); - results.m_results.push_back(result); + results.m_results.emplace_back(std::move(result)); } return results; From d23e6184029698e234e464c1553ce1ee214256f6 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Tue, 30 Jan 2024 13:23:30 -0800 Subject: [PATCH 215/651] [Android][Peripherals] Initialize buttonmap with Android mapping, if possible --- xbmc/games/controllers/ControllerIDs.h | 11 + xbmc/peripherals/Peripherals.cpp | 2 +- xbmc/peripherals/addons/AddonButtonMap.cpp | 29 +- xbmc/peripherals/addons/AddonButtonMap.h | 5 +- .../peripherals/addons/AddonButtonMapping.cpp | 2 +- .../peripherals/addons/AddonInputHandling.cpp | 24 +- xbmc/peripherals/addons/AddonInputHandling.h | 11 +- xbmc/peripherals/bus/PeripheralBus.h | 17 ++ xbmc/peripherals/devices/Peripheral.cpp | 6 +- .../devices/PeripheralJoystick.cpp | 3 +- .../peripherals/AndroidJoystickState.cpp | 265 ++++++++++++++++-- .../peripherals/AndroidJoystickState.h | 30 ++ .../peripherals/AndroidJoystickTranslator.cpp | 52 ++++ .../peripherals/AndroidJoystickTranslator.h | 9 + .../peripherals/PeripheralBusAndroid.cpp | 45 +++ .../peripherals/PeripheralBusAndroid.h | 2 + 16 files changed, 470 insertions(+), 43 deletions(-) diff --git a/xbmc/games/controllers/ControllerIDs.h b/xbmc/games/controllers/ControllerIDs.h index a9254e015dfd3..68d100d156134 100644 --- a/xbmc/games/controllers/ControllerIDs.h +++ b/xbmc/games/controllers/ControllerIDs.h @@ -13,3 +13,14 @@ #define DEFAULT_KEYBOARD_ID "game.controller.keyboard" #define DEFAULT_MOUSE_ID "game.controller.mouse" #define DEFAULT_REMOTE_ID "game.controller.remote" + +namespace KODI +{ +namespace GAME +{ + +// Used to set the appearance of PlayStation controllers +constexpr const char* CONTROLLER_ID_PLAYSTATION = "game.controller.ps.dualanalog"; + +} // namespace GAME +} // namespace KODI diff --git a/xbmc/peripherals/Peripherals.cpp b/xbmc/peripherals/Peripherals.cpp index e282fbf70040d..1276cf52d6763 100644 --- a/xbmc/peripherals/Peripherals.cpp +++ b/xbmc/peripherals/Peripherals.cpp @@ -917,7 +917,7 @@ void CPeripherals::ResetButtonMaps(const std::string& controllerId) PeripheralAddonPtr addon; if (addonBus->GetAddonWithButtonMap(peripheral.get(), addon)) { - CAddonButtonMap buttonMap(peripheral.get(), addon, controllerId); + CAddonButtonMap buttonMap(peripheral.get(), addon, controllerId, *this); buttonMap.Reset(); } } diff --git a/xbmc/peripherals/addons/AddonButtonMap.cpp b/xbmc/peripherals/addons/AddonButtonMap.cpp index e9764ba81cb25..0471d293eb048 100644 --- a/xbmc/peripherals/addons/AddonButtonMap.cpp +++ b/xbmc/peripherals/addons/AddonButtonMap.cpp @@ -10,6 +10,7 @@ #include "PeripheralAddonTranslator.h" #include "input/joysticks/JoystickUtils.h" +#include "peripherals/Peripherals.h" #include "peripherals/devices/Peripheral.h" #include "utils/log.h" @@ -24,8 +25,9 @@ using namespace PERIPHERALS; CAddonButtonMap::CAddonButtonMap(CPeripheral* device, const std::weak_ptr& addon, - const std::string& strControllerId) - : m_device(device), m_addon(addon), m_strControllerId(strControllerId) + const std::string& strControllerId, + CPeripherals& manager) + : m_device(device), m_addon(addon), m_strControllerId(strControllerId), m_manager(manager) { auto peripheralAddon = m_addon.lock(); assert(peripheralAddon != nullptr); @@ -59,6 +61,29 @@ bool CAddonButtonMap::Load(void) bSuccess |= addon->GetIgnoredPrimitives(m_device, ignoredPrimitives); } + if (features.empty()) + { + // Check if we can initialize a buttonmap from the peripheral bus + PeripheralBusPtr peripheralBus = m_manager.GetBusByType(m_device->GetBusType()); + if (peripheralBus) + { + CLog::Log(LOGDEBUG, + "Buttonmap not found for {}, attempting to initialize from peripheral bus", + m_device->Location()); + if (peripheralBus->InitializeButtonMap(*m_device, *this)) + { + bSuccess = true; + + if (auto addon = m_addon.lock()) + { + addon->GetAppearance(m_device, controllerAppearance); + addon->GetFeatures(m_device, m_strControllerId, features); + addon->GetIgnoredPrimitives(m_device, ignoredPrimitives); + } + } + } + } + // GetFeatures() was changed to always return false if no features were // retrieved. Check here, just in case its contract is changed or violated in // the future. diff --git a/xbmc/peripherals/addons/AddonButtonMap.h b/xbmc/peripherals/addons/AddonButtonMap.h index b4b6463feec03..7bbc91947f92e 100644 --- a/xbmc/peripherals/addons/AddonButtonMap.h +++ b/xbmc/peripherals/addons/AddonButtonMap.h @@ -18,6 +18,7 @@ namespace PERIPHERALS { class CPeripheral; +class CPeripherals; /*! * \ingroup peripherals @@ -27,7 +28,8 @@ class CAddonButtonMap : public KODI::JOYSTICK::IButtonMap public: CAddonButtonMap(CPeripheral* device, const std::weak_ptr& addon, - const std::string& strControllerId); + const std::string& strControllerId, + CPeripherals& manager); ~CAddonButtonMap(void) override; @@ -133,6 +135,7 @@ class CAddonButtonMap : public KODI::JOYSTICK::IButtonMap CPeripheral* const m_device; const std::weak_ptr m_addon; const std::string m_strControllerId; + CPeripherals& m_manager; // Button map state std::string m_controllerAppearance; diff --git a/xbmc/peripherals/addons/AddonButtonMapping.cpp b/xbmc/peripherals/addons/AddonButtonMapping.cpp index 3be55df3f6051..0da424ab27087 100644 --- a/xbmc/peripherals/addons/AddonButtonMapping.cpp +++ b/xbmc/peripherals/addons/AddonButtonMapping.cpp @@ -33,7 +33,7 @@ CAddonButtonMapping::CAddonButtonMapping(CPeripherals& manager, else { const std::string controllerId = mapper->ControllerID(); - m_buttonMap = std::make_unique(peripheral, addon, controllerId); + m_buttonMap = std::make_unique(peripheral, addon, controllerId, manager); if (m_buttonMap->Load()) { KEYMAP::IKeymap* keymap = peripheral->GetKeymap(controllerId); diff --git a/xbmc/peripherals/addons/AddonInputHandling.cpp b/xbmc/peripherals/addons/AddonInputHandling.cpp index 088d01a2f1dae..4643e84e3ff5b 100644 --- a/xbmc/peripherals/addons/AddonInputHandling.cpp +++ b/xbmc/peripherals/addons/AddonInputHandling.cpp @@ -24,28 +24,38 @@ using namespace KODI; using namespace JOYSTICK; using namespace PERIPHERALS; -CAddonInputHandling::CAddonInputHandling(CPeripheral* peripheral, +CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, std::shared_ptr addon, IInputHandler* handler, IDriverReceiver* receiver) - : m_peripheral(peripheral), + : m_manager(manager), + m_peripheral(peripheral), m_addon(std::move(addon)), m_joystickInputHandler(handler), m_joystickDriverReceiver(receiver) { } -CAddonInputHandling::CAddonInputHandling(CPeripheral* peripheral, +CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, std::shared_ptr addon, KEYBOARD::IKeyboardInputHandler* handler) - : m_peripheral(peripheral), m_addon(std::move(addon)), m_keyboardInputHandler(handler) + : m_manager(manager), + m_peripheral(peripheral), + m_addon(std::move(addon)), + m_keyboardInputHandler(handler) { } -CAddonInputHandling::CAddonInputHandling(CPeripheral* peripheral, +CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, std::shared_ptr addon, MOUSE::IMouseInputHandler* handler) - : m_peripheral(peripheral), m_addon(std::move(addon)), m_mouseInputHandler(handler) + : m_manager(manager), + m_peripheral(peripheral), + m_addon(std::move(addon)), + m_mouseInputHandler(handler) { } @@ -69,7 +79,7 @@ bool CAddonInputHandling::Load() controllerId = m_mouseInputHandler->ControllerID(); if (!controllerId.empty()) - m_buttonMap = std::make_unique(m_peripheral, m_addon, controllerId); + m_buttonMap = std::make_unique(m_peripheral, m_addon, controllerId, m_manager); if (m_buttonMap && m_buttonMap->Load()) { diff --git a/xbmc/peripherals/addons/AddonInputHandling.h b/xbmc/peripherals/addons/AddonInputHandling.h index e5c98e37482ac..19816a6ca521a 100644 --- a/xbmc/peripherals/addons/AddonInputHandling.h +++ b/xbmc/peripherals/addons/AddonInputHandling.h @@ -38,6 +38,7 @@ class IMouseInputHandler; namespace PERIPHERALS { class CPeripheral; +class CPeripherals; class CPeripheralAddon; /*! @@ -49,16 +50,19 @@ class CAddonInputHandling : public KODI::JOYSTICK::IDriverHandler, public KODI::MOUSE::IMouseDriverHandler { public: - CAddonInputHandling(CPeripheral* peripheral, + CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, std::shared_ptr addon, KODI::JOYSTICK::IInputHandler* handler, KODI::JOYSTICK::IDriverReceiver* receiver); - CAddonInputHandling(CPeripheral* peripheral, + CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, std::shared_ptr addon, KODI::KEYBOARD::IKeyboardInputHandler* handler); - CAddonInputHandling(CPeripheral* peripheral, + CAddonInputHandling(CPeripherals& manager, + CPeripheral* peripheral, std::shared_ptr addon, KODI::MOUSE::IMouseInputHandler* handler); @@ -89,6 +93,7 @@ class CAddonInputHandling : public KODI::JOYSTICK::IDriverHandler, private: // Construction parameters + CPeripherals& m_manager; CPeripheral* const m_peripheral; const std::shared_ptr m_addon; KODI::JOYSTICK::IInputHandler* const m_joystickInputHandler{nullptr}; diff --git a/xbmc/peripherals/bus/PeripheralBus.h b/xbmc/peripherals/bus/PeripheralBus.h index 424d012d9fd92..33594a0faa8ef 100644 --- a/xbmc/peripherals/bus/PeripheralBus.h +++ b/xbmc/peripherals/bus/PeripheralBus.h @@ -17,6 +17,14 @@ class CFileItemList; +namespace KODI +{ +namespace JOYSTICK +{ +class IButtonMap; +} // namespace JOYSTICK +} // namespace KODI + namespace PERIPHERALS { class CPeripheral; @@ -59,6 +67,15 @@ class CPeripheralBus : protected CThread */ virtual bool InitializeProperties(CPeripheral& peripheral); + /*! + * \brief Initialize a joystick buttonmap, if possible + */ + virtual bool InitializeButtonMap(const CPeripheral& peripheral, + KODI::JOYSTICK::IButtonMap& buttonMap) const + { + return false; + } + /*! * @brief Get the instance of the peripheral at the given location. * @param strLocation The location. diff --git a/xbmc/peripherals/devices/Peripheral.cpp b/xbmc/peripherals/devices/Peripheral.cpp index ff9910c2ef4d9..bb00d516a3468 100644 --- a/xbmc/peripherals/devices/Peripheral.cpp +++ b/xbmc/peripherals/devices/Peripheral.cpp @@ -597,7 +597,7 @@ void CPeripheral::RegisterInputHandler(IInputHandler* handler, bool bPromiscuous if (addon) { std::unique_ptr addonInput = std::make_unique( - this, std::move(addon), handler, GetDriverReceiver()); + m_manager, this, std::move(addon), handler, GetDriverReceiver()); if (addonInput->Load()) { RegisterJoystickDriverHandler(addonInput.get(), bPromiscuous); @@ -638,7 +638,7 @@ void CPeripheral::RegisterKeyboardHandler(KEYBOARD::IKeyboardInputHandler* handl if (addon) { std::unique_ptr addonInput = - std::make_unique(this, std::move(addon), handler); + std::make_unique(m_manager, this, std::move(addon), handler); if (addonInput->Load()) keyboardDriverHandler = std::move(addonInput); } @@ -689,7 +689,7 @@ void CPeripheral::RegisterMouseHandler(MOUSE::IMouseInputHandler* handler, if (addon) { std::unique_ptr addonInput = - std::make_unique(this, std::move(addon), handler); + std::make_unique(m_manager, this, std::move(addon), handler); if (addonInput->Load()) mouseDriverHandler = std::move(addonInput); } diff --git a/xbmc/peripherals/devices/PeripheralJoystick.cpp b/xbmc/peripherals/devices/PeripheralJoystick.cpp index a6029561d64e1..c5285c245eec7 100644 --- a/xbmc/peripherals/devices/PeripheralJoystick.cpp +++ b/xbmc/peripherals/devices/PeripheralJoystick.cpp @@ -92,7 +92,8 @@ bool CPeripheralJoystick::InitialiseFeature(const PeripheralFeature feature) if (bSuccess) { - m_buttonMap = std::make_unique(this, addon, DEFAULT_CONTROLLER_ID); + m_buttonMap = + std::make_unique(this, addon, DEFAULT_CONTROLLER_ID, m_manager); if (m_buttonMap->Load()) { InitializeDeadzoneFiltering(*m_buttonMap); diff --git a/xbmc/platform/android/peripherals/AndroidJoystickState.cpp b/xbmc/platform/android/peripherals/AndroidJoystickState.cpp index 1a8244fbc9ea4..c3032b4352201 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickState.cpp +++ b/xbmc/platform/android/peripherals/AndroidJoystickState.cpp @@ -9,6 +9,11 @@ #include "AndroidJoystickState.h" #include "AndroidJoystickTranslator.h" +#include "games/controllers/ControllerIDs.h" +#include "games/controllers/DefaultController.h" +#include "input/joysticks/DriverPrimitive.h" +#include "input/joysticks/JoystickTypes.h" +#include "input/joysticks/interfaces/IButtonMap.h" #include "utils/StringUtils.h" #include "utils/log.h" @@ -19,8 +24,42 @@ #include #include +using namespace KODI; using namespace PERIPHERALS; +namespace +{ +// clang-format off +static const std::vector ButtonKeycodes{ + // add the usual suspects + AKEYCODE_BUTTON_A, + AKEYCODE_BUTTON_B, + AKEYCODE_BUTTON_C, + AKEYCODE_BUTTON_X, + AKEYCODE_BUTTON_Y, + AKEYCODE_BUTTON_Z, + AKEYCODE_BACK, + AKEYCODE_MENU, + AKEYCODE_HOME, + AKEYCODE_BUTTON_SELECT, + AKEYCODE_BUTTON_MODE, + AKEYCODE_BUTTON_START, + AKEYCODE_BUTTON_L1, + AKEYCODE_BUTTON_R1, + AKEYCODE_BUTTON_L2, + AKEYCODE_BUTTON_R2, + AKEYCODE_BUTTON_THUMBL, + AKEYCODE_BUTTON_THUMBR, + AKEYCODE_DPAD_UP, + AKEYCODE_DPAD_RIGHT, + AKEYCODE_DPAD_DOWN, + AKEYCODE_DPAD_LEFT, + AKEYCODE_DPAD_CENTER, + // only add additional buttons at the end of the list +}; +// clang-format on +} // namespace + static std::string PrintAxisIds(const std::vector& axisIds) { if (axisIds.empty()) @@ -151,30 +190,9 @@ bool CAndroidJoystickState::Initialize(const CJNIViewInputDevice& inputDevice) axisId, deviceName, m_deviceId); } - // add the usual suspects - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_A}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_B}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_C}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_X}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_Y}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_Z}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BACK}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_MENU}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_HOME}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_SELECT}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_MODE}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_START}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_L1}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_R1}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_L2}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_R2}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_THUMBL}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_BUTTON_THUMBR}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_DPAD_UP}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_DPAD_RIGHT}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_DPAD_DOWN}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_DPAD_LEFT}}); - m_buttons.emplace_back(JoystickAxis{{AKEYCODE_DPAD_CENTER}}); + // map buttons + for (int buttonKeycode : ButtonKeycodes) + m_buttons.emplace_back(JoystickAxis{{buttonKeycode}}); // check if there are no buttons or axes at all if (GetButtonCount() == 0 && GetAxisCount() == 0) @@ -200,6 +218,67 @@ void CAndroidJoystickState::Deinitialize(void) m_digitalEvents.clear(); } +bool CAndroidJoystickState::InitializeButtonMap(JOYSTICK::IButtonMap& buttonMap) const +{ + // We only map the default controller + if (buttonMap.ControllerID() != DEFAULT_CONTROLLER_ID) + return false; + + bool success = false; + + // Map buttons + for (int buttonKeycode : ButtonKeycodes) + success |= MapButton(buttonMap, buttonKeycode); + + // Map D-pad + success |= MapDpad(buttonMap, AMOTION_EVENT_AXIS_HAT_X, AMOTION_EVENT_AXIS_HAT_Y); + + // Map triggers + // Note: This should come after buttons, because the PS4 controller uses + // both a digital button and an analog axis for the triggers, and we want + // the analog axis to override the button for full range of motion. + success |= MapTrigger(buttonMap, AMOTION_EVENT_AXIS_LTRIGGER, + GAME::CDefaultController::FEATURE_LEFT_TRIGGER); + success |= MapTrigger(buttonMap, AMOTION_EVENT_AXIS_RTRIGGER, + GAME::CDefaultController::FEATURE_RIGHT_TRIGGER); + + // Map analog sticks + success |= MapAnalogStick(buttonMap, AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y, + GAME::CDefaultController::FEATURE_LEFT_STICK); + success |= MapAnalogStick(buttonMap, AMOTION_EVENT_AXIS_Z, AMOTION_EVENT_AXIS_RZ, + GAME::CDefaultController::FEATURE_RIGHT_STICK); + + if (success) + { + // If the controller has both L2/R2 buttons and LTRIGGER/RTRIGGER axes, it's + // probably a PS controller + size_t indexL2 = 0; + size_t indexR2 = 0; + size_t indexLTrigger = 0; + size_t indexRTrigger = 0; + if (GetAxesIndex({AKEYCODE_BUTTON_L2}, m_buttons, indexL2) && + GetAxesIndex({AKEYCODE_BUTTON_R2}, m_buttons, indexR2) && + GetAxesIndex({AMOTION_EVENT_AXIS_LTRIGGER}, m_axes, indexLTrigger) && + GetAxesIndex({AMOTION_EVENT_AXIS_RTRIGGER}, m_axes, indexRTrigger)) + { + CLog::Log(LOGDEBUG, "Detected dual-input triggers, ignoring digital buttons"); + std::vector ignoredPrimitives{ + {JOYSTICK::PRIMITIVE_TYPE::BUTTON, static_cast(indexL2)}, + {JOYSTICK::PRIMITIVE_TYPE::BUTTON, static_cast(indexR2)}, + }; + buttonMap.SetIgnoredPrimitives(ignoredPrimitives); + + CLog::Log(LOGDEBUG, "Setting appearance to {}", GAME::CONTROLLER_ID_PLAYSTATION); + buttonMap.SetAppearance(GAME::CONTROLLER_ID_PLAYSTATION); + } + + // Save the buttonmap + buttonMap.SaveButtonMap(); + } + + return success; +} + bool CAndroidJoystickState::ProcessEvent(const AInputEvent* event) { int32_t type = AInputEvent_getType(event); @@ -334,6 +413,144 @@ bool CAndroidJoystickState::SetAxisValue(const std::vector& axisIds, return true; } +bool CAndroidJoystickState::MapButton(JOYSTICK::IButtonMap& buttonMap, int buttonKeycode) const +{ + size_t buttonIndex = 0; + std::string featureName; + + if (!GetAxesIndex({buttonKeycode}, m_buttons, buttonIndex)) + return false; + + // Check if button is already mapped + JOYSTICK::CDriverPrimitive buttonPrimitive{JOYSTICK::PRIMITIVE_TYPE::BUTTON, + static_cast(buttonIndex)}; + if (buttonMap.GetFeature(buttonPrimitive, featureName)) + return false; + + // Translate the button + std::string controllerButton = CAndroidJoystickTranslator::TranslateJoystickButton(buttonKeycode); + if (controllerButton.empty()) + return false; + + // Map the button + CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", controllerButton, + buttonPrimitive.ToString()); + buttonMap.AddScalar(controllerButton, buttonPrimitive); + + return true; +} + +bool CAndroidJoystickState::MapTrigger(JOYSTICK::IButtonMap& buttonMap, + int axisId, + const std::string& triggerName) const +{ + size_t axisIndex = 0; + std::string featureName; + + if (!GetAxesIndex({axisId}, m_axes, axisIndex)) + return false; + + const JOYSTICK::CDriverPrimitive semiaxis{static_cast(axisIndex), 0, + JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1}; + if (buttonMap.GetFeature(semiaxis, featureName)) + return false; + + CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", triggerName, semiaxis.ToString()); + buttonMap.AddScalar(triggerName, semiaxis); + + return true; +} + +bool CAndroidJoystickState::MapDpad(JOYSTICK::IButtonMap& buttonMap, + int horizAxisId, + int vertAxisId) const +{ + bool success = false; + + size_t axisIndex = 0; + std::string featureName; + + // Map horizontal axis + if (GetAxesIndex({horizAxisId}, m_axes, axisIndex)) + { + const JOYSTICK::CDriverPrimitive positiveSemiaxis{static_cast(axisIndex), 0, + JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1}; + const JOYSTICK::CDriverPrimitive negativeSemiaxis{static_cast(axisIndex), 0, + JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE, 1}; + if (!buttonMap.GetFeature(positiveSemiaxis, featureName) && + !buttonMap.GetFeature(negativeSemiaxis, featureName)) + { + CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_LEFT, + negativeSemiaxis.ToString()); + CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_RIGHT, + positiveSemiaxis.ToString()); + buttonMap.AddScalar(GAME::CDefaultController::FEATURE_LEFT, negativeSemiaxis); + buttonMap.AddScalar(GAME::CDefaultController::FEATURE_RIGHT, positiveSemiaxis); + success |= true; + } + } + + // Map vertical axis + if (GetAxesIndex({vertAxisId}, m_axes, axisIndex)) + { + const JOYSTICK::CDriverPrimitive positiveSemiaxis{static_cast(axisIndex), 0, + JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1}; + const JOYSTICK::CDriverPrimitive negativeSemiaxis{static_cast(axisIndex), 0, + JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE, 1}; + if (!buttonMap.GetFeature(positiveSemiaxis, featureName) && + !buttonMap.GetFeature(negativeSemiaxis, featureName)) + { + CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_UP, + negativeSemiaxis.ToString()); + CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", GAME::CDefaultController::FEATURE_DOWN, + positiveSemiaxis.ToString()); + buttonMap.AddScalar(GAME::CDefaultController::FEATURE_DOWN, positiveSemiaxis); + buttonMap.AddScalar(GAME::CDefaultController::FEATURE_UP, negativeSemiaxis); + success |= true; + } + } + + return success; +} + +bool CAndroidJoystickState::MapAnalogStick(JOYSTICK::IButtonMap& buttonMap, + int horizAxisId, + int vertAxisId, + const std::string& analogStickName) const +{ + size_t axisIndex1 = 0; + size_t axisIndex2 = 0; + std::string featureName; + + if (!GetAxesIndex({horizAxisId}, m_axes, axisIndex1) || + !GetAxesIndex({vertAxisId}, m_axes, axisIndex2)) + return false; + + const JOYSTICK::CDriverPrimitive upSemiaxis{static_cast(axisIndex2), 0, + JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE, 1}; + const JOYSTICK::CDriverPrimitive downSemiaxis{static_cast(axisIndex2), 0, + JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1}; + const JOYSTICK::CDriverPrimitive leftSemiaxis{static_cast(axisIndex1), 0, + JOYSTICK::SEMIAXIS_DIRECTION::NEGATIVE, 1}; + const JOYSTICK::CDriverPrimitive rightSemiaxis{static_cast(axisIndex1), 0, + JOYSTICK::SEMIAXIS_DIRECTION::POSITIVE, 1}; + if (buttonMap.GetFeature(upSemiaxis, featureName) || + buttonMap.GetFeature(downSemiaxis, featureName) || + buttonMap.GetFeature(leftSemiaxis, featureName) || + buttonMap.GetFeature(rightSemiaxis, featureName)) + return false; + + CLog::Log(LOGDEBUG, "Automatically mapping {} to [{}, {}, {}, {}]", analogStickName, + upSemiaxis.ToString(), downSemiaxis.ToString(), leftSemiaxis.ToString(), + rightSemiaxis.ToString()); + buttonMap.AddAnalogStick(analogStickName, JOYSTICK::ANALOG_STICK_DIRECTION::UP, upSemiaxis); + buttonMap.AddAnalogStick(analogStickName, JOYSTICK::ANALOG_STICK_DIRECTION::DOWN, downSemiaxis); + buttonMap.AddAnalogStick(analogStickName, JOYSTICK::ANALOG_STICK_DIRECTION::LEFT, leftSemiaxis); + buttonMap.AddAnalogStick(analogStickName, JOYSTICK::ANALOG_STICK_DIRECTION::RIGHT, rightSemiaxis); + + return true; +} + float CAndroidJoystickState::Contain(float value, float min, float max) { if (value < min) diff --git a/xbmc/platform/android/peripherals/AndroidJoystickState.h b/xbmc/platform/android/peripherals/AndroidJoystickState.h index e146193541c6d..550222d6f76a9 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickState.h +++ b/xbmc/platform/android/peripherals/AndroidJoystickState.h @@ -18,6 +18,14 @@ struct AInputEvent; class CJNIViewInputDevice; +namespace KODI +{ +namespace JOYSTICK +{ +class IButtonMap; +} // namespace JOYSTICK +} // namespace KODI + namespace PERIPHERALS { class CAndroidJoystickState @@ -39,6 +47,18 @@ class CAndroidJoystickState */ bool Initialize(const CJNIViewInputDevice& inputDevice); + /*! + * \brief Initialize a joystick buttonmap, if possible + * + * Android has a large database of buttonmaps, which it uses to provide + * mapped button keycodes such as AKEYCODE_BUTTON_A. We can take advantage of + * this to initialize a default buttonmap based on these mappings. + * + * If Android can't map the buttons, it will use generic button keycodes such + * as AKEYCODE_BUTTON_1, in which case we can't initialize the buttonmap. + */ + bool InitializeButtonMap(KODI::JOYSTICK::IButtonMap& buttonMap) const; + /*! * \brief Deinitialize the joystick object * @@ -63,6 +83,16 @@ class CAndroidJoystickState void GetButtonEvents(std::vector& events); void GetAxisEvents(std::vector& events) const; + bool MapButton(KODI::JOYSTICK::IButtonMap& buttonMap, int buttonKeycode) const; + bool MapTrigger(KODI::JOYSTICK::IButtonMap& buttonMap, + int axisId, + const std::string& triggerName) const; + bool MapDpad(KODI::JOYSTICK::IButtonMap& buttonMap, int horizAxisId, int vertAxisId) const; + bool MapAnalogStick(KODI::JOYSTICK::IButtonMap& buttonMap, + int horizAxisId, + int vertAxisId, + const std::string& analogStickName) const; + static float Contain(float value, float min, float max); static float Scale(float value, float max, float scaledMax); static float Deadzone(float value, float deadzone); diff --git a/xbmc/platform/android/peripherals/AndroidJoystickTranslator.cpp b/xbmc/platform/android/peripherals/AndroidJoystickTranslator.cpp index 3717616413ec5..f8f638cbdef78 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickTranslator.cpp +++ b/xbmc/platform/android/peripherals/AndroidJoystickTranslator.cpp @@ -8,9 +8,12 @@ #include "AndroidJoystickTranslator.h" +#include "games/controllers/DefaultController.h" + #include #include +using namespace KODI; using namespace PERIPHERALS; const char* CAndroidJoystickTranslator::TranslateAxis(int axisId) @@ -726,3 +729,52 @@ const char* CAndroidJoystickTranslator::TranslateKeyCode(int keyCode) return "unknown"; } + +const char* CAndroidJoystickTranslator::TranslateJoystickButton(int buttonKeycode) +{ + switch (buttonKeycode) + { + case AKEYCODE_BUTTON_A: + case AKEYCODE_DPAD_CENTER: + return GAME::CDefaultController::FEATURE_A; + case AKEYCODE_BUTTON_B: + return GAME::CDefaultController::FEATURE_B; + case AKEYCODE_BUTTON_X: + return GAME::CDefaultController::FEATURE_X; + case AKEYCODE_BUTTON_Y: + return GAME::CDefaultController::FEATURE_Y; + case AKEYCODE_BUTTON_START: + case AKEYCODE_MENU: + return GAME::CDefaultController::FEATURE_START; + case AKEYCODE_BUTTON_SELECT: + case AKEYCODE_BACK: + return GAME::CDefaultController::FEATURE_BACK; + case AKEYCODE_BUTTON_MODE: + case AKEYCODE_HOME: + return GAME::CDefaultController::FEATURE_GUIDE; + case AKEYCODE_DPAD_UP: + return GAME::CDefaultController::FEATURE_UP; + case AKEYCODE_DPAD_RIGHT: + return GAME::CDefaultController::FEATURE_RIGHT; + case AKEYCODE_DPAD_DOWN: + return GAME::CDefaultController::FEATURE_DOWN; + case AKEYCODE_DPAD_LEFT: + return GAME::CDefaultController::FEATURE_LEFT; + case AKEYCODE_BUTTON_L1: + return GAME::CDefaultController::FEATURE_LEFT_BUMPER; + case AKEYCODE_BUTTON_R1: + return GAME::CDefaultController::FEATURE_RIGHT_BUMPER; + case AKEYCODE_BUTTON_L2: + return GAME::CDefaultController::FEATURE_LEFT_TRIGGER; + case AKEYCODE_BUTTON_R2: + return GAME::CDefaultController::FEATURE_RIGHT_TRIGGER; + case AKEYCODE_BUTTON_THUMBL: + return GAME::CDefaultController::FEATURE_LEFT_THUMB; + case AKEYCODE_BUTTON_THUMBR: + return GAME::CDefaultController::FEATURE_RIGHT_THUMB; + default: + break; + } + + return ""; +} diff --git a/xbmc/platform/android/peripherals/AndroidJoystickTranslator.h b/xbmc/platform/android/peripherals/AndroidJoystickTranslator.h index a5bb8e33d5c4d..8681de0c123c1 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickTranslator.h +++ b/xbmc/platform/android/peripherals/AndroidJoystickTranslator.h @@ -30,5 +30,14 @@ class CAndroidJoystickTranslator * \return The translated enum label, or "unknown" if unknown */ static const char* TranslateKeyCode(int keyCode); + + /*! + * \brief Translate a button key code to a feature on the default controller + * + * \param buttonKeycode The key code given in + * + * \return The translated feature, or "" if unknown + */ + static const char* TranslateJoystickButton(int buttonKeycode); }; } // namespace PERIPHERALS diff --git a/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp b/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp index 4fef6fed69fc8..f8282eeb55d0a 100644 --- a/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp +++ b/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp @@ -16,6 +16,7 @@ #include "utils/log.h" #include "platform/android/activity/XBMCApp.h" +#include "platform/android/peripherals/AndroidJoystickState.h" #include #include @@ -120,6 +121,50 @@ bool CPeripheralBusAndroid::InitializeProperties(CPeripheral& peripheral) return true; } +bool CPeripheralBusAndroid::InitializeButtonMap(const CPeripheral& peripheral, + JOYSTICK::IButtonMap& buttonMap) const +{ + int deviceId; + if (!GetDeviceId(peripheral.Location(), deviceId)) + { + CLog::Log(LOGWARNING, + "CPeripheralBusAndroid: failed to initialize buttonmap due to unknown device ID for " + "peripheral \"{}\"", + peripheral.Location()); + return false; + } + + // get the joystick state + auto it = m_joystickStates.find(deviceId); + if (it == m_joystickStates.end()) + { + CLog::Log(LOGWARNING, + "CPeripheralBusAndroid: joystick with device ID {} not found for peripheral \"{}\"", + deviceId, peripheral.Location()); + return false; + } + + const CAndroidJoystickState& joystick = it->second; + if (joystick.GetButtonCount() == 0 && joystick.GetAxisCount() == 0) + { + CLog::Log(LOGDEBUG, + "CPeripheralBusAndroid: joystick has no buttons or axes for peripheral \"{}\"", + peripheral.Location()); + return false; + } + + if (!joystick.InitializeButtonMap(buttonMap)) + { + CLog::Log( + LOGDEBUG, + "CPeripheralBusAndroid: failed to initialize joystick buttonmap for peripheral \"{}\"", + peripheral.Location()); + return false; + } + + return true; +} + void CPeripheralBusAndroid::Initialise(void) { CPeripheralBus::Initialise(); diff --git a/xbmc/platform/android/peripherals/PeripheralBusAndroid.h b/xbmc/platform/android/peripherals/PeripheralBusAndroid.h index c3c42aa73038a..5ee9480970d8b 100644 --- a/xbmc/platform/android/peripherals/PeripheralBusAndroid.h +++ b/xbmc/platform/android/peripherals/PeripheralBusAndroid.h @@ -35,6 +35,8 @@ class CPeripheralBusAndroid : public CPeripheralBus, // specialisation of CPeripheralBus bool InitializeProperties(CPeripheral& peripheral) override; + bool InitializeButtonMap(const CPeripheral& peripheral, + KODI::JOYSTICK::IButtonMap& buttonMap) const override; void Initialise(void) override; void ProcessEvents() override; From 78de029d6bc0fab890eef93d17863664eb411316 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Sun, 28 Jan 2024 19:09:47 -0800 Subject: [PATCH 216/651] [Android][Peripherals] Fix input for generic buttons and axes --- .../peripherals/AndroidJoystickState.cpp | 127 +++++++++++------- 1 file changed, 82 insertions(+), 45 deletions(-) diff --git a/xbmc/platform/android/peripherals/AndroidJoystickState.cpp b/xbmc/platform/android/peripherals/AndroidJoystickState.cpp index c3032b4352201..9e622d4d73af0 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickState.cpp +++ b/xbmc/platform/android/peripherals/AndroidJoystickState.cpp @@ -55,33 +55,63 @@ static const std::vector ButtonKeycodes{ AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_CENTER, + // add generic gamepad buttons for controllers that Android doesn't know + // how to map + AKEYCODE_BUTTON_1, + AKEYCODE_BUTTON_2, + AKEYCODE_BUTTON_3, + AKEYCODE_BUTTON_4, + AKEYCODE_BUTTON_5, + AKEYCODE_BUTTON_6, + AKEYCODE_BUTTON_7, + AKEYCODE_BUTTON_8, + AKEYCODE_BUTTON_9, + AKEYCODE_BUTTON_10, + AKEYCODE_BUTTON_11, + AKEYCODE_BUTTON_12, + AKEYCODE_BUTTON_13, + AKEYCODE_BUTTON_14, + AKEYCODE_BUTTON_15, + AKEYCODE_BUTTON_16, // only add additional buttons at the end of the list }; // clang-format on -} // namespace - -static std::string PrintAxisIds(const std::vector& axisIds) -{ - if (axisIds.empty()) - return ""; - - if (axisIds.size() == 1) - return std::to_string(axisIds.front()); - - std::string strAxisIds; - for (const auto& axisId : axisIds) - { - if (strAxisIds.empty()) - strAxisIds = "["; - else - strAxisIds += " | "; - strAxisIds += std::to_string(axisId); - } - strAxisIds += "]"; - - return strAxisIds; -} +// clang-format off +static const std::vector AxisIDs{ + AMOTION_EVENT_AXIS_HAT_X, + AMOTION_EVENT_AXIS_HAT_Y, + AMOTION_EVENT_AXIS_X, + AMOTION_EVENT_AXIS_Y, + AMOTION_EVENT_AXIS_Z, + AMOTION_EVENT_AXIS_RX, + AMOTION_EVENT_AXIS_RY, + AMOTION_EVENT_AXIS_RZ, + AMOTION_EVENT_AXIS_LTRIGGER, + AMOTION_EVENT_AXIS_RTRIGGER, + AMOTION_EVENT_AXIS_GAS, + AMOTION_EVENT_AXIS_BRAKE, + AMOTION_EVENT_AXIS_THROTTLE, + AMOTION_EVENT_AXIS_RUDDER, + AMOTION_EVENT_AXIS_WHEEL, + AMOTION_EVENT_AXIS_GENERIC_1, + AMOTION_EVENT_AXIS_GENERIC_2, + AMOTION_EVENT_AXIS_GENERIC_3, + AMOTION_EVENT_AXIS_GENERIC_4, + AMOTION_EVENT_AXIS_GENERIC_5, + AMOTION_EVENT_AXIS_GENERIC_6, + AMOTION_EVENT_AXIS_GENERIC_7, + AMOTION_EVENT_AXIS_GENERIC_8, + AMOTION_EVENT_AXIS_GENERIC_9, + AMOTION_EVENT_AXIS_GENERIC_10, + AMOTION_EVENT_AXIS_GENERIC_11, + AMOTION_EVENT_AXIS_GENERIC_12, + AMOTION_EVENT_AXIS_GENERIC_13, + AMOTION_EVENT_AXIS_GENERIC_14, + AMOTION_EVENT_AXIS_GENERIC_15, + AMOTION_EVENT_AXIS_GENERIC_16, +}; +// clang-format on static void MapAxisIds(int axisId, int primaryAxisId, @@ -105,6 +135,7 @@ static void MapAxisIds(int axisId, else if (axisId == secondaryAxisId) axisIds.insert(axisIds.begin(), primaryAxisId); } +} // namespace CAndroidJoystickState::CAndroidJoystickState(CAndroidJoystickState&& other) noexcept : m_deviceId(other.m_deviceId), @@ -139,10 +170,9 @@ bool CAndroidJoystickState::Initialize(const CJNIViewInputDevice& inputDevice) !motionRange.isFromSource(CJNIViewInputDevice::SOURCE_GAMEPAD)) { CLog::Log(LOGDEBUG, - "CAndroidJoystickState: ignoring axis {} from source {} for input device \"{}\" " + "CAndroidJoystickState: axis {} has unexpected source {} for input device \"{}\" " "with ID {}", motionRange.getAxis(), motionRange.getSource(), deviceName, m_deviceId); - continue; } int axisId = motionRange.getAxis(); @@ -154,24 +184,16 @@ bool CAndroidJoystickState::Initialize(const CJNIViewInputDevice& inputDevice) motionRange.getRange(), motionRange.getResolution()}; - // check if the axis ID belongs to a D-pad, analogue stick or trigger - if (axisId == AMOTION_EVENT_AXIS_HAT_X || axisId == AMOTION_EVENT_AXIS_HAT_Y || - axisId == AMOTION_EVENT_AXIS_X || axisId == AMOTION_EVENT_AXIS_Y || - axisId == AMOTION_EVENT_AXIS_Z || axisId == AMOTION_EVENT_AXIS_RX || - axisId == AMOTION_EVENT_AXIS_RY || axisId == AMOTION_EVENT_AXIS_RZ || - axisId == AMOTION_EVENT_AXIS_LTRIGGER || axisId == AMOTION_EVENT_AXIS_RTRIGGER || - axisId == AMOTION_EVENT_AXIS_GAS || axisId == AMOTION_EVENT_AXIS_BRAKE || - axisId == AMOTION_EVENT_AXIS_THROTTLE || axisId == AMOTION_EVENT_AXIS_RUDDER || - axisId == AMOTION_EVENT_AXIS_WHEEL) + // check if the axis ID belongs to a D-pad, analogue stick, trigger or + // generic axis + if (std::find(AxisIDs.begin(), AxisIDs.end(), axisId) != AxisIDs.end()) { + CLog::Log(LOGDEBUG, "CAndroidJoystickState: axis found: {} ({})", + CAndroidJoystickTranslator::TranslateAxis(axisId), axisId); + // check if this axis is already known if (ContainsAxis(axisId, m_axes)) - { - CLog::Log(LOGWARNING, - "CAndroidJoystickState: duplicate axis {} on input device \"{}\" with ID {}", - PrintAxisIds(axis.ids), deviceName, m_deviceId); continue; - } // map AMOTION_EVENT_AXIS_GAS to AMOTION_EVENT_AXIS_RTRIGGER and // AMOTION_EVENT_AXIS_BRAKE to AMOTION_EVENT_AXIS_LTRIGGER @@ -180,9 +202,6 @@ bool CAndroidJoystickState::Initialize(const CJNIViewInputDevice& inputDevice) MapAxisIds(axisId, AMOTION_EVENT_AXIS_RTRIGGER, AMOTION_EVENT_AXIS_GAS, axis.ids); m_axes.emplace_back(std::move(axis)); - CLog::Log(LOGDEBUG, - "CAndroidJoystickState: axis {} on input device \"{}\" with ID {} detected", - PrintAxisIds(m_axes.back().ids), deviceName, m_deviceId); } else CLog::Log(LOGWARNING, @@ -190,9 +209,27 @@ bool CAndroidJoystickState::Initialize(const CJNIViewInputDevice& inputDevice) axisId, deviceName, m_deviceId); } - // map buttons - for (int buttonKeycode : ButtonKeycodes) - m_buttons.emplace_back(JoystickAxis{{buttonKeycode}}); + // check for presence of buttons + auto results = inputDevice.hasKeys(ButtonKeycodes); + + if (results.size() != ButtonKeycodes.size()) + { + CLog::Log(LOGERROR, "CAndroidJoystickState: failed to get key status for {} buttons", + ButtonKeycodes.size()); + return false; + } + + // log positive results and assign results to buttons + for (unsigned int i = 0; i < ButtonKeycodes.size(); ++i) + { + if (results[i]) + { + const int buttonKeycode = ButtonKeycodes[i]; + CLog::Log(LOGDEBUG, "CAndroidJoystickState: button found: {} ({})", + CAndroidJoystickTranslator::TranslateKeyCode(buttonKeycode), buttonKeycode); + m_buttons.emplace_back(JoystickAxis{{buttonKeycode}}); + } + } // check if there are no buttons or axes at all if (GetButtonCount() == 0 && GetAxisCount() == 0) From b4cc8df3e99c92f6d02b2a6983f505807b2cc362 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 27 Jun 2024 17:28:34 +1000 Subject: [PATCH 217/651] [windows][buildsteps] run-tests.bat fail if test executable outputs no test data --- tools/buildsteps/windows/run-tests.bat | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/buildsteps/windows/run-tests.bat b/tools/buildsteps/windows/run-tests.bat index 2bde835b0aacb..ab60f59e8d73c 100644 --- a/tools/buildsteps/windows/run-tests.bat +++ b/tools/buildsteps/windows/run-tests.bat @@ -43,6 +43,11 @@ ECHO ------------------------------------------------------------ ECHO Running testsuite... "%buildconfig%\%APP_NAME%-test.exe" --gtest_output=xml:%WORKSPACE%\gtestresults.xml + IF NOT EXIST %WORKSPACE%\gtestresults.xml ( + set DIETEXT="%APP_NAME%-test.exe failed to execute or output test results!" + goto DIE + ) + rem Adapt gtest xml output to be conform with junit xml rem this basically looks for lines which have "notrun" in the tag rem and adds a subtag into it. For example: From bea7e6de2f6828e13227c7e6783f865ebd87e046 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 21 Jun 2024 16:49:05 +0200 Subject: [PATCH 218/651] [PVR] Group Manager: Fix group thumbnail update. --- xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp | 60 ++++++++++++++++--- xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h | 2 +- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp index f04c7502aada9..6ef722b6d895c 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp @@ -26,6 +26,7 @@ #include "pvr/PVRPlaybackState.h" #include "pvr/addons/PVRClient.h" #include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroup.h" #include "pvr/channels/PVRChannelGroupMember.h" #include "pvr/channels/PVRChannelGroups.h" #include "pvr/channels/PVRChannelGroupsContainer.h" @@ -160,7 +161,7 @@ bool CGUIDialogPVRGroupManager::ActionButtonNewGroup(const CGUIMessage& message) if (!strGroupName.empty()) { // add the group if it doesn't already exist - auto groups = CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio); + CPVRChannelGroups* groups{CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)}; const auto group = groups->AddGroup(strGroupName); if (group) { @@ -200,7 +201,7 @@ bool CGUIDialogPVRGroupManager::ActionButtonDeleteGroup(const CGUIMessage& messa if (pDialog->IsConfirmed()) { - ClearSelectedGroupsThumbnail(); + ClearGroupThumbnails(*m_channelGroups->Get(m_iSelectedChannelGroup)); if (CServiceBroker::GetPVRManager() .ChannelGroups() ->Get(m_bIsRadio) @@ -237,7 +238,7 @@ bool CGUIDialogPVRGroupManager::ActionButtonRenameGroup(const CGUIMessage& messa if (!strGroupName.empty()) { - ClearSelectedGroupsThumbnail(); + ClearGroupThumbnails(*m_channelGroups->Get(m_iSelectedChannelGroup)); if (CServiceBroker::GetPVRManager() .ChannelGroups() ->Get(m_bIsRadio) @@ -282,7 +283,7 @@ bool CGUIDialogPVRGroupManager::ActionButtonUngroupedChannels(const CGUIMessage& ->Get(m_bIsRadio) ->AppendToGroup(m_selectedGroup, itemChannel->GetPVRChannelGroupMemberInfoTag())) { - ClearSelectedGroupsThumbnail(); + ClearGroupThumbnails(*itemChannel); m_iSelectedChannelGroup = -1; // recalc index in Update() Update(); } @@ -312,7 +313,7 @@ bool CGUIDialogPVRGroupManager::ActionButtonGroupMembers(const CGUIMessage& mess if (m_selectedGroup && m_groupMembers->GetFileCount() > 0) { const auto itemChannel = m_groupMembers->Get(m_iSelectedGroupMember); - ClearSelectedGroupsThumbnail(); + ClearGroupThumbnails(*itemChannel); if (CServiceBroker::GetPVRManager() .ChannelGroups() ->Get(m_bIsRadio) @@ -648,7 +649,7 @@ void CGUIDialogPVRGroupManager::Update() m_groupMembers->Add(std::make_shared(groupMember)); } - const auto groups = CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio); + CPVRChannelGroups* groups{CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)}; const auto availableMembers = groups->GetMembersAvailableForGroup(m_selectedGroup); for (const auto& groupMember : availableMembers) @@ -678,7 +679,50 @@ void CGUIDialogPVRGroupManager::Clear() m_channelGroups->Clear(); } -void CGUIDialogPVRGroupManager::ClearSelectedGroupsThumbnail() +void CGUIDialogPVRGroupManager::ClearGroupThumbnails(const CFileItem& changedItem) { - m_thumbLoader.ClearCachedImage(*m_channelGroups->Get(m_iSelectedChannelGroup)); + const CPVRChannelGroups* groups{CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)}; + + const std::shared_ptr changedMember{ + changedItem.GetPVRChannelGroupMemberInfoTag()}; + if (changedMember) + { + // Item represents a channel group member. + + for (const auto& groupItem : *m_channelGroups) + { + const std::shared_ptr group{ + groups->GetGroupByPath(groupItem->GetPath())}; + if (!group) + continue; + + // If the changed channel group member is a member of this group, update this group's thumb. + if (group->IsGroupMember(changedMember)) + m_thumbLoader.ClearCachedImage(*groupItem); + } + } + else + { + // Item represents a group. + + const std::shared_ptr changedGroup{ + groups->GetGroupByPath(changedItem.GetPath())}; + for (const auto& groupItem : *m_channelGroups) + { + const std::shared_ptr group{ + groups->GetGroupByPath(groupItem->GetPath())}; + if (!group) + continue; + + for (const auto& member : group->GetMembers()) + { + // If this channel group member is a member of the changed group, update this group's thumb. + if (changedGroup->IsGroupMember(member)) + { + m_thumbLoader.ClearCachedImage(*groupItem); + break; // Next group. + } + } + } + } } diff --git a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h index b5a8aae14fd74..b0a83101b9e20 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h +++ b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.h @@ -39,7 +39,7 @@ class CGUIDialogPVRGroupManager : public CGUIDialog private: void Clear(); - void ClearSelectedGroupsThumbnail(); + void ClearGroupThumbnails(const CFileItem& changedItem); void Update(); bool ActionButtonOk(const CGUIMessage& message); bool ActionButtonNewGroup(const CGUIMessage& message); From 14aac683c8efcd7e3ce0672ddf446bf5c4e22706 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 26 Jun 2024 23:14:39 +0200 Subject: [PATCH 219/651] [video] GUIDialogVideoBookmarks: Use correct item path to write and read bookmarks. --- .../video/dialogs/GUIDialogVideoBookmarks.cpp | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp b/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp index 344b0b5a077d2..f97c8c1d216b5 100644 --- a/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp @@ -188,10 +188,7 @@ void CGUIDialogVideoBookmarks::Delete(int item) { 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(); + const std::string path{g_application.CurrentFileItem().GetDynPath()}; videoDatabase.ClearBookMarkOfFile(path, m_bookmarks[item], m_bookmarks[item].type); videoDatabase.Close(); CUtil::DeleteVideoDatabaseDirectoryCache(); @@ -205,10 +202,7 @@ void CGUIDialogVideoBookmarks::OnRefreshList() std::vector 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(); + m_filePath = g_application.CurrentFileItem().GetDynPath(); CVideoDatabase videoDatabase; videoDatabase.Open(); @@ -355,10 +349,7 @@ 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(); + const std::string path{g_application.CurrentFileItem().GetDynPath()}; videoDatabase.ClearBookMarksOfFile(path, CBookmark::STANDARD); videoDatabase.ClearBookMarksOfFile(path, CBookmark::RESUME); videoDatabase.ClearBookMarksOfFile(path, CBookmark::EPISODE); @@ -471,10 +462,7 @@ bool CGUIDialogVideoBookmarks::AddBookmark(CVideoInfoTag* 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(); + const std::string path{g_application.CurrentFileItem().GetDynPath()}; videoDatabase.AddBookMarkToFile(path, bookmark, CBookmark::STANDARD); } videoDatabase.Close(); From 66dc6399f08ef0487a7bf59811e3433b09eb87af Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 27 Jun 2024 13:04:22 +0200 Subject: [PATCH 220/651] [FileItem] Fix CFileItem::LoadDetails not to pass item's path to CVideoDatabase::Get*Info methods. The path is actually not needed because we always have the media id available here. Even worse, item's path could be a videodb URL which leads to misbehavior once passed to CVideoDatabase::Get*Info methods, including writing wrong entries to video db's 'files' and 'path' tables. The latter causes all kind of weird follow-up issues, like not being able to resume playback from the right position once a bookmark for a movie was created by the user. --- xbmc/FileItem.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 43ca4749edb62..98360446a337b 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -2430,12 +2430,12 @@ bool CFileItem::LoadDetails() bool ret{false}; auto tag{std::make_unique()}; if (params.GetMovieId() >= 0) - ret = db.GetMovieInfo(GetPath(), *tag, static_cast(params.GetMovieId()), + ret = db.GetMovieInfo({}, *tag, static_cast(params.GetMovieId()), static_cast(params.GetVideoVersionId())); else if (params.GetMVideoId() >= 0) - ret = db.GetMusicVideoInfo(GetPath(), *tag, static_cast(params.GetMVideoId())); + ret = db.GetMusicVideoInfo({}, *tag, static_cast(params.GetMVideoId())); else if (params.GetEpisodeId() >= 0) - ret = db.GetEpisodeInfo(GetPath(), *tag, static_cast(params.GetEpisodeId())); + ret = db.GetEpisodeInfo({}, *tag, static_cast(params.GetEpisodeId())); else if (params.GetSetId() >= 0) // movie set ret = db.GetSetInfo(static_cast(params.GetSetId()), *tag, this); else if (params.GetTvShowId() >= 0) @@ -2448,7 +2448,7 @@ bool CFileItem::LoadDetails() ret = db.GetSeasonInfo(idSeason, *tag, this); } else - ret = db.GetTvShowInfo(GetPath(), *tag, static_cast(params.GetTvShowId()), this); + ret = db.GetTvShowInfo({}, *tag, static_cast(params.GetTvShowId()), this); } if (ret) From 5748081f1fb2074eb30c589357d8bc39ed870afb Mon Sep 17 00:00:00 2001 From: fuzzard Date: Fri, 28 Jun 2024 17:01:56 +1000 Subject: [PATCH 221/651] [test][playlists] Fix WPL playlist for windows --- xbmc/playlists/test/TestPlayListWPL.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/xbmc/playlists/test/TestPlayListWPL.cpp b/xbmc/playlists/test/TestPlayListWPL.cpp index 19a687c9a2129..df3c620c09f1b 100644 --- a/xbmc/playlists/test/TestPlayListWPL.cpp +++ b/xbmc/playlists/test/TestPlayListWPL.cpp @@ -8,6 +8,7 @@ #include "FileItem.h" #include "URL.h" +#include "Util.h" #include "music/tags/MusicInfoTag.h" #include "playlists/PlayListWPL.h" #include "test/TestUtils.h" @@ -37,8 +38,18 @@ TEST(TestPlayListWPL, LoadData) EXPECT_EQ(playlist.size(), 3); EXPECT_STREQ(playlist[0]->GetLabel().c_str(), "track01.mp3"); + + std::string track_url = "\\\\server\\vol\\music\\Classical\\Composer\\OrganWorks\\cd03\\track01.mp3"; + std::string track_path = ""; + CUtil::GetQualifiedFilename(track_path, track_url); + EXPECT_STREQ(playlist[0]->GetURL().Get().c_str(), - "/server/vol/music/Classical/Composer/OrganWorks/cd03/track01.mp3"); + (track_path + track_url).c_str()); + + + track_url = "\\\\server\\vol\\music\\Classical\\Composer\\OrganWorks\\cd03\\track02.mp3"; + track_path = ""; + CUtil::GetQualifiedFilename(track_path, track_url); EXPECT_STREQ(playlist[1]->GetURL().Get().c_str(), - "/server/vol/music/Classical/Composer/OrganWorks/cd03/track02.mp3"); + (track_path + track_url).c_str()); } From 20ac23938ef6e4fb277450c2d2d75de6c1587402 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:29:14 +0200 Subject: [PATCH 222/651] [PVR] Group manager: Disable channel lists if group does not support adding/removing channels. --- xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp index 6ef722b6d895c..17c788aea847c 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp @@ -637,6 +637,10 @@ void CGUIDialogPVRGroupManager::Update() CONTROL_ENABLE_ON_CONDITION(BUTTON_DELGROUP, m_selectedGroup->SupportsDelete()); + CONTROL_ENABLE_ON_CONDITION(CONTROL_LIST_CHANNELS_LEFT, m_selectedGroup->SupportsMemberAdd()); + CONTROL_ENABLE_ON_CONDITION(CONTROL_LIST_CHANNELS_RIGHT, + m_selectedGroup->SupportsMemberRemove()); + SET_CONTROL_LABEL(CONTROL_UNGROUPED_LABEL, g_localizeStrings.Get(19219)); SET_CONTROL_LABEL( CONTROL_IN_GROUP_LABEL, From 06fd4aa6aec4ee0710124a502f5d962c1e8ba696 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 22 Jun 2024 15:32:07 +0200 Subject: [PATCH 223/651] [Estuary] PVR Group manager: Display name of selected group in header bar, instead of redundant total number of groups. --- addons/skin.estuary/xml/DialogPVRGroupManager.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/skin.estuary/xml/DialogPVRGroupManager.xml b/addons/skin.estuary/xml/DialogPVRGroupManager.xml index 0e3523e37d9a1..4bc5372751b39 100644 --- a/addons/skin.estuary/xml/DialogPVRGroupManager.xml +++ b/addons/skin.estuary/xml/DialogPVRGroupManager.xml @@ -11,7 +11,7 @@ - + From 00a89df07ebee1c247ac27b0df3f01bc8d3a7e49 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 25 Jun 2024 00:05:43 +0200 Subject: [PATCH 224/651] [PVR] Cleanup: clang-format PVRChannelGroupsContainer.(cpp|h). --- .../channels/PVRChannelGroupsContainer.cpp | 12 +- xbmc/pvr/channels/PVRChannelGroupsContainer.h | 306 +++++++++--------- 2 files changed, 160 insertions(+), 158 deletions(-) diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp index 7e57310c2c17b..c308426bdecf5 100644 --- a/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp +++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp @@ -19,9 +19,8 @@ using namespace PVR; -CPVRChannelGroupsContainer::CPVRChannelGroupsContainer() : - m_groupsRadio(new CPVRChannelGroups(true)), - m_groupsTV(new CPVRChannelGroups(false)) +CPVRChannelGroupsContainer::CPVRChannelGroupsContainer() + : m_groupsRadio(new CPVRChannelGroups(true)), m_groupsTV(new CPVRChannelGroups(false)) { } @@ -103,7 +102,9 @@ std::shared_ptr CPVRChannelGroupsContainer::GetChannelForEpgTag( if (!epgTag) return {}; - return Get(epgTag->IsRadio())->GetGroupAll()->GetByUniqueID(epgTag->UniqueChannelID(), epgTag->ClientID()); + return Get(epgTag->IsRadio()) + ->GetGroupAll() + ->GetByUniqueID(epgTag->UniqueChannelID(), epgTag->ClientID()); } std::shared_ptr CPVRChannelGroupsContainer::GetChannelGroupMemberByPath( @@ -126,7 +127,8 @@ std::shared_ptr CPVRChannelGroupsContainer::GetByPath(const std::st return {}; } -std::shared_ptr CPVRChannelGroupsContainer::GetByUniqueID(int iUniqueChannelId, int iClientID) const +std::shared_ptr CPVRChannelGroupsContainer::GetByUniqueID(int iUniqueChannelId, + int iClientID) const { std::shared_ptr channel; std::shared_ptr channelgroup = GetGroupAllTV(); diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.h b/xbmc/pvr/channels/PVRChannelGroupsContainer.h index 5a680bc1665c0..593036977a6d6 100644 --- a/xbmc/pvr/channels/PVRChannelGroupsContainer.h +++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.h @@ -15,156 +15,156 @@ namespace PVR { - class CPVRChannel; - class CPVRChannelGroup; - class CPVRChannelGroupMember; - class CPVRChannelGroups; - class CPVRClient; - class CPVREpgInfoTag; - - class CPVRChannelGroupsContainer - { - public: - /*! - * @brief Create a new container for all channel groups - */ - CPVRChannelGroupsContainer(); - - /*! - * @brief Destroy this container. - */ - virtual ~CPVRChannelGroupsContainer(); - - /*! - * @brief Update all channel groups and all channels from PVR database and from given clients. - * @param clients The PVR clients data should be loaded for. Leave empty for all clients. - * @return True on success, false otherwise. - */ - bool Update(const std::vector>& clients); - - /*! - * @brief Update data with groups and channels from the given clients, sync with local data. - * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. - * @param bChannelsOnly Set to true to only update channels, not the groups themselves. - * @return True on success, false otherwise. - */ - bool UpdateFromClients(const std::vector>& clients, - bool bChannelsOnly = false); - - /*! - * @brief Unload and destruct all channel groups and all channels in them. - */ - void Unload(); - - /*! - * @brief Get the TV channel groups. - * @return The TV channel groups. - */ - CPVRChannelGroups* GetTV() const { return Get(false); } - - /*! - * @brief Get the radio channel groups. - * @return The radio channel groups. - */ - CPVRChannelGroups* GetRadio() const { return Get(true); } - - /*! - * @brief Get the radio or TV channel groups. - * @param bRadio If true, get the radio channel groups. Get the TV channel groups otherwise. - * @return The requested groups. - */ - CPVRChannelGroups* Get(bool bRadio) const; - - /*! - * @brief Get the group containing all TV channels. - * @return The group containing all TV channels. - */ - std::shared_ptr GetGroupAllTV() const { return GetGroupAll(false); } - - /*! - * @brief Get the group containing all radio channels. - * @return The group containing all radio channels. - */ - std::shared_ptr GetGroupAllRadio() const { return GetGroupAll(true); } - - /*! - * @brief Get the group containing all TV or radio channels. - * @param bRadio If true, get the group containing all radio channels. Get the group containing all TV channels otherwise. - * @return The requested group. - */ - std::shared_ptr GetGroupAll(bool bRadio) const; - - /*! - * @brief Get a group given it's ID. - * @param iGroupId The ID of the group. - * @return The requested group or NULL if it wasn't found. - */ - std::shared_ptr GetByIdFromAll(int iGroupId) const; - - /*! - * @brief Get a channel given it's database ID. - * @param iChannelId The ID of the channel. - * @return The channel or NULL if it wasn't found. - */ - std::shared_ptr GetChannelById(int iChannelId) const; - - /*! - * @brief Get the channel for the given epg tag. - * @param epgTag The epg tag. - * @return The channel. - */ - std::shared_ptr GetChannelForEpgTag( - const std::shared_ptr& epgTag) const; - - /*! - * @brief Get a channel given it's path. - * @param strPath The path. - * @return The channel or nullptr if it wasn't found. - */ - std::shared_ptr GetByPath(const std::string& strPath) const; - - /*! - * @brief Get a channel group member given it's path. - * @param strPath The path. - * @return The channel group member or nullptr if it wasn't found. - */ - std::shared_ptr GetChannelGroupMemberByPath( - const std::string& strPath) const; - - /*! - * @brief Get a channel given it's channel ID from all containers. - * @param iUniqueChannelId The unique channel id on the client. - * @param iClientID The ID of the client. - * @return The channel or NULL if it wasn't found. - */ - std::shared_ptr GetByUniqueID(int iUniqueChannelId, int iClientID) const; - - /*! - * @brief Get the channel group member that was played last. - * @return The requested channel group member or nullptr. - */ - std::shared_ptr GetLastPlayedChannelGroupMember() const; - - /*! - * @brief Erase stale texture db entries and image files. - * @return number of cleaned up images. - */ - int CleanupCachedImages(); - - private: - CPVRChannelGroupsContainer& operator=(const CPVRChannelGroupsContainer&) = delete; - CPVRChannelGroupsContainer(const CPVRChannelGroupsContainer&) = delete; - - /*! - * @brief Load all channel groups and all channels from PVR database. - * @param clients The PVR clients data should be loaded for. Leave empty for all clients. - * @return True on success, false otherwise. - */ - bool LoadFromDatabase(const std::vector>& clients); - - CPVRChannelGroups* m_groupsRadio; /*!< all radio channel groups */ - CPVRChannelGroups* m_groupsTV; /*!< all TV channel groups */ - CCriticalSection m_critSection; - bool m_bIsUpdating = false; - }; -} +class CPVRChannel; +class CPVRChannelGroup; +class CPVRChannelGroupMember; +class CPVRChannelGroups; +class CPVRClient; +class CPVREpgInfoTag; + +class CPVRChannelGroupsContainer +{ +public: + /*! + * @brief Create a new container for all channel groups + */ + CPVRChannelGroupsContainer(); + + /*! + * @brief Destroy this container. + */ + virtual ~CPVRChannelGroupsContainer(); + + /*! + * @brief Update all channel groups and all channels from PVR database and from given clients. + * @param clients The PVR clients data should be loaded for. Leave empty for all clients. + * @return True on success, false otherwise. + */ + bool Update(const std::vector>& clients); + + /*! + * @brief Update data with groups and channels from the given clients, sync with local data. + * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. + * @param bChannelsOnly Set to true to only update channels, not the groups themselves. + * @return True on success, false otherwise. + */ + bool UpdateFromClients(const std::vector>& clients, + bool bChannelsOnly = false); + + /*! + * @brief Unload and destruct all channel groups and all channels in them. + */ + void Unload(); + + /*! + * @brief Get the TV channel groups. + * @return The TV channel groups. + */ + CPVRChannelGroups* GetTV() const { return Get(false); } + + /*! + * @brief Get the radio channel groups. + * @return The radio channel groups. + */ + CPVRChannelGroups* GetRadio() const { return Get(true); } + + /*! + * @brief Get the radio or TV channel groups. + * @param bRadio If true, get the radio channel groups. Get the TV channel groups otherwise. + * @return The requested groups. + */ + CPVRChannelGroups* Get(bool bRadio) const; + + /*! + * @brief Get the group containing all TV channels. + * @return The group containing all TV channels. + */ + std::shared_ptr GetGroupAllTV() const { return GetGroupAll(false); } + + /*! + * @brief Get the group containing all radio channels. + * @return The group containing all radio channels. + */ + std::shared_ptr GetGroupAllRadio() const { return GetGroupAll(true); } + + /*! + * @brief Get the group containing all TV or radio channels. + * @param bRadio If true, get the group containing all radio channels. Get the group containing all TV channels otherwise. + * @return The requested group. + */ + std::shared_ptr GetGroupAll(bool bRadio) const; + + /*! + * @brief Get a group given it's ID. + * @param iGroupId The ID of the group. + * @return The requested group or NULL if it wasn't found. + */ + std::shared_ptr GetByIdFromAll(int iGroupId) const; + + /*! + * @brief Get a channel given it's database ID. + * @param iChannelId The ID of the channel. + * @return The channel or NULL if it wasn't found. + */ + std::shared_ptr GetChannelById(int iChannelId) const; + + /*! + * @brief Get the channel for the given epg tag. + * @param epgTag The epg tag. + * @return The channel. + */ + std::shared_ptr GetChannelForEpgTag( + const std::shared_ptr& epgTag) const; + + /*! + * @brief Get a channel given it's path. + * @param strPath The path. + * @return The channel or nullptr if it wasn't found. + */ + std::shared_ptr GetByPath(const std::string& strPath) const; + + /*! + * @brief Get a channel group member given it's path. + * @param strPath The path. + * @return The channel group member or nullptr if it wasn't found. + */ + std::shared_ptr GetChannelGroupMemberByPath( + const std::string& strPath) const; + + /*! + * @brief Get a channel given it's channel ID from all containers. + * @param iUniqueChannelId The unique channel id on the client. + * @param iClientID The ID of the client. + * @return The channel or NULL if it wasn't found. + */ + std::shared_ptr GetByUniqueID(int iUniqueChannelId, int iClientID) const; + + /*! + * @brief Get the channel group member that was played last. + * @return The requested channel group member or nullptr. + */ + std::shared_ptr GetLastPlayedChannelGroupMember() const; + + /*! + * @brief Erase stale texture db entries and image files. + * @return number of cleaned up images. + */ + int CleanupCachedImages(); + +private: + CPVRChannelGroupsContainer& operator=(const CPVRChannelGroupsContainer&) = delete; + CPVRChannelGroupsContainer(const CPVRChannelGroupsContainer&) = delete; + + /*! + * @brief Load all channel groups and all channels from PVR database. + * @param clients The PVR clients data should be loaded for. Leave empty for all clients. + * @return True on success, false otherwise. + */ + bool LoadFromDatabase(const std::vector>& clients); + + CPVRChannelGroups* m_groupsRadio; /*!< all radio channel groups */ + CPVRChannelGroups* m_groupsTV; /*!< all TV channel groups */ + CCriticalSection m_critSection; + bool m_bIsUpdating = false; +}; +} // namespace PVR From 388c809bffaa539c441be2f11b49a8af82e7882b Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 24 Jun 2024 16:56:50 +0200 Subject: [PATCH 225/651] [guilib][PVR] New GUI info label: ListItem.PVRGroupOrigin. --- .../resources/strings.po | 20 +++++++++++- xbmc/GUIInfoManager.cpp | 10 ++++++ xbmc/guilib/guiinfo/GUIInfoLabels.h | 1 + .../channels/PVRChannelGroupsContainer.cpp | 12 +++++++ xbmc/pvr/channels/PVRChannelGroupsContainer.h | 17 +++++++--- xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 31 +++++++++++++++++++ 6 files changed, 85 insertions(+), 6 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 76ba757a12274..70c600ca6d64b 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -3942,7 +3942,25 @@ msgctxt "#855" msgid "Recently added channels" msgstr "" -#empty strings from id 856 to 996 +#. Label for the origin of a channel group (provided by a PVR client add-on). +#: xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +msgctxt "#856" +msgid "Client" +msgstr "" + +#. Label for the origin of a channel group (automatically created by Kodi). +#: xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +msgctxt "#857" +msgid "System" +msgstr "" + +#. Label for the origin of a channel group (manually created by the user). +#: xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +msgctxt "#858" +msgid "User" +msgstr "" + +#empty strings from id 859 to 996 #: xbmc/windows/GUIMediaWindow.cpp msgctxt "#997" diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 549ea2abc931e..b7689993152e9 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -7006,6 +7006,15 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @skinning_v22 **[New Infolabel]** \link ListItem_PVRInstanceName `ListItem.PVRInstanceName`\endlink ///

/// } +/// \table_row3{ `ListItem.PVRGroupOrigin`, +/// \anchor ListItem_PVRGroupOrigin +/// _string_, +/// @return If selected item is of type PVR channel group, the creator (user, system, client) of +/// the group. +///


+/// @skinning_v22 **[New Infolabel]** \link ListItem_PVRGroupOrigin `ListItem.PVRGroupOrigin`\endlink +///

+/// } /// \table_end /// /// ----------------------------------------------------------------------------- @@ -7230,6 +7239,7 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "hasvideoextras", LISTITEM_HASVIDEOEXTRAS }, { "pvrclientname", LISTITEM_PVR_CLIENT_NAME }, { "pvrinstancename", LISTITEM_PVR_INSTANCE_NAME }, + { "pvrgrouporigin", LISTITEM_PVR_GROUP_ORIGIN }, }; // clang-format on diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index f86aad749833d..c801a1eb33833 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -988,6 +988,7 @@ static constexpr unsigned int SYSTEM_LOCALE = 1012; #define LISTITEM_PVR_CLIENT_NAME (LISTITEM_START + 217) #define LISTITEM_PVR_INSTANCE_NAME (LISTITEM_START + 218) #define LISTITEM_CHANNEL_LOGO (LISTITEM_START + 219) +#define LISTITEM_PVR_GROUP_ORIGIN (LISTITEM_START + 220) #define LISTITEM_END (LISTITEM_START + 2500) diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp index c308426bdecf5..04ff915c33a6c 100644 --- a/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp +++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp @@ -78,6 +78,18 @@ std::shared_ptr CPVRChannelGroupsContainer::GetGroupAll(bool b return Get(bRadio)->GetGroupAll(); } +std::shared_ptr CPVRChannelGroupsContainer::GetGroupByPath( + const std::string& path) const +{ + const CPVRChannelsPath channelsPath{path}; + if (channelsPath.IsValid() && channelsPath.IsChannelGroup()) + return Get(channelsPath.IsRadio())->GetGroupByPath(path); + else + CLog::LogFC(LOGERROR, LOGPVR, "Invalid path '{}'", path); + + return {}; +} + std::shared_ptr CPVRChannelGroupsContainer::GetByIdFromAll(int iGroupId) const { std::shared_ptr group = m_groupsTV->GetById(iGroupId); diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.h b/xbmc/pvr/channels/PVRChannelGroupsContainer.h index 593036977a6d6..8fb71c8e355a6 100644 --- a/xbmc/pvr/channels/PVRChannelGroupsContainer.h +++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.h @@ -95,14 +95,21 @@ class CPVRChannelGroupsContainer std::shared_ptr GetGroupAll(bool bRadio) const; /*! - * @brief Get a group given it's ID. + * @brief Get a group given its path. + * @param path The path to the channel group. + * @return The channel group, or nullptr if not found. + */ + std::shared_ptr GetGroupByPath(const std::string& path) const; + + /*! + * @brief Get a group given its ID. * @param iGroupId The ID of the group. * @return The requested group or NULL if it wasn't found. */ std::shared_ptr GetByIdFromAll(int iGroupId) const; /*! - * @brief Get a channel given it's database ID. + * @brief Get a channel given its database ID. * @param iChannelId The ID of the channel. * @return The channel or NULL if it wasn't found. */ @@ -117,14 +124,14 @@ class CPVRChannelGroupsContainer const std::shared_ptr& epgTag) const; /*! - * @brief Get a channel given it's path. + * @brief Get a channel given its path. * @param strPath The path. * @return The channel or nullptr if it wasn't found. */ std::shared_ptr GetByPath(const std::string& strPath) const; /*! - * @brief Get a channel group member given it's path. + * @brief Get a channel group member given its path. * @param strPath The path. * @return The channel group member or nullptr if it wasn't found. */ @@ -132,7 +139,7 @@ class CPVRChannelGroupsContainer const std::string& strPath) const; /*! - * @brief Get a channel given it's channel ID from all containers. + * @brief Get a channel given its channel ID from all containers. * @param iUniqueChannelId The unique channel id on the client. * @param iClientID The ID of the client. * @return The channel or NULL if it wasn't found. diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index 558f11f7ba0bc..c259759112970 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -27,6 +27,7 @@ #include "pvr/channels/PVRChannel.h" #include "pvr/channels/PVRChannelGroup.h" #include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/channels/PVRChannelGroups.h" #include "pvr/channels/PVRChannelGroupsContainer.h" #include "pvr/channels/PVRRadioRDSInfoTag.h" #include "pvr/epg/EpgContainer.h" @@ -608,6 +609,36 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, return false; } + if (item->IsPVRChannelGroup()) + { + switch (info.m_info) + { + case LISTITEM_PVR_GROUP_ORIGIN: + { + const std::shared_ptr group{ + CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupByPath(item->GetPath())}; + if (group) + { + const CPVRChannelGroup::Origin origin{group->GetOrigin()}; + switch (origin) + { + case CPVRChannelGroup::Origin::CLIENT: + strValue = g_localizeStrings.Get(856); // Client + return true; + case CPVRChannelGroup::Origin::SYSTEM: + strValue = g_localizeStrings.Get(857); // System + return true; + case CPVRChannelGroup::Origin::USER: + strValue = g_localizeStrings.Get(858); // User + return true; + } + } + break; + } + } + return false; + } + std::shared_ptr epgTag; std::shared_ptr channel; if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer()) From 04a3d1f9ac7a9e3637138f25a255466b20293667 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 24 Jun 2024 16:59:10 +0200 Subject: [PATCH 226/651] [Estuary] PVR Group manager: Display group's origin along with the group name so the user has an idea what kind of groups they face (and what can be done with them). --- .../skin.estuary/xml/DialogPVRGroupManager.xml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/addons/skin.estuary/xml/DialogPVRGroupManager.xml b/addons/skin.estuary/xml/DialogPVRGroupManager.xml index 4bc5372751b39..c354acb05e3c8 100644 --- a/addons/skin.estuary/xml/DialogPVRGroupManager.xml +++ b/addons/skin.estuary/xml/DialogPVRGroupManager.xml @@ -49,13 +49,25 @@ 20 + 5 70 - 70 + 30 center - font27 + font12 grey + + 20 + 35 + 70 + 30 + font10 + center + + grey + true + 60 60 @@ -93,7 +105,7 @@ 30 font10 center - + true From f24615b6fee521e9cfd2fab570e2f7ff4bbad3fa Mon Sep 17 00:00:00 2001 From: Hitcher Date: Fri, 28 Jun 2024 21:13:26 +0100 Subject: [PATCH 227/651] Fix missing subtitle help tips (#25365) * Update strings.po for subtitle help * Update settings.xml for subtitle help * Revert border blur --- .../resources/strings.po | 38 ++++++++++++++++++- system/settings/settings.xml | 12 +++--- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 76ba757a12274..b6328ce82e05d 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -3247,7 +3247,43 @@ msgctxt "#687" msgid "From metadata" msgstr "" -#empty strings from id 688 to 699 +#. Description of setting with label #39159 "Border size" +#: system/settings/settings.xml +msgctxt "#688" +msgid "Set the border size." +msgstr "" + +#. Description of setting with label #39160 "Border colour" +#: system/settings/settings.xml +msgctxt "#689" +msgid "Set the border colour." +msgstr "" + +#. Description of setting with label #39173 "Blur" +#: system/settings/settings.xml +msgctxt "#690" +msgid "Set the blur." +msgstr "" + +#. Description of setting with label #39171 "Shadow colour" +#: system/settings/settings.xml +msgctxt "#691" +msgid "Set the shadow colour." +msgstr "" + +#. Description of setting with label #39172 "Shadow opacity" +#: system/settings/settings.xml +msgctxt "#692" +msgid "Set the shadow opacity." +msgstr "" + +#. Description of setting with label #39170 "Shadow size" +#: system/settings/settings.xml +msgctxt "#693" +msgid "Set the shadow size." +msgstr "" + +#empty strings from id 694 to 699 #: xbmc/interfaces/builtins/LibraryBuiltins.cpp #: xbmc/music/MusicLibraryQueue.cpp diff --git a/system/settings/settings.xml b/system/settings/settings.xml index bd47c128f4250..ff748c2584c8c 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -640,7 +640,7 @@ 100 - + 3 25 @@ -648,7 +648,7 @@ - + 3 FF000000 @@ -656,7 +656,7 @@ - + 3 0 @@ -700,7 +700,7 @@ - + 3 FF000000 @@ -713,7 +713,7 @@ - + 3 100 @@ -726,7 +726,7 @@ - + 3 15 From 6efd635acb9e8ce9252459cc30f8b994c3c09e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Fri, 28 Jun 2024 00:34:12 +0200 Subject: [PATCH 228/651] RegExp: Fix heap-use-after-free `m_iOvector` is a pointer array into the match data, so the match data must be kept as long as `m_iOvector` is used. Address Sanitizer output: ==28015==ERROR: AddressSanitizer: heap-use-after-free on address 0x5190000406e8 at pc 0x59cf02e5ad9e bp 0x7ffdcd8a1a10 sp 0x7ffdcd8a1a08 READ of size 8 at 0x5190000406e8 thread T0 #0 0x59cf02e5ad9d in CRegExp::PrivateRegFind(unsigned long, char const*, unsigned int, int) xbmc/utils/RegExp.cpp:425:10 #1 0x59cf013cb942 in CRegExp::RegFind(std::__cxx11::basic_string, std::allocator> const&, unsigned int, int) xbmc/utils/RegExp.h:95:12 #2 0x59cf0316a9ef in CXBMCTinyXML2::ParseHelper(unsigned long, std::__cxx11::basic_string, std::allocator>&&) xbmc/utils/XBMCTinyXML2.cpp:106:12 #3 0x59cf0316965b in CXBMCTinyXML2::Parse(std::basic_string_view>) xbmc/utils/XBMCTinyXML2.cpp:82:10 #4 0x59cf03168fac in CXBMCTinyXML2::LoadFile(std::__cxx11::basic_string, std::allocator> const&) xbmc/utils/XBMCTinyXML2.cpp:36:3 #5 0x59cf0563fe1b in CMediaSourceSettings::Load(std::__cxx11::basic_string, std::allocator> const&) xbmc/settings/MediaSourceSettings.cpp:85:15 #6 0x59cf0563f590 in CMediaSourceSettings::Load() xbmc/settings/MediaSourceSettings.cpp:71:10 #7 0x59cf0563f3d0 in CMediaSourceSettings::OnSettingsLoaded() xbmc/settings/MediaSourceSettings.cpp:61:3 #8 0x59cf053c86c4 in CSettingsManager::OnSettingsLoaded() xbmc/settings/lib/SettingsManager.cpp:1022:22 #9 0x59cf053969dc in CSettingsManager::Load(TiXmlElement const*, bool&, bool, std::map, std::allocator>, std::shared_ptr, std::less, std::allocator>>, std::allocator, std::allocator> const, std::shared_ptr>>>*) xbmc/settings/lib/SettingsManager.cpp:173:5 #10 0x59cf056ed775 in CSettingsBase::LoadValuesFromXml(TiXmlElement const*, bool&) xbmc/settings/SettingsBase.cpp:86:29 #11 0x59cf056b96b0 in CSettings::Load(TiXmlElement const*, bool&) xbmc/settings/Settings.cpp:217:23 #12 0x59cf056b8f00 in CSettings::Load(std::__cxx11::basic_string, std::allocator> const&) xbmc/settings/Settings.cpp:125:8 #13 0x59cf056b87e5 in CSettings::Load() xbmc/settings/Settings.cpp:117:10 #14 0x59cf05715a60 in CSettingsComponent::Load() xbmc/settings/SettingsComponent.cpp:83:22 #15 0x59cf041da912 in CApplication::Create() xbmc/application/Application.cpp:320:27 #16 0x59cf033b4eed in XBMC_Run xbmc/platform/xbmc.cpp:26:22 #17 0x59cf00752b7f in main xbmc/platform/posix/main.cpp:70:16 #18 0x74a07d239c87 (/usr/lib/libc.so.6+0x25c87) (BuildId: 32a656aa5562eece8c59a585f5eacd6cf5e2307b) #19 0x74a07d239d4b in __libc_start_main (/usr/lib/libc.so.6+0x25d4b) (BuildId: 32a656aa5562eece8c59a585f5eacd6cf5e2307b) #20 0x59cf00618804 in _start (/home/mark/Coding/Repos/kodi-git/build_clang_debug_sanitizer/kodi.bin+0x9f91804) (BuildId: fa447ae84e6fbfe91e7ec718a600116496d7607e) 0x5190000406e8 is located 104 bytes inside of 1112-byte region [0x519000040680,0x519000040ad8) freed by thread T0 here: #0 0x59cf007069b2 in free.part.0 (/home/mark/Coding/Repos/kodi-git/build_clang_debug_sanitizer/kodi.bin+0xa07f9b2) (BuildId: fa447ae84e6fbfe91e7ec718a600116496d7607e) #1 0x59cf02e59ed8 in CRegExp::PrivateRegFind(unsigned long, char const*, unsigned int, int) xbmc/utils/RegExp.cpp:352:3 #2 0x59cf013cb942 in CRegExp::RegFind(std::__cxx11::basic_string, std::allocator> const&, unsigned int, int) xbmc/utils/RegExp.h:95:12 #3 0x59cf0316a9ef in CXBMCTinyXML2::ParseHelper(unsigned long, std::__cxx11::basic_string, std::allocator>&&) xbmc/utils/XBMCTinyXML2.cpp:106:12 #4 0x59cf0316965b in CXBMCTinyXML2::Parse(std::basic_string_view>) xbmc/utils/XBMCTinyXML2.cpp:82:10 #5 0x59cf03168fac in CXBMCTinyXML2::LoadFile(std::__cxx11::basic_string, std::allocator> const&) xbmc/utils/XBMCTinyXML2.cpp:36:3 #6 0x59cf0563fe1b in CMediaSourceSettings::Load(std::__cxx11::basic_string, std::allocator> const&) xbmc/settings/MediaSourceSettings.cpp:85:15 #7 0x59cf0563f590 in CMediaSourceSettings::Load() xbmc/settings/MediaSourceSettings.cpp:71:10 #8 0x59cf0563f3d0 in CMediaSourceSettings::OnSettingsLoaded() xbmc/settings/MediaSourceSettings.cpp:61:3 #9 0x59cf053c86c4 in CSettingsManager::OnSettingsLoaded() xbmc/settings/lib/SettingsManager.cpp:1022:22 #10 0x59cf053969dc in CSettingsManager::Load(TiXmlElement const*, bool&, bool, std::map, std::allocator>, std::shared_ptr, std::less, std::allocator>>, std::allocator, std::allocator> const, std::shared_ptr>>>*) xbmc/settings/lib/SettingsManager.cpp:173:5 #11 0x59cf056ed775 in CSettingsBase::LoadValuesFromXml(TiXmlElement const*, bool&) xbmc/settings/SettingsBase.cpp:86:29 #12 0x59cf056b96b0 in CSettings::Load(TiXmlElement const*, bool&) xbmc/settings/Settings.cpp:217:23 #13 0x59cf056b8f00 in CSettings::Load(std::__cxx11::basic_string, std::allocator> const&) xbmc/settings/Settings.cpp:125:8 #14 0x59cf056b87e5 in CSettings::Load() xbmc/settings/Settings.cpp:117:10 #15 0x59cf05715a60 in CSettingsComponent::Load() xbmc/settings/SettingsComponent.cpp:83:22 #16 0x59cf041da912 in CApplication::Create() xbmc/application/Application.cpp:320:27 #17 0x59cf033b4eed in XBMC_Run xbmc/platform/xbmc.cpp:26:22 #18 0x59cf00752b7f in main xbmc/platform/posix/main.cpp:70:16 #19 0x74a07d239c87 (/usr/lib/libc.so.6+0x25c87) (BuildId: 32a656aa5562eece8c59a585f5eacd6cf5e2307b) #20 0x74a07d239d4b in __libc_start_main (/usr/lib/libc.so.6+0x25d4b) (BuildId: 32a656aa5562eece8c59a585f5eacd6cf5e2307b) #21 0x59cf00618804 in _start (/home/mark/Coding/Repos/kodi-git/build_clang_debug_sanitizer/kodi.bin+0x9f91804) (BuildId: fa447ae84e6fbfe91e7ec718a600116496d7607e) previously allocated by thread T0 here: #0 0x59cf007079e9 in malloc (/home/mark/Coding/Repos/kodi-git/build_clang_debug_sanitizer/kodi.bin+0xa0809e9) (BuildId: fa447ae84e6fbfe91e7ec718a600116496d7607e) #1 0x74a07fb7faed (/usr/lib/libpcre2-8.so.0+0x12aed) (BuildId: d6a22ace8f92ae592b620499fc467ef7899f99a0) #2 0x74a07fbbf29f in pcre2_match_data_create_8 (/usr/lib/libpcre2-8.so.0+0x5229f) (BuildId: d6a22ace8f92ae592b620499fc467ef7899f99a0) #3 0x59cf02e59c65 in CRegExp::PrivateRegFind(unsigned long, char const*, unsigned int, int) xbmc/utils/RegExp.cpp:347:8 #4 0x59cf013cb942 in CRegExp::RegFind(std::__cxx11::basic_string, std::allocator> const&, unsigned int, int) xbmc/utils/RegExp.h:95:12 #5 0x59cf0316a9ef in CXBMCTinyXML2::ParseHelper(unsigned long, std::__cxx11::basic_string, std::allocator>&&) xbmc/utils/XBMCTinyXML2.cpp:106:12 #6 0x59cf0316965b in CXBMCTinyXML2::Parse(std::basic_string_view>) xbmc/utils/XBMCTinyXML2.cpp:82:10 #7 0x59cf03168fac in CXBMCTinyXML2::LoadFile(std::__cxx11::basic_string, std::allocator> const&) xbmc/utils/XBMCTinyXML2.cpp:36:3 #8 0x59cf0563fe1b in CMediaSourceSettings::Load(std::__cxx11::basic_string, std::allocator> const&) xbmc/settings/MediaSourceSettings.cpp:85:15 #9 0x59cf0563f590 in CMediaSourceSettings::Load() xbmc/settings/MediaSourceSettings.cpp:71:10 #10 0x59cf0563f3d0 in CMediaSourceSettings::OnSettingsLoaded() xbmc/settings/MediaSourceSettings.cpp:61:3 #11 0x59cf053c86c4 in CSettingsManager::OnSettingsLoaded() xbmc/settings/lib/SettingsManager.cpp:1022:22 #12 0x59cf053969dc in CSettingsManager::Load(TiXmlElement const*, bool&, bool, std::map, std::allocator>, std::shared_ptr, std::less, std::allocator>>, std::allocator, std::allocator> const, std::shared_ptr>>>*) xbmc/settings/lib/SettingsManager.cpp:173:5 #13 0x59cf056ed775 in CSettingsBase::LoadValuesFromXml(TiXmlElement const*, bool&) xbmc/settings/SettingsBase.cpp:86:29 #14 0x59cf056b96b0 in CSettings::Load(TiXmlElement const*, bool&) xbmc/settings/Settings.cpp:217:23 #15 0x59cf056b8f00 in CSettings::Load(std::__cxx11::basic_string, std::allocator> const&) xbmc/settings/Settings.cpp:125:8 #16 0x59cf056b87e5 in CSettings::Load() xbmc/settings/Settings.cpp:117:10 #17 0x59cf05715a60 in CSettingsComponent::Load() xbmc/settings/SettingsComponent.cpp:83:22 #18 0x59cf041da912 in CApplication::Create() xbmc/application/Application.cpp:320:27 #19 0x59cf033b4eed in XBMC_Run xbmc/platform/xbmc.cpp:26:22 #20 0x59cf00752b7f in main xbmc/platform/posix/main.cpp:70:16 #21 0x74a07d239c87 (/usr/lib/libc.so.6+0x25c87) (BuildId: 32a656aa5562eece8c59a585f5eacd6cf5e2307b) #22 0x74a07d239d4b in __libc_start_main (/usr/lib/libc.so.6+0x25d4b) (BuildId: 32a656aa5562eece8c59a585f5eacd6cf5e2307b) #23 0x59cf00618804 in _start (/home/mark/Coding/Repos/kodi-git/build_clang_debug_sanitizer/kodi.bin+0x9f91804) (BuildId: fa447ae84e6fbfe91e7ec718a600116496d7607e) SUMMARY: AddressSanitizer: heap-use-after-free xbmc/utils/RegExp.cpp:425:10 in CRegExp::PrivateRegFind(unsigned long, char const*, unsigned int, int) Shadow bytes around the buggy address: 0x519000040400: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x519000040480: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x519000040500: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x519000040580: fd fd fd fd fd fd fa fa fa fa fa fa fa fa fa fa 0x519000040600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x519000040680: fd fd fd fd fd fd fd fd fd fd fd fd fd[fd]fd fd 0x519000040700: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x519000040780: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x519000040800: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x519000040880: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x519000040900: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==28015==ABORTING --- xbmc/utils/RegExp.cpp | 19 +++++++++++++------ xbmc/utils/RegExp.h | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/xbmc/utils/RegExp.cpp b/xbmc/utils/RegExp.cpp index cb620208dcba8..c4ada8422af78 100644 --- a/xbmc/utils/RegExp.cpp +++ b/xbmc/utils/RegExp.cpp @@ -46,6 +46,7 @@ void CRegExp::InitValues(bool caseless /*= false*/, CRegExp::utf8Mode utf8 /*= a m_jitCompiled = false; m_bMatched = false; m_iMatchCount = 0; + m_matchData = nullptr; m_iOvector = nullptr; m_jitStack = NULL; } @@ -204,6 +205,7 @@ CRegExp::CRegExp(const CRegExp& re) { m_re = NULL; m_ctxt = nullptr; + m_matchData = nullptr; m_iOvector = nullptr; m_jitStack = NULL; m_utf8Mode = re.m_utf8Mode; @@ -303,7 +305,6 @@ int CRegExp::RegFind(const char *str, unsigned int startoffset /*= 0*/, int maxN int CRegExp::PrivateRegFind(size_t bufferLen, const char *str, unsigned int startoffset /* = 0*/, int maxNumberOfCharsToTest /*= -1*/) { - pcre2_match_data* md; PCRE2_SIZE offset; m_offset = 0; @@ -344,12 +345,12 @@ int CRegExp::PrivateRegFind(size_t bufferLen, const char *str, unsigned int star bufferLen = std::min(bufferLen, startoffset + maxNumberOfCharsToTest); m_subject.assign(str + startoffset, bufferLen - startoffset); - md = pcre2_match_data_create(OVECCOUNT, nullptr); + if (m_matchData == nullptr) + m_matchData = pcre2_match_data_create(OVECCOUNT, nullptr); int rc = pcre2_match(m_re, reinterpret_cast(m_subject.c_str()), m_subject.length(), 0, - 0, md, m_ctxt); - m_iOvector = pcre2_get_ovector_pointer(md); - offset = pcre2_get_startchar(md); - pcre2_match_data_free(md); + 0, m_matchData, m_ctxt); + m_iOvector = pcre2_get_ovector_pointer(m_matchData); + offset = pcre2_get_startchar(m_matchData); if (rc<1) { @@ -549,6 +550,12 @@ void CRegExp::Cleanup() pcre2_jit_stack_free(m_jitStack); m_jitStack = NULL; } + + if (m_matchData) + { + pcre2_match_data_free(m_matchData); + m_matchData = nullptr; + } } inline bool CRegExp::IsValidSubNumber(int iSub) const diff --git a/xbmc/utils/RegExp.h b/xbmc/utils/RegExp.h index 90d3db541e77a..2d018265c0f0a 100644 --- a/xbmc/utils/RegExp.h +++ b/xbmc/utils/RegExp.h @@ -134,6 +134,7 @@ class CRegExp pcre2_match_context* m_ctxt; static const int OVECCOUNT=(m_MaxNumOfBackrefrences + 1) * 3; unsigned int m_offset; + pcre2_match_data* m_matchData; PCRE2_SIZE* m_iOvector; utf8Mode m_utf8Mode; int m_iMatchCount; From 1b552f6ee2d4fdffb828224fa58b753857ba4ed8 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 29 Jun 2024 08:16:58 +1000 Subject: [PATCH 229/651] [tools/depends][target] exiv2 add patch for GCC13 cstdint include Already upstreamed, just no release as yet https://github.com/Exiv2/exiv2/pull/2969/commits/03436ce9c693dba9d074e9e99e792b5129463e26 --- cmake/modules/FindExiv2.cmake | 3 ++- tools/depends/target/exiv2/0005-GCC13-cstdint.patch | 10 ++++++++++ tools/depends/target/exiv2/Makefile | 6 +++++- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 tools/depends/target/exiv2/0005-GCC13-cstdint.patch diff --git a/cmake/modules/FindExiv2.cmake b/cmake/modules/FindExiv2.cmake index 8bd5df2543bd2..0496a11f0f69a 100644 --- a/cmake/modules/FindExiv2.cmake +++ b/cmake/modules/FindExiv2.cmake @@ -16,7 +16,8 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch" "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch" "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0003-UWP-Disable-getLoadedLibraries.patch" - "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0004-WIN-lib-postfix.patch") + "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0004-WIN-lib-postfix.patch" + "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0005-GCC13-cstdint.patch") generate_patchcommand("${patches}") diff --git a/tools/depends/target/exiv2/0005-GCC13-cstdint.patch b/tools/depends/target/exiv2/0005-GCC13-cstdint.patch new file mode 100644 index 0000000000000..72746e0605904 --- /dev/null +++ b/tools/depends/target/exiv2/0005-GCC13-cstdint.patch @@ -0,0 +1,10 @@ +--- a/src/futils.cpp ++++ b/src/futils.cpp +@@ -11,6 +11,7 @@ + // + standard includes + #include + #include ++#include + #include + #include + #include diff --git a/tools/depends/target/exiv2/Makefile b/tools/depends/target/exiv2/Makefile index b26a4459a4a4b..b289e02eb3608 100644 --- a/tools/depends/target/exiv2/Makefile +++ b/tools/depends/target/exiv2/Makefile @@ -2,7 +2,9 @@ include ../../Makefile.include EXIV2-VERSION ../../download-files.include DEPS = ../../Makefile.include Makefile EXIV2-VERSION ../../download-files.include \ 0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch \ 0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch \ - 0003-UWP-Disable-getLoadedLibraries.patch + 0003-UWP-Disable-getLoadedLibraries.patch \ + 0004-WIN-lib-postfix.patch \ + 0005-GCC13-cstdint.patch # configuration settings CMAKE_OPTIONS=-DCMAKE_INSTALL_PREFIX=$(PREFIX) \ @@ -30,6 +32,8 @@ $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) cd $(PLATFORM); patch -p1 -i ../0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch cd $(PLATFORM); patch -p1 -i ../0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch cd $(PLATFORM); patch -p1 -i ../0003-UWP-Disable-getLoadedLibraries.patch + cd $(PLATFORM); patch -p1 -i ../0004-WIN-lib-postfix.patch + cd $(PLATFORM); patch -p1 -i ../0005-GCC13-cstdint.patch cd $(PLATFORM)/build; $(CMAKE) $(CMAKE_OPTIONS) .. $(LIBDYLIB): $(PLATFORM) From 78851d4af8011d85e86ee459f28005d3e9680aaa Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Sat, 29 Jun 2024 21:41:53 +0200 Subject: [PATCH 230/651] fixed: avoid static global data for mime types this caused initialization order issues which broke tests on windows --- xbmc/utils/Mime.cpp | 1006 ++++++++++++++++++++++--------------------- xbmc/utils/Mime.h | 6 +- 2 files changed, 510 insertions(+), 502 deletions(-) diff --git a/xbmc/utils/Mime.cpp b/xbmc/utils/Mime.cpp index 2a3acd5c979a4..529ae109b081c 100644 --- a/xbmc/utils/Mime.cpp +++ b/xbmc/utils/Mime.cpp @@ -17,502 +17,513 @@ #include "video/VideoInfoTag.h" #include +#include -const std::map CMime::m_mimetypes = { - {{"3dm", "x-world/x-3dmf"}, - {"3dmf", "x-world/x-3dmf"}, - {"3fr", "image/3fr"}, - {"a", "application/octet-stream"}, - {"aab", "application/x-authorware-bin"}, - {"aam", "application/x-authorware-map"}, - {"aas", "application/x-authorware-seg"}, - {"abc", "text/vnd.abc"}, - {"acgi", "text/html"}, - {"afl", "video/animaflex"}, - {"ai", "application/postscript"}, - {"aif", "audio/aiff"}, - {"aifc", "audio/x-aiff"}, - {"aiff", "audio/aiff"}, - {"aim", "application/x-aim"}, - {"aip", "text/x-audiosoft-intra"}, - {"ani", "application/x-navi-animation"}, - {"aos", "application/x-nokia-9000-communicator-add-on-software"}, - {"apng", "image/apng"}, - {"aps", "application/mime"}, - {"arc", "application/octet-stream"}, - {"arj", "application/arj"}, - {"art", "image/x-jg"}, - {"arw", "image/arw"}, - {"asf", "video/x-ms-asf"}, - {"asm", "text/x-asm"}, - {"asp", "text/asp"}, - {"asx", "video/x-ms-asf"}, - {"au", "audio/basic"}, - {"avi", "video/avi"}, - {"avs", "video/avs-video"}, - {"bcpio", "application/x-bcpio"}, - {"bin", "application/octet-stream"}, - {"bm", "image/bmp"}, - {"bmp", "image/bmp"}, - {"boo", "application/book"}, - {"book", "application/book"}, - {"boz", "application/x-bzip2"}, - {"bsh", "application/x-bsh"}, - {"bz", "application/x-bzip"}, - {"bz2", "application/x-bzip2"}, - {"c", "text/plain"}, - {"c++", "text/plain"}, - {"cat", "application/vnd.ms-pki.seccat"}, - {"cc", "text/plain"}, - {"ccad", "application/clariscad"}, - {"cco", "application/x-cocoa"}, - {"cdf", "application/cdf"}, - {"cer", "application/pkix-cert"}, - {"cer", "application/x-x509-ca-cert"}, - {"cha", "application/x-chat"}, - {"chat", "application/x-chat"}, - {"class", "application/java"}, - {"com", "application/octet-stream"}, - {"conf", "text/plain"}, - {"cpio", "application/x-cpio"}, - {"cpp", "text/x-c"}, - {"cpt", "application/x-cpt"}, - {"crl", "application/pkcs-crl"}, - {"crt", "application/pkix-cert"}, - {"cr2", "image/cr2"}, - {"crw", "image/crw"}, - {"csh", "application/x-csh"}, - {"css", "text/css"}, - {"cxx", "text/plain"}, - {"dcr", "application/x-director"}, - {"deepv", "application/x-deepv"}, - {"def", "text/plain"}, - {"der", "application/x-x509-ca-cert"}, - {"dif", "video/x-dv"}, - {"dir", "application/x-director"}, - {"dl", "video/dl"}, - {"divx", "video/x-msvideo"}, - {"dng", "image/dng"}, - {"doc", "application/msword"}, - {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, - {"dot", "application/msword"}, - {"dp", "application/commonground"}, - {"drw", "application/drafting"}, - {"dump", "application/octet-stream"}, - {"dv", "video/x-dv"}, - {"dvi", "application/x-dvi"}, - {"dwf", "model/vnd.dwf"}, - {"dwg", "image/vnd.dwg"}, - {"dxf", "image/vnd.dwg"}, - {"dxr", "application/x-director"}, - {"el", "text/x-script.elisp"}, - {"elc", "application/x-elc"}, - {"env", "application/x-envoy"}, - {"eps", "application/postscript"}, - {"erf", "image/erf"}, - {"es", "application/x-esrehber"}, - {"etx", "text/x-setext"}, - {"evy", "application/envoy"}, - {"exe", "application/octet-stream"}, - {"f", "text/x-fortran"}, - {"f77", "text/x-fortran"}, - {"f90", "text/x-fortran"}, - {"fdf", "application/vnd.fdf"}, - {"fif", "image/fif"}, - {"flac", "audio/flac"}, - {"fli", "video/fli"}, - {"flo", "image/florian"}, - {"flv", "video/x-flv"}, - {"flx", "text/vnd.fmi.flexstor"}, - {"fmf", "video/x-atomic3d-feature"}, - {"for", "text/plain"}, - {"for", "text/x-fortran"}, - {"fpx", "image/vnd.fpx"}, - {"frl", "application/freeloader"}, - {"funk", "audio/make"}, - {"g", "text/plain"}, - {"g3", "image/g3fax"}, - {"gif", "image/gif"}, - {"gl", "video/x-gl"}, - {"gsd", "audio/x-gsm"}, - {"gsm", "audio/x-gsm"}, - {"gsp", "application/x-gsp"}, - {"gss", "application/x-gss"}, - {"gtar", "application/x-gtar"}, - {"gz", "application/x-compressed"}, - {"gzip", "application/x-gzip"}, - {"h", "text/plain"}, - {"hdf", "application/x-hdf"}, - {"heic", "image/heic"}, - {"heif", "image/heif"}, - {"help", "application/x-helpfile"}, - {"hgl", "application/vnd.hp-hpgl"}, - {"hh", "text/plain"}, - {"hlb", "text/x-script"}, - {"hlp", "application/hlp"}, - {"hpg", "application/vnd.hp-hpgl"}, - {"hpgl", "application/vnd.hp-hpgl"}, - {"hqx", "application/binhex"}, - {"hta", "application/hta"}, - {"htc", "text/x-component"}, - {"htm", "text/html"}, - {"html", "text/html"}, - {"htmls", "text/html"}, - {"htt", "text/webviewhtml"}, - {"htx", "text/html"}, - {"ice", "x-conference/x-cooltalk"}, - {"ico", "image/x-icon"}, - {"idc", "text/plain"}, - {"ief", "image/ief"}, - {"iefs", "image/ief"}, - {"iges", "application/iges"}, - {"igs", "application/iges"}, - {"ima", "application/x-ima"}, - {"imap", "application/x-httpd-imap"}, - {"inf", "application/inf"}, - {"ins", "application/x-internet-signup"}, - {"ip", "application/x-ip2"}, - {"isu", "video/x-isvideo"}, - {"it", "audio/it"}, - {"iv", "application/x-inventor"}, - {"ivr", "i-world/i-vrml"}, - {"ivy", "application/x-livescreen"}, - {"jam", "audio/x-jam"}, - {"jav", "text/x-java-source"}, - {"java", "text/x-java-source"}, - {"jcm", "application/x-java-commerce"}, - {"jfif", "image/jpeg"}, - {"jp2", "image/jp2"}, - {"jfif-tbnl", "image/jpeg"}, - {"jpe", "image/jpeg"}, - {"jpeg", "image/jpeg"}, - {"jpg", "image/jpeg"}, - {"jps", "image/x-jps"}, - {"js", "application/javascript"}, - {"json", "application/json"}, - {"jut", "image/jutvision"}, - {"kar", "music/x-karaoke"}, - {"kdc", "image/kdc"}, - {"ksh", "text/x-script.ksh"}, - {"la", "audio/nspaudio"}, - {"lam", "audio/x-liveaudio"}, - {"latex", "application/x-latex"}, - {"lha", "application/lha"}, - {"lhx", "application/octet-stream"}, - {"list", "text/plain"}, - {"lma", "audio/nspaudio"}, - {"log", "text/plain"}, - {"lsp", "application/x-lisp"}, - {"lst", "text/plain"}, - {"lsx", "text/x-la-asf"}, - {"ltx", "application/x-latex"}, - {"lzh", "application/x-lzh"}, - {"lzx", "application/lzx"}, - {"m", "text/x-m"}, - {"m1v", "video/mpeg"}, - {"m2a", "audio/mpeg"}, - {"m2v", "video/mpeg"}, - {"m3u", "audio/x-mpegurl"}, - {"man", "application/x-troff-man"}, - {"map", "application/x-navimap"}, - {"mar", "text/plain"}, - {"mbd", "application/mbedlet"}, - {"mc$", "application/x-magic-cap-package-1.0"}, - {"mcd", "application/x-mathcad"}, - {"mcf", "text/mcf"}, - {"mcp", "application/netmc"}, - {"mdc", "image/mdc"}, - {"me", "application/x-troff-me"}, - {"mef", "image/mef"}, - {"mht", "message/rfc822"}, - {"mhtml", "message/rfc822"}, - {"mid", "audio/midi"}, - {"midi", "audio/midi"}, - {"mif", "application/x-mif"}, - {"mime", "message/rfc822"}, - {"mjf", "audio/x-vnd.audioexplosion.mjuicemediafile"}, - {"mjpg", "video/x-motion-jpeg"}, - {"mka", "audio/x-matroska"}, - {"mkv", "video/x-matroska"}, - {"mk3d", "video/x-matroska-3d"}, - {"mm", "application/x-meme"}, - {"mme", "application/base64"}, - {"mod", "audio/mod"}, - {"moov", "video/quicktime"}, - {"mov", "video/quicktime"}, - {"movie", "video/x-sgi-movie"}, - {"mos", "image/mos"}, - {"mp2", "audio/mpeg"}, - {"mp3", "audio/mpeg3"}, - {"mp4", "video/mp4"}, - {"mpa", "audio/mpeg"}, - {"mpc", "application/x-project"}, - {"mpe", "video/mpeg"}, - {"mpeg", "video/mpeg"}, - {"mpg", "video/mpeg"}, - {"mpga", "audio/mpeg"}, - {"mpp", "application/vnd.ms-project"}, - {"mpt", "application/x-project"}, - {"mpv", "application/x-project"}, - {"mpx", "application/x-project"}, - {"mrc", "application/marc"}, - {"mrw", "image/mrw"}, - {"ms", "application/x-troff-ms"}, - {"mv", "video/x-sgi-movie"}, - {"my", "audio/make"}, - {"mzz", "application/x-vnd.audioexplosion.mzz"}, - {"nap", "image/naplps"}, - {"naplps", "image/naplps"}, - {"nc", "application/x-netcdf"}, - {"ncm", "application/vnd.nokia.configuration-message"}, - {"nef", "image/nef"}, - {"nfo", "text/xml"}, - {"nif", "image/x-niff"}, - {"niff", "image/x-niff"}, - {"nix", "application/x-mix-transfer"}, - {"nrw", "image/nrw"}, - {"nsc", "application/x-conference"}, - {"nvd", "application/x-navidoc"}, - {"o", "application/octet-stream"}, - {"oda", "application/oda"}, - {"ogg", "audio/ogg"}, - {"omc", "application/x-omc"}, - {"omcd", "application/x-omcdatamaker"}, - {"omcr", "application/x-omcregerator"}, - {"orf", "image/orf"}, - {"p", "text/x-pascal"}, - {"p10", "application/pkcs10"}, - {"p12", "application/pkcs-12"}, - {"p7a", "application/x-pkcs7-signature"}, - {"p7c", "application/pkcs7-mime"}, - {"p7m", "application/pkcs7-mime"}, - {"p7r", "application/x-pkcs7-certreqresp"}, - {"p7s", "application/pkcs7-signature"}, - {"part", "application/pro_eng"}, - {"pas", "text/pascal"}, - {"pbm", "image/x-portable-bitmap"}, - {"pcl", "application/vnd.hp-pcl"}, - {"pct", "image/x-pict"}, - {"pcx", "image/x-pcx"}, - {"pdb", "chemical/x-pdb"}, - {"pdf", "application/pdf"}, - {"pef", "image/pef"}, - {"pfunk", "audio/make.my.funk"}, - {"pgm", "image/x-portable-greymap"}, - {"pic", "image/pict"}, - {"pict", "image/pict"}, - {"pkg", "application/x-newton-compatible-pkg"}, - {"pko", "application/vnd.ms-pki.pko"}, - {"pl", "text/x-script.perl"}, - {"plx", "application/x-pixclscript"}, - {"pm", "text/x-script.perl-module"}, - {"pm4", "application/x-pagemaker"}, - {"pm5", "application/x-pagemaker"}, - {"png", "image/png"}, - {"pnm", "application/x-portable-anymap"}, - {"pot", "application/vnd.ms-powerpoint"}, - {"pov", "model/x-pov"}, - {"ppa", "application/vnd.ms-powerpoint"}, - {"ppm", "image/x-portable-pixmap"}, - {"pps", "application/mspowerpoint"}, - {"ppt", "application/mspowerpoint"}, - {"ppz", "application/mspowerpoint"}, - {"pre", "application/x-freelance"}, - {"prt", "application/pro_eng"}, - {"ps", "application/postscript"}, - {"psd", "application/octet-stream"}, - {"pvu", "paleovu/x-pv"}, - {"pwz", "application/vnd.ms-powerpoint"}, - {"py", "text/x-script.python"}, - {"pyc", "application/x-bytecode.python"}, - {"qcp", "audio/vnd.qcelp"}, - {"qd3", "x-world/x-3dmf"}, - {"qd3d", "x-world/x-3dmf"}, - {"qif", "image/x-quicktime"}, - {"qt", "video/quicktime"}, - {"qtc", "video/x-qtc"}, - {"qti", "image/x-quicktime"}, - {"qtif", "image/x-quicktime"}, - {"ra", "audio/x-realaudio"}, - {"raf", "image/raf"}, - {"ram", "audio/x-pn-realaudio"}, - {"ras", "image/cmu-raster"}, - {"rast", "image/cmu-raster"}, - {"raw", "image/raw"}, - {"rexx", "text/x-script.rexx"}, - {"rf", "image/vnd.rn-realflash"}, - {"rgb", "image/x-rgb"}, - {"rm", "application/vnd.rn-realmedia"}, - {"rmi", "audio/mid"}, - {"rmm", "audio/x-pn-realaudio"}, - {"rmp", "audio/x-pn-realaudio"}, - {"rng", "application/ringing-tones"}, - {"rnx", "application/vnd.rn-realplayer"}, - {"roff", "application/x-troff"}, - {"rp", "image/vnd.rn-realpix"}, - {"rpm", "audio/x-pn-realaudio-plugin"}, - {"rt", "text/richtext"}, - {"rtf", "text/richtext"}, - {"rtx", "text/richtext"}, - {"rv", "video/vnd.rn-realvideo"}, - {"rw2", "image/rw2"}, - {"s", "text/x-asm"}, - {"s3m", "audio/s3m"}, - {"saveme", "application/octet-stream"}, - {"sbk", "application/x-tbook"}, - {"scm", "video/x-scm"}, - {"sdml", "text/plain"}, - {"sdp", "application/sdp"}, - {"sdr", "application/sounder"}, - {"sea", "application/sea"}, - {"set", "application/set"}, - {"sgm", "text/sgml"}, - {"sgml", "text/sgml"}, - {"sh", "text/x-script.sh"}, - {"shar", "application/x-bsh"}, - {"shtml", "text/x-server-parsed-html"}, - {"sid", "audio/x-psid"}, - {"sit", "application/x-stuffit"}, - {"skd", "application/x-koan"}, - {"skm", "application/x-koan"}, - {"skp", "application/x-koan"}, - {"skt", "application/x-koan"}, - {"sl", "application/x-seelogo"}, - {"smi", "application/smil"}, - {"smil", "application/smil"}, - {"snd", "audio/basic"}, - {"sol", "application/solids"}, - {"spc", "text/x-speech"}, - {"spl", "application/futuresplash"}, - {"spr", "application/x-sprite"}, - {"sprite", "application/x-sprite"}, - {"src", "application/x-wais-source"}, - {"srw", "image/srw"}, - {"ssi", "text/x-server-parsed-html"}, - {"ssm", "application/streamingmedia"}, - {"sst", "application/vnd.ms-pki.certstore"}, - {"step", "application/step"}, - {"stl", "application/sla"}, - {"stp", "application/step"}, - {"sup", "application/x-pgs"}, - {"sv4cpio", "application/x-sv4cpio"}, - {"sv4crc", "application/x-sv4crc"}, - {"svf", "image/vnd.dwg"}, - {"svg", "image/svg+xml"}, - {"svr", "application/x-world"}, - {"swf", "application/x-shockwave-flash"}, - {"t", "application/x-troff"}, - {"talk", "text/x-speech"}, - {"tar", "application/x-tar"}, - {"tbk", "application/toolbook"}, - {"tcl", "text/x-script.tcl"}, - {"tcsh", "text/x-script.tcsh"}, - {"tex", "application/x-tex"}, - {"texi", "application/x-texinfo"}, - {"texinfo", "application/x-texinfo"}, - {"text", "text/plain"}, - {"tgz", "application/x-compressed"}, - {"tif", "image/tiff"}, - {"tiff", "image/tiff"}, - {"tr", "application/x-troff"}, - {"ts", "video/mp2t"}, - {"tsi", "audio/tsp-audio"}, - {"tsp", "audio/tsplayer"}, - {"tsv", "text/tab-separated-values"}, - {"turbot", "image/florian"}, - {"txt", "text/plain"}, - {"uil", "text/x-uil"}, - {"uni", "text/uri-list"}, - {"unis", "text/uri-list"}, - {"unv", "application/i-deas"}, - {"uri", "text/uri-list"}, - {"uris", "text/uri-list"}, - {"ustar", "application/x-ustar"}, - {"uu", "text/x-uuencode"}, - {"uue", "text/x-uuencode"}, - {"vcd", "application/x-cdlink"}, - {"vcs", "text/x-vcalendar"}, - {"vda", "application/vda"}, - {"vdo", "video/vdo"}, - {"vew", "application/groupwise"}, - {"viv", "video/vivo"}, - {"vivo", "video/vivo"}, - {"vmd", "application/vocaltec-media-desc"}, - {"vmf", "application/vocaltec-media-file"}, - {"voc", "audio/voc"}, - {"vos", "video/vosaic"}, - {"vox", "audio/voxware"}, - {"vqe", "audio/x-twinvq-plugin"}, - {"vqf", "audio/x-twinvq"}, - {"vql", "audio/x-twinvq-plugin"}, - {"vrml", "application/x-vrml"}, - {"vrt", "x-world/x-vrt"}, - {"vsd", "application/x-visio"}, - {"vst", "application/x-visio"}, - {"vsw", "application/x-visio"}, - {"vtt", "text/vtt"}, - {"w60", "application/wordperfect6.0"}, - {"w61", "application/wordperfect6.1"}, - {"w6w", "application/msword"}, - {"wav", "audio/wav"}, - {"wb1", "application/x-qpro"}, - {"wbmp", "image/vnd.wap.wbmp"}, - {"web", "application/vnd.xara"}, - {"webp", "image/webp"}, - {"wiz", "application/msword"}, - {"wk1", "application/x-123"}, - {"wma", "audio/x-ms-wma"}, - {"wmf", "windows/metafile"}, - {"wml", "text/vnd.wap.wml"}, - {"wmlc", "application/vnd.wap.wmlc"}, - {"wmls", "text/vnd.wap.wmlscript"}, - {"wmlsc", "application/vnd.wap.wmlscriptc"}, - {"wmv", "video/x-ms-wmv"}, - {"word", "application/msword"}, - {"wp", "application/wordperfect"}, - {"wp5", "application/wordperfect"}, - {"wp6", "application/wordperfect"}, - {"wpd", "application/wordperfect"}, - {"wq1", "application/x-lotus"}, - {"wri", "application/mswrite"}, - {"wrl", "model/vrml"}, - {"wrz", "model/vrml"}, - {"wsc", "text/scriplet"}, - {"wsrc", "application/x-wais-source"}, - {"wtk", "application/x-wintalk"}, - {"x3f", "image/x3f"}, - {"xbm", "image/xbm"}, - {"xdr", "video/x-amt-demorun"}, - {"xgz", "xgl/drawing"}, - {"xif", "image/vnd.xiff"}, - {"xl", "application/excel"}, - {"xla", "application/excel"}, - {"xlb", "application/excel"}, - {"xlc", "application/excel"}, - {"xld", "application/excel"}, - {"xlk", "application/excel"}, - {"xll", "application/excel"}, - {"xlm", "application/excel"}, - {"xls", "application/excel"}, - {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, - {"xlt", "application/excel"}, - {"xlv", "application/excel"}, - {"xlw", "application/excel"}, - {"xm", "audio/xm"}, - {"xml", "text/xml"}, - {"xmz", "xgl/movie"}, - {"xpix", "application/x-vnd.ls-xpix"}, - {"xpm", "image/xpm"}, - {"x-png", "image/png"}, - {"xspf", "application/xspf+xml"}, - {"xsr", "video/x-amt-showrun"}, - {"xvid", "video/x-msvideo"}, - {"xwd", "image/x-xwd"}, - {"xyz", "chemical/x-pdb"}, - {"z", "application/x-compressed"}, - {"zip", "application/zip"}, - {"zoo", "application/octet-stream"}, - {"zsh", "text/x-script.zsh"}}}; +namespace +{ + +const std::unordered_map& GetMap() +{ + static const std::unordered_map mimetypes = { + {"3dm", "x-world/x-3dmf"}, + {"3dmf", "x-world/x-3dmf"}, + {"3fr", "image/3fr"}, + {"a", "application/octet-stream"}, + {"aab", "application/x-authorware-bin"}, + {"aam", "application/x-authorware-map"}, + {"aas", "application/x-authorware-seg"}, + {"abc", "text/vnd.abc"}, + {"acgi", "text/html"}, + {"afl", "video/animaflex"}, + {"ai", "application/postscript"}, + {"aif", "audio/aiff"}, + {"aifc", "audio/x-aiff"}, + {"aiff", "audio/aiff"}, + {"aim", "application/x-aim"}, + {"aip", "text/x-audiosoft-intra"}, + {"ani", "application/x-navi-animation"}, + {"aos", "application/x-nokia-9000-communicator-add-on-software"}, + {"apng", "image/apng"}, + {"aps", "application/mime"}, + {"arc", "application/octet-stream"}, + {"arj", "application/arj"}, + {"art", "image/x-jg"}, + {"arw", "image/arw"}, + {"asf", "video/x-ms-asf"}, + {"asm", "text/x-asm"}, + {"asp", "text/asp"}, + {"asx", "video/x-ms-asf"}, + {"au", "audio/basic"}, + {"avi", "video/avi"}, + {"avs", "video/avs-video"}, + {"bcpio", "application/x-bcpio"}, + {"bin", "application/octet-stream"}, + {"bm", "image/bmp"}, + {"bmp", "image/bmp"}, + {"boo", "application/book"}, + {"book", "application/book"}, + {"boz", "application/x-bzip2"}, + {"bsh", "application/x-bsh"}, + {"bz", "application/x-bzip"}, + {"bz2", "application/x-bzip2"}, + {"c", "text/plain"}, + {"c++", "text/plain"}, + {"cat", "application/vnd.ms-pki.seccat"}, + {"cc", "text/plain"}, + {"ccad", "application/clariscad"}, + {"cco", "application/x-cocoa"}, + {"cdf", "application/cdf"}, + {"cer", "application/pkix-cert"}, + {"cer", "application/x-x509-ca-cert"}, + {"cha", "application/x-chat"}, + {"chat", "application/x-chat"}, + {"class", "application/java"}, + {"com", "application/octet-stream"}, + {"conf", "text/plain"}, + {"cpio", "application/x-cpio"}, + {"cpp", "text/x-c"}, + {"cpt", "application/x-cpt"}, + {"crl", "application/pkcs-crl"}, + {"crt", "application/pkix-cert"}, + {"cr2", "image/cr2"}, + {"crw", "image/crw"}, + {"csh", "application/x-csh"}, + {"css", "text/css"}, + {"cxx", "text/plain"}, + {"dcr", "application/x-director"}, + {"deepv", "application/x-deepv"}, + {"def", "text/plain"}, + {"der", "application/x-x509-ca-cert"}, + {"dif", "video/x-dv"}, + {"dir", "application/x-director"}, + {"dl", "video/dl"}, + {"divx", "video/x-msvideo"}, + {"dng", "image/dng"}, + {"doc", "application/msword"}, + {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {"dot", "application/msword"}, + {"dp", "application/commonground"}, + {"drw", "application/drafting"}, + {"dump", "application/octet-stream"}, + {"dv", "video/x-dv"}, + {"dvi", "application/x-dvi"}, + {"dwf", "model/vnd.dwf"}, + {"dwg", "image/vnd.dwg"}, + {"dxf", "image/vnd.dwg"}, + {"dxr", "application/x-director"}, + {"el", "text/x-script.elisp"}, + {"elc", "application/x-elc"}, + {"env", "application/x-envoy"}, + {"eps", "application/postscript"}, + {"erf", "image/erf"}, + {"es", "application/x-esrehber"}, + {"etx", "text/x-setext"}, + {"evy", "application/envoy"}, + {"exe", "application/octet-stream"}, + {"f", "text/x-fortran"}, + {"f77", "text/x-fortran"}, + {"f90", "text/x-fortran"}, + {"fdf", "application/vnd.fdf"}, + {"fif", "image/fif"}, + {"flac", "audio/flac"}, + {"fli", "video/fli"}, + {"flo", "image/florian"}, + {"flv", "video/x-flv"}, + {"flx", "text/vnd.fmi.flexstor"}, + {"fmf", "video/x-atomic3d-feature"}, + {"for", "text/plain"}, + {"for", "text/x-fortran"}, + {"fpx", "image/vnd.fpx"}, + {"frl", "application/freeloader"}, + {"funk", "audio/make"}, + {"g", "text/plain"}, + {"g3", "image/g3fax"}, + {"gif", "image/gif"}, + {"gl", "video/x-gl"}, + {"gsd", "audio/x-gsm"}, + {"gsm", "audio/x-gsm"}, + {"gsp", "application/x-gsp"}, + {"gss", "application/x-gss"}, + {"gtar", "application/x-gtar"}, + {"gz", "application/x-compressed"}, + {"gzip", "application/x-gzip"}, + {"h", "text/plain"}, + {"hdf", "application/x-hdf"}, + {"heic", "image/heic"}, + {"heif", "image/heif"}, + {"help", "application/x-helpfile"}, + {"hgl", "application/vnd.hp-hpgl"}, + {"hh", "text/plain"}, + {"hlb", "text/x-script"}, + {"hlp", "application/hlp"}, + {"hpg", "application/vnd.hp-hpgl"}, + {"hpgl", "application/vnd.hp-hpgl"}, + {"hqx", "application/binhex"}, + {"hta", "application/hta"}, + {"htc", "text/x-component"}, + {"htm", "text/html"}, + {"html", "text/html"}, + {"htmls", "text/html"}, + {"htt", "text/webviewhtml"}, + {"htx", "text/html"}, + {"ice", "x-conference/x-cooltalk"}, + {"ico", "image/x-icon"}, + {"idc", "text/plain"}, + {"ief", "image/ief"}, + {"iefs", "image/ief"}, + {"iges", "application/iges"}, + {"igs", "application/iges"}, + {"ima", "application/x-ima"}, + {"imap", "application/x-httpd-imap"}, + {"inf", "application/inf"}, + {"ins", "application/x-internet-signup"}, + {"ip", "application/x-ip2"}, + {"isu", "video/x-isvideo"}, + {"it", "audio/it"}, + {"iv", "application/x-inventor"}, + {"ivr", "i-world/i-vrml"}, + {"ivy", "application/x-livescreen"}, + {"jam", "audio/x-jam"}, + {"jav", "text/x-java-source"}, + {"java", "text/x-java-source"}, + {"jcm", "application/x-java-commerce"}, + {"jfif", "image/jpeg"}, + {"jp2", "image/jp2"}, + {"jfif-tbnl", "image/jpeg"}, + {"jpe", "image/jpeg"}, + {"jpeg", "image/jpeg"}, + {"jpg", "image/jpeg"}, + {"jps", "image/x-jps"}, + {"js", "application/javascript"}, + {"json", "application/json"}, + {"jut", "image/jutvision"}, + {"kar", "music/x-karaoke"}, + {"kdc", "image/kdc"}, + {"ksh", "text/x-script.ksh"}, + {"la", "audio/nspaudio"}, + {"lam", "audio/x-liveaudio"}, + {"latex", "application/x-latex"}, + {"lha", "application/lha"}, + {"lhx", "application/octet-stream"}, + {"list", "text/plain"}, + {"lma", "audio/nspaudio"}, + {"log", "text/plain"}, + {"lsp", "application/x-lisp"}, + {"lst", "text/plain"}, + {"lsx", "text/x-la-asf"}, + {"ltx", "application/x-latex"}, + {"lzh", "application/x-lzh"}, + {"lzx", "application/lzx"}, + {"m", "text/x-m"}, + {"m1v", "video/mpeg"}, + {"m2a", "audio/mpeg"}, + {"m2v", "video/mpeg"}, + {"m3u", "audio/x-mpegurl"}, + {"man", "application/x-troff-man"}, + {"map", "application/x-navimap"}, + {"mar", "text/plain"}, + {"mbd", "application/mbedlet"}, + {"mc$", "application/x-magic-cap-package-1.0"}, + {"mcd", "application/x-mathcad"}, + {"mcf", "text/mcf"}, + {"mcp", "application/netmc"}, + {"mdc", "image/mdc"}, + {"me", "application/x-troff-me"}, + {"mef", "image/mef"}, + {"mht", "message/rfc822"}, + {"mhtml", "message/rfc822"}, + {"mid", "audio/midi"}, + {"midi", "audio/midi"}, + {"mif", "application/x-mif"}, + {"mime", "message/rfc822"}, + {"mjf", "audio/x-vnd.audioexplosion.mjuicemediafile"}, + {"mjpg", "video/x-motion-jpeg"}, + {"mka", "audio/x-matroska"}, + {"mkv", "video/x-matroska"}, + {"mk3d", "video/x-matroska-3d"}, + {"mm", "application/x-meme"}, + {"mme", "application/base64"}, + {"mod", "audio/mod"}, + {"moov", "video/quicktime"}, + {"mov", "video/quicktime"}, + {"movie", "video/x-sgi-movie"}, + {"mos", "image/mos"}, + {"mp2", "audio/mpeg"}, + {"mp3", "audio/mpeg3"}, + {"mp4", "video/mp4"}, + {"mpa", "audio/mpeg"}, + {"mpc", "application/x-project"}, + {"mpe", "video/mpeg"}, + {"mpeg", "video/mpeg"}, + {"mpg", "video/mpeg"}, + {"mpga", "audio/mpeg"}, + {"mpp", "application/vnd.ms-project"}, + {"mpt", "application/x-project"}, + {"mpv", "application/x-project"}, + {"mpx", "application/x-project"}, + {"mrc", "application/marc"}, + {"mrw", "image/mrw"}, + {"ms", "application/x-troff-ms"}, + {"mv", "video/x-sgi-movie"}, + {"my", "audio/make"}, + {"mzz", "application/x-vnd.audioexplosion.mzz"}, + {"nap", "image/naplps"}, + {"naplps", "image/naplps"}, + {"nc", "application/x-netcdf"}, + {"ncm", "application/vnd.nokia.configuration-message"}, + {"nef", "image/nef"}, + {"nfo", "text/xml"}, + {"nif", "image/x-niff"}, + {"niff", "image/x-niff"}, + {"nix", "application/x-mix-transfer"}, + {"nrw", "image/nrw"}, + {"nsc", "application/x-conference"}, + {"nvd", "application/x-navidoc"}, + {"o", "application/octet-stream"}, + {"oda", "application/oda"}, + {"ogg", "audio/ogg"}, + {"omc", "application/x-omc"}, + {"omcd", "application/x-omcdatamaker"}, + {"omcr", "application/x-omcregerator"}, + {"orf", "image/orf"}, + {"p", "text/x-pascal"}, + {"p10", "application/pkcs10"}, + {"p12", "application/pkcs-12"}, + {"p7a", "application/x-pkcs7-signature"}, + {"p7c", "application/pkcs7-mime"}, + {"p7m", "application/pkcs7-mime"}, + {"p7r", "application/x-pkcs7-certreqresp"}, + {"p7s", "application/pkcs7-signature"}, + {"part", "application/pro_eng"}, + {"pas", "text/pascal"}, + {"pbm", "image/x-portable-bitmap"}, + {"pcl", "application/vnd.hp-pcl"}, + {"pct", "image/x-pict"}, + {"pcx", "image/x-pcx"}, + {"pdb", "chemical/x-pdb"}, + {"pdf", "application/pdf"}, + {"pef", "image/pef"}, + {"pfunk", "audio/make.my.funk"}, + {"pgm", "image/x-portable-greymap"}, + {"pic", "image/pict"}, + {"pict", "image/pict"}, + {"pkg", "application/x-newton-compatible-pkg"}, + {"pko", "application/vnd.ms-pki.pko"}, + {"pl", "text/x-script.perl"}, + {"plx", "application/x-pixclscript"}, + {"pm", "text/x-script.perl-module"}, + {"pm4", "application/x-pagemaker"}, + {"pm5", "application/x-pagemaker"}, + {"png", "image/png"}, + {"pnm", "application/x-portable-anymap"}, + {"pot", "application/vnd.ms-powerpoint"}, + {"pov", "model/x-pov"}, + {"ppa", "application/vnd.ms-powerpoint"}, + {"ppm", "image/x-portable-pixmap"}, + {"pps", "application/mspowerpoint"}, + {"ppt", "application/mspowerpoint"}, + {"ppz", "application/mspowerpoint"}, + {"pre", "application/x-freelance"}, + {"prt", "application/pro_eng"}, + {"ps", "application/postscript"}, + {"psd", "application/octet-stream"}, + {"pvu", "paleovu/x-pv"}, + {"pwz", "application/vnd.ms-powerpoint"}, + {"py", "text/x-script.python"}, + {"pyc", "application/x-bytecode.python"}, + {"qcp", "audio/vnd.qcelp"}, + {"qd3", "x-world/x-3dmf"}, + {"qd3d", "x-world/x-3dmf"}, + {"qif", "image/x-quicktime"}, + {"qt", "video/quicktime"}, + {"qtc", "video/x-qtc"}, + {"qti", "image/x-quicktime"}, + {"qtif", "image/x-quicktime"}, + {"ra", "audio/x-realaudio"}, + {"raf", "image/raf"}, + {"ram", "audio/x-pn-realaudio"}, + {"ras", "image/cmu-raster"}, + {"rast", "image/cmu-raster"}, + {"raw", "image/raw"}, + {"rexx", "text/x-script.rexx"}, + {"rf", "image/vnd.rn-realflash"}, + {"rgb", "image/x-rgb"}, + {"rm", "application/vnd.rn-realmedia"}, + {"rmi", "audio/mid"}, + {"rmm", "audio/x-pn-realaudio"}, + {"rmp", "audio/x-pn-realaudio"}, + {"rng", "application/ringing-tones"}, + {"rnx", "application/vnd.rn-realplayer"}, + {"roff", "application/x-troff"}, + {"rp", "image/vnd.rn-realpix"}, + {"rpm", "audio/x-pn-realaudio-plugin"}, + {"rt", "text/richtext"}, + {"rtf", "text/richtext"}, + {"rtx", "text/richtext"}, + {"rv", "video/vnd.rn-realvideo"}, + {"rw2", "image/rw2"}, + {"s", "text/x-asm"}, + {"s3m", "audio/s3m"}, + {"saveme", "application/octet-stream"}, + {"sbk", "application/x-tbook"}, + {"scm", "video/x-scm"}, + {"sdml", "text/plain"}, + {"sdp", "application/sdp"}, + {"sdr", "application/sounder"}, + {"sea", "application/sea"}, + {"set", "application/set"}, + {"sgm", "text/sgml"}, + {"sgml", "text/sgml"}, + {"sh", "text/x-script.sh"}, + {"shar", "application/x-bsh"}, + {"shtml", "text/x-server-parsed-html"}, + {"sid", "audio/x-psid"}, + {"sit", "application/x-stuffit"}, + {"skd", "application/x-koan"}, + {"skm", "application/x-koan"}, + {"skp", "application/x-koan"}, + {"skt", "application/x-koan"}, + {"sl", "application/x-seelogo"}, + {"smi", "application/smil"}, + {"smil", "application/smil"}, + {"snd", "audio/basic"}, + {"sol", "application/solids"}, + {"spc", "text/x-speech"}, + {"spl", "application/futuresplash"}, + {"spr", "application/x-sprite"}, + {"sprite", "application/x-sprite"}, + {"src", "application/x-wais-source"}, + {"srw", "image/srw"}, + {"ssi", "text/x-server-parsed-html"}, + {"ssm", "application/streamingmedia"}, + {"sst", "application/vnd.ms-pki.certstore"}, + {"step", "application/step"}, + {"stl", "application/sla"}, + {"stp", "application/step"}, + {"sup", "application/x-pgs"}, + {"sv4cpio", "application/x-sv4cpio"}, + {"sv4crc", "application/x-sv4crc"}, + {"svf", "image/vnd.dwg"}, + {"svg", "image/svg+xml"}, + {"svr", "application/x-world"}, + {"swf", "application/x-shockwave-flash"}, + {"t", "application/x-troff"}, + {"talk", "text/x-speech"}, + {"tar", "application/x-tar"}, + {"tbk", "application/toolbook"}, + {"tcl", "text/x-script.tcl"}, + {"tcsh", "text/x-script.tcsh"}, + {"tex", "application/x-tex"}, + {"texi", "application/x-texinfo"}, + {"texinfo", "application/x-texinfo"}, + {"text", "text/plain"}, + {"tgz", "application/x-compressed"}, + {"tif", "image/tiff"}, + {"tiff", "image/tiff"}, + {"tr", "application/x-troff"}, + {"ts", "video/mp2t"}, + {"tsi", "audio/tsp-audio"}, + {"tsp", "audio/tsplayer"}, + {"tsv", "text/tab-separated-values"}, + {"turbot", "image/florian"}, + {"txt", "text/plain"}, + {"uil", "text/x-uil"}, + {"uni", "text/uri-list"}, + {"unis", "text/uri-list"}, + {"unv", "application/i-deas"}, + {"uri", "text/uri-list"}, + {"uris", "text/uri-list"}, + {"ustar", "application/x-ustar"}, + {"uu", "text/x-uuencode"}, + {"uue", "text/x-uuencode"}, + {"vcd", "application/x-cdlink"}, + {"vcs", "text/x-vcalendar"}, + {"vda", "application/vda"}, + {"vdo", "video/vdo"}, + {"vew", "application/groupwise"}, + {"viv", "video/vivo"}, + {"vivo", "video/vivo"}, + {"vmd", "application/vocaltec-media-desc"}, + {"vmf", "application/vocaltec-media-file"}, + {"voc", "audio/voc"}, + {"vos", "video/vosaic"}, + {"vox", "audio/voxware"}, + {"vqe", "audio/x-twinvq-plugin"}, + {"vqf", "audio/x-twinvq"}, + {"vql", "audio/x-twinvq-plugin"}, + {"vrml", "application/x-vrml"}, + {"vrt", "x-world/x-vrt"}, + {"vsd", "application/x-visio"}, + {"vst", "application/x-visio"}, + {"vsw", "application/x-visio"}, + {"vtt", "text/vtt"}, + {"w60", "application/wordperfect6.0"}, + {"w61", "application/wordperfect6.1"}, + {"w6w", "application/msword"}, + {"wav", "audio/wav"}, + {"wb1", "application/x-qpro"}, + {"wbmp", "image/vnd.wap.wbmp"}, + {"web", "application/vnd.xara"}, + {"webp", "image/webp"}, + {"wiz", "application/msword"}, + {"wk1", "application/x-123"}, + {"wma", "audio/x-ms-wma"}, + {"wmf", "windows/metafile"}, + {"wml", "text/vnd.wap.wml"}, + {"wmlc", "application/vnd.wap.wmlc"}, + {"wmls", "text/vnd.wap.wmlscript"}, + {"wmlsc", "application/vnd.wap.wmlscriptc"}, + {"wmv", "video/x-ms-wmv"}, + {"word", "application/msword"}, + {"wp", "application/wordperfect"}, + {"wp5", "application/wordperfect"}, + {"wp6", "application/wordperfect"}, + {"wpd", "application/wordperfect"}, + {"wq1", "application/x-lotus"}, + {"wri", "application/mswrite"}, + {"wrl", "model/vrml"}, + {"wrz", "model/vrml"}, + {"wsc", "text/scriplet"}, + {"wsrc", "application/x-wais-source"}, + {"wtk", "application/x-wintalk"}, + {"x3f", "image/x3f"}, + {"xbm", "image/xbm"}, + {"xdr", "video/x-amt-demorun"}, + {"xgz", "xgl/drawing"}, + {"xif", "image/vnd.xiff"}, + {"xl", "application/excel"}, + {"xla", "application/excel"}, + {"xlb", "application/excel"}, + {"xlc", "application/excel"}, + {"xld", "application/excel"}, + {"xlk", "application/excel"}, + {"xll", "application/excel"}, + {"xlm", "application/excel"}, + {"xls", "application/excel"}, + {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {"xlt", "application/excel"}, + {"xlv", "application/excel"}, + {"xlw", "application/excel"}, + {"xm", "audio/xm"}, + {"xml", "text/xml"}, + {"xmz", "xgl/movie"}, + {"xpix", "application/x-vnd.ls-xpix"}, + {"xpm", "image/xpm"}, + {"x-png", "image/png"}, + {"xspf", "application/xspf+xml"}, + {"xsr", "video/x-amt-showrun"}, + {"xvid", "video/x-msvideo"}, + {"xwd", "image/x-xwd"}, + {"xyz", "chemical/x-pdb"}, + {"z", "application/x-compressed"}, + {"zip", "application/zip"}, + {"zoo", "application/octet-stream"}, + {"zsh", "text/x-script.zsh"}}; + + return mimetypes; +} + +} // namespace std::string CMime::GetMimeType(const std::string &extension) { @@ -525,8 +536,9 @@ std::string CMime::GetMimeType(const std::string &extension) ext = extension.substr(posNotPoint); transform(ext.begin(), ext.end(), ext.begin(), ::tolower); - std::map::const_iterator it = m_mimetypes.find(ext); - if (it != m_mimetypes.end()) + const auto& mime_map = GetMap(); + const auto it = mime_map.find(ext); + if (it != mime_map.end()) return it->second; return ""; diff --git a/xbmc/utils/Mime.h b/xbmc/utils/Mime.h index d3554b9945ece..72cc7ab9610bd 100644 --- a/xbmc/utils/Mime.h +++ b/xbmc/utils/Mime.h @@ -8,12 +8,10 @@ #pragma once -#include #include -class CURL; - class CFileItem; +class CURL; class CMime { @@ -41,6 +39,4 @@ class CMime private: static bool parseMimeType(const std::string& mimeType, std::string& type, std::string& subtype); - - static const std::map m_mimetypes; }; From 382375835424353e115b8437941d4a3b580571a0 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Sat, 29 Jun 2024 21:42:32 +0200 Subject: [PATCH 231/651] fixed: video classify tests now that mime types are operational the mimetype from the file extension would override the intended checks for tags these entries were testing. change the file extensions to avoid this --- xbmc/video/test/TestVideoFileItemClassify.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xbmc/video/test/TestVideoFileItemClassify.cpp b/xbmc/video/test/TestVideoFileItemClassify.cpp index 128980f68d551..c9c500bc78fa6 100644 --- a/xbmc/video/test/TestVideoFileItemClassify.cpp +++ b/xbmc/video/test/TestVideoFileItemClassify.cpp @@ -113,9 +113,9 @@ TEST_P(VideoTest, IsVideo) const auto video_tests = std::array{ VideoClassifyTest{"/home/user/video.avi", true, "video/avi"}, VideoClassifyTest{"/home/user/video.avi", true, "", 1}, - VideoClassifyTest{"/home/user/video.avi", false, "", 2}, - VideoClassifyTest{"/home/user/video.avi", false, "", 3}, - VideoClassifyTest{"/home/user/video.avi", false, "", 4}, + VideoClassifyTest{"/home/user/video.gam", false, "", 2}, + VideoClassifyTest{"/home/user/video.mus", false, "", 3}, + VideoClassifyTest{"/home/user/video.pic", false, "", 4}, VideoClassifyTest{"pvr://recordings/tv/1", true}, VideoClassifyTest{"pvr://123", false}, VideoClassifyTest{"dvd://VIDEO_TS/video_ts.ifo", true}, From 54ac31dc7be0d3090e831c8752295ad52b09855a Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sun, 30 Jun 2024 10:14:28 +0200 Subject: [PATCH 232/651] [Windows] remove blue color from tests script --- tools/buildsteps/windows/run-tests.bat | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/buildsteps/windows/run-tests.bat b/tools/buildsteps/windows/run-tests.bat index 2bde835b0aacb..38d64b84149b8 100644 --- a/tools/buildsteps/windows/run-tests.bat +++ b/tools/buildsteps/windows/run-tests.bat @@ -11,7 +11,6 @@ REM read the version values from version.txt FOR /f "tokens=1,2" %%i IN (%WORKSPACE%\version.txt) DO IF "%%i" == "APP_NAME" SET APP_NAME=%%j CLS -COLOR 1B TITLE %APP_NAME% testsuite Build-/Runscript rem ------------------------------------------------------------- From 5a750d7084871d7442a9a9468ed7141763f418f9 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Sun, 30 Jun 2024 13:05:22 +0200 Subject: [PATCH 233/651] add std:: prefix to shut up ksooo-format --- xbmc/utils/Mime.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xbmc/utils/Mime.cpp b/xbmc/utils/Mime.cpp index 529ae109b081c..63c4ac8b701b2 100644 --- a/xbmc/utils/Mime.cpp +++ b/xbmc/utils/Mime.cpp @@ -17,6 +17,7 @@ #include "video/VideoInfoTag.h" #include +#include #include namespace @@ -534,7 +535,7 @@ std::string CMime::GetMimeType(const std::string &extension) size_t posNotPoint = ext.find_first_not_of('.'); if (posNotPoint != std::string::npos && posNotPoint > 0) ext = extension.substr(posNotPoint); - transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); const auto& mime_map = GetMap(); const auto it = mime_map.find(ext); From ab1cf0cf792083fcceff7788a58a6773fe67578b Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 29 Jun 2024 17:23:02 +0200 Subject: [PATCH 234/651] [PVR] Fix CPVRTimers::UpdateEntries: Delete existing epg-based local timers if the respective EPG tag does no longer exist. --- xbmc/pvr/timers/PVRTimers.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xbmc/pvr/timers/PVRTimers.cpp b/xbmc/pvr/timers/PVRTimers.cpp index 53be1a926993a..91dd76481c621 100644 --- a/xbmc/pvr/timers/PVRTimers.cpp +++ b/xbmc/pvr/timers/PVRTimers.cpp @@ -551,6 +551,12 @@ bool CPVRTimers::UpdateEntries(int iMaxNotificationDelay) } } } + else if (!timer->IsTimerRule()) + { + // epg event no longer present. delete the timer + bDeleteTimer = true; + timer->DeleteFromDatabase(); + } } } From d9fd7760e74713503fc16c6aece4def0bc2bae83 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 29 Jun 2024 17:55:55 +0200 Subject: [PATCH 235/651] [PVR] Fix CPVRTimers::UpdateEntries: Delete existing epg-based local timers if the respective EPG tag was re-used for an unrelated event that no longer matches the related timer rule. --- xbmc/pvr/timers/PVRTimers.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/xbmc/pvr/timers/PVRTimers.cpp b/xbmc/pvr/timers/PVRTimers.cpp index 91dd76481c621..440f33e57f472 100644 --- a/xbmc/pvr/timers/PVRTimers.cpp +++ b/xbmc/pvr/timers/PVRTimers.cpp @@ -550,6 +550,23 @@ bool CPVRTimers::UpdateEntries(int iMaxNotificationDelay) timer->Persist(); } } + + // check for epg tag uids that were re-used for a different event (which is actually + // an add-on/a backend bug) + if (!timer->IsTimerRule() && (epgTag->Title() != timer->Title())) + { + const std::shared_ptr parent{GetTimerRule(timer)}; + if (parent) + { + const CPVRTimerRuleMatcher matcher{parent, now}; + if (!matcher.Matches(epgTag)) + { + // epg event no longer matches the rule. delete the timer + bDeleteTimer = true; + timer->DeleteFromDatabase(); + } + } + } } else if (!timer->IsTimerRule()) { From efc508d5d1d8927fde504345991f61b7bb3ee7b7 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 29 Jun 2024 21:04:03 +0200 Subject: [PATCH 236/651] [PVR] Fix CPVRTimers::UpdateEntries: Re-insert timers with changed start time before checking whether new children for local timer rules need to be created. The timers with changed start time need to be found during that check. This fixes creation of duplicate timers. --- xbmc/pvr/timers/PVRTimers.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/xbmc/pvr/timers/PVRTimers.cpp b/xbmc/pvr/timers/PVRTimers.cpp index 440f33e57f472..8339da75b7756 100644 --- a/xbmc/pvr/timers/PVRTimers.cpp +++ b/xbmc/pvr/timers/PVRTimers.cpp @@ -668,6 +668,13 @@ bool CPVRTimers::UpdateEntries(int iMaxNotificationDelay) ++it; } + // reinsert timers with changed timer start + for (const auto& timer : timersToReinsert) + { + InsertEntry(timer); + timer->Persist(); + } + // create new children of local epg-based reminder timer rules for (const auto& epgMapEntry : epgMap) { @@ -694,14 +701,7 @@ bool CPVRTimers::UpdateEntries(int iMaxNotificationDelay) } } - // reinsert timers with changed timer start - for (const auto& timer : timersToReinsert) - { - InsertEntry(timer); - timer->Persist(); - } - - // insert new children of time-based local timer rules + // persist and insert/update new children of local time-based and epg-based reminder timer rules for (const auto& timerPair : childTimersToInsert) { PersistAndUpdateLocalTimer(timerPair.second, timerPair.first); From afeb7e56f81c544422f665782c299f610ce5b68d Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Tue, 25 Jun 2024 07:14:40 +0200 Subject: [PATCH 237/651] changed: move CFileItem::GetTBNFile to ArtUtils --- xbmc/FileItem.cpp | 54 ++----------------------- xbmc/FileItem.h | 2 - xbmc/ThumbLoader.cpp | 5 ++- xbmc/utils/ArtUtils.cpp | 69 ++++++++++++++++++++++++++++++++ xbmc/utils/ArtUtils.h | 21 ++++++++++ xbmc/utils/CMakeLists.txt | 2 + xbmc/utils/test/CMakeLists.txt | 1 + xbmc/utils/test/TestArtUtils.cpp | 63 +++++++++++++++++++++++++++++ xbmc/video/VideoDatabase.cpp | 7 ++-- 9 files changed, 168 insertions(+), 56 deletions(-) create mode 100644 xbmc/utils/ArtUtils.cpp create mode 100644 xbmc/utils/ArtUtils.h create mode 100644 xbmc/utils/test/TestArtUtils.cpp diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 98360446a337b..5065b51c461ad 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -52,6 +52,7 @@ #include "settings/SettingsComponent.h" #include "settings/lib/Setting.h" #include "utils/Archive.h" +#include "utils/ArtUtils.h" #include "utils/FileExtensionProvider.h" #include "utils/Mime.h" #include "utils/RegExp.h" @@ -1926,7 +1927,7 @@ std::string CFileItem::GetUserMusicThumb(bool alwaysCheckRemote /* = false */, b return ""; // we first check for .tbn or .tbn - std::string fileThumb(GetTBNFile()); + std::string fileThumb(ART::GetTBNFile(*this)); if (CFile::Exists(fileThumb)) return fileThumb; @@ -1984,53 +1985,6 @@ std::string CFileItem::GetUserMusicThumb(bool alwaysCheckRemote /* = false */, b return ""; } -// Gets the .tbn filename from a file or folder name. -// .ext -> .tbn -// / -> .tbn -std::string CFileItem::GetTBNFile() const -{ - std::string thumbFile; - std::string strFile = m_strPath; - - if (IsStack()) - { - std::string strPath, strReturn; - URIUtils::GetParentPath(m_strPath,strPath); - CFileItem item(CStackDirectory::GetFirstStackedFile(strFile),false); - std::string strTBNFile = item.GetTBNFile(); - strReturn = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strTBNFile)); - if (CFile::Exists(strReturn)) - return strReturn; - - strFile = URIUtils::AddFileToFolder(strPath,URIUtils::GetFileName(CStackDirectory::GetStackedTitlePath(strFile))); - } - - if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) - { - std::string strPath = URIUtils::GetDirectory(strFile); - std::string strParent; - URIUtils::GetParentPath(strPath,strParent); - strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(m_strPath)); - } - - CURL url(strFile); - strFile = url.GetFileName(); - - if (m_bIsFolder && !IsFileFolder()) - URIUtils::RemoveSlashAtEnd(strFile); - - if (!strFile.empty()) - { - if (m_bIsFolder && !IsFileFolder()) - thumbFile = strFile + ".tbn"; // folder, so just add ".tbn" - else - thumbFile = URIUtils::ReplaceExtension(strFile, ".tbn"); - url.SetFileName(thumbFile); - thumbFile = url.Get(); - } - return thumbFile; -} - bool CFileItem::SkipLocalArt() const { return (m_strPath.empty() || StringUtils::StartsWithNoCase(m_strPath, "newsmartplaylist://") || @@ -2257,7 +2211,7 @@ std::string CFileItem::GetLocalFanart() const strPath2 = dir.GetStackedTitlePath(strFile); strFile = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strPath2)); CFileItem item(dir.GetFirstStackedFile(m_strPath),false); - std::string strTBNFile(URIUtils::ReplaceExtension(item.GetTBNFile(), "-fanart")); + std::string strTBNFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), "-fanart")); strFile2 = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strTBNFile)); } if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) @@ -2663,7 +2617,7 @@ std::string CFileItem::FindTrailer() const strPath2 = dir.GetStackedTitlePath(strFile); strFile = URIUtils::AddFileToFolder(strPath,URIUtils::GetFileName(strPath2)); CFileItem item(dir.GetFirstStackedFile(m_strPath),false); - std::string strTBNFile(URIUtils::ReplaceExtension(item.GetTBNFile(), "-trailer")); + std::string strTBNFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), "-trailer")); strFile2 = URIUtils::AddFileToFolder(strPath,URIUtils::GetFileName(strTBNFile)); } if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index 2023c2e43ef07..140d130f48868 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -443,8 +443,6 @@ class CFileItem : */ std::string GetThumbHideIfUnwatched(const CFileItem* item) const; - // Gets the .tbn file associated with this item - std::string GetTBNFile() const; // Gets the folder image associated with this item (defaults to folder.jpg) std::string GetFolderThumb(const std::string &folderJPG = "folder.jpg") const; // Gets the correct movie title diff --git a/xbmc/ThumbLoader.cpp b/xbmc/ThumbLoader.cpp index 3e1508cc49b7d..40db919d5290f 100644 --- a/xbmc/ThumbLoader.cpp +++ b/xbmc/ThumbLoader.cpp @@ -11,8 +11,11 @@ #include "FileItem.h" #include "ServiceBroker.h" #include "TextureCache.h" +#include "utils/ArtUtils.h" #include "utils/FileUtils.h" +using namespace KODI; + CThumbLoader::CThumbLoader() : CBackgroundInfoLoader() { @@ -117,7 +120,7 @@ std::string CProgramThumbLoader::GetLocalThumb(const CFileItem &item) } else { - std::string fileThumb(item.GetTBNFile()); + std::string fileThumb(ART::GetTBNFile(item)); if (CFileUtils::Exists(fileThumb)) return fileThumb; } diff --git a/xbmc/utils/ArtUtils.cpp b/xbmc/utils/ArtUtils.cpp new file mode 100644 index 0000000000000..46251a1480810 --- /dev/null +++ b/xbmc/utils/ArtUtils.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 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 "ArtUtils.h" + +#include "FileItem.h" +#include "filesystem/File.h" +#include "filesystem/StackDirectory.h" +#include "utils/URIUtils.h" + +using namespace XFILE; + +namespace KODI::ART +{ + +// Gets the .tbn filename from a file or folder name. +// .ext -> .tbn +// / -> .tbn +std::string GetTBNFile(const CFileItem& item) +{ + std::string thumbFile; + std::string strFile = item.GetPath(); + + if (item.IsStack()) + { + std::string strPath, strReturn; + URIUtils::GetParentPath(item.GetPath(), strPath); + CFileItem item(CStackDirectory::GetFirstStackedFile(strFile), false); + std::string strTBNFile = GetTBNFile(item); + strReturn = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strTBNFile)); + if (CFile::Exists(strReturn)) + return strReturn; + + strFile = URIUtils::AddFileToFolder( + strPath, URIUtils::GetFileName(CStackDirectory::GetStackedTitlePath(strFile))); + } + + if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) + { + std::string strPath = URIUtils::GetDirectory(strFile); + std::string strParent; + URIUtils::GetParentPath(strPath, strParent); + strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(item.GetPath())); + } + + CURL url(strFile); + strFile = url.GetFileName(); + + if (item.m_bIsFolder && !item.IsFileFolder()) + URIUtils::RemoveSlashAtEnd(strFile); + + if (!strFile.empty()) + { + if (item.m_bIsFolder && !item.IsFileFolder()) + thumbFile = strFile + ".tbn"; // folder, so just add ".tbn" + else + thumbFile = URIUtils::ReplaceExtension(strFile, ".tbn"); + url.SetFileName(thumbFile); + thumbFile = url.Get(); + } + return thumbFile; +} + +} // namespace KODI::ART diff --git a/xbmc/utils/ArtUtils.h b/xbmc/utils/ArtUtils.h new file mode 100644 index 0000000000000..b2ae688206e4b --- /dev/null +++ b/xbmc/utils/ArtUtils.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 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 + +class CFileItem; + +namespace KODI::ART +{ + +// Gets the .tbn file associated with an item +std::string GetTBNFile(const CFileItem& item); + +} // namespace KODI::ART diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index 13dbe058a9d8e..7379405cd5645 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES ActorProtocol.cpp AlarmClock.cpp AliasShortcutUtils.cpp Archive.cpp + ArtUtils.cpp Base64.cpp BitstreamConverter.cpp BitstreamReader.cpp @@ -82,6 +83,7 @@ set(HEADERS ActorProtocol.h AlarmClock.h AliasShortcutUtils.h Archive.h + ArtUtils.h Base64.h BitstreamConverter.h BitstreamReader.h diff --git a/xbmc/utils/test/CMakeLists.txt b/xbmc/utils/test/CMakeLists.txt index 3cdc5d64481de..99bd4faaf736d 100644 --- a/xbmc/utils/test/CMakeLists.txt +++ b/xbmc/utils/test/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES TestAlarmClock.cpp TestAliasShortcutUtils.cpp TestArchive.cpp + TestArtUtils.cpp TestBase64.cpp TestBitstreamStats.cpp TestCharsetConverter.cpp diff --git a/xbmc/utils/test/TestArtUtils.cpp b/xbmc/utils/test/TestArtUtils.cpp new file mode 100644 index 0000000000000..92770435ecdf8 --- /dev/null +++ b/xbmc/utils/test/TestArtUtils.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 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 "FileItem.h" +#include "platform/Filesystem.h" +#include "utils/ArtUtils.h" +#include "utils/FileUtils.h" +#include "utils/URIUtils.h" + +#include +#include + +#include +#include + +using namespace KODI; + +struct TbnTest +{ + std::string path; + std::string result; + bool isFolder = false; +}; + +class GetTbnTest : public testing::WithParamInterface, public testing::Test +{ +}; + +TEST_P(GetTbnTest, TbnTest) +{ + EXPECT_EQ(ART::GetTBNFile(CFileItem(GetParam().path, GetParam().isFolder)), GetParam().result); +} + +const auto tbn_tests = std::array{ + TbnTest{"/home/user/video.avi", "/home/user/video.tbn"}, + TbnTest{"/home/user/video/", "/home/user/video.tbn", true}, + TbnTest{"/home/user/bar.xbt", "/home/user/bar.tbn", true}, + TbnTest{"zip://%2fhome%2fuser%2fbar.zip/foo.avi", "/home/user/foo.tbn"}, + TbnTest{"stack:///home/user/foo-cd1.avi , /home/user/foo-cd2.avi", "/home/user/foo.tbn"}}; + +INSTANTIATE_TEST_SUITE_P(TestArtUtils, GetTbnTest, testing::ValuesIn(tbn_tests)); + +TEST(TestArtUtils, GetTbnStack) +{ + std::error_code ec; + auto path = KODI::PLATFORM::FILESYSTEM::temp_directory_path(ec); + ASSERT_TRUE(!ec); + const auto file_path = URIUtils::AddFileToFolder(path, "foo-cd1.tbn"); + { + std::ofstream of(file_path, std::ios::out); + } + const std::string stackPath = + fmt::format("stack://{} , {}", URIUtils::AddFileToFolder(path, "foo-cd1.avi"), + URIUtils::AddFileToFolder(path, "foo-cd2.avi")); + CFileItem item(stackPath, false); + EXPECT_EQ(ART::GetTBNFile(item), file_path); + CFileUtils::DeleteItem(file_path); +} diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 1e9cf8993f710..686dbbbe3de38 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -44,6 +44,7 @@ #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "storage/MediaManager.h" +#include "utils/ArtUtils.h" #include "utils/FileUtils.h" #include "utils/GroupUtils.h" #include "utils/LabelFormatter.h" @@ -10710,7 +10711,7 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t } else { - std::string nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo")); + std::string nfoFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), ".nfo")); if (item.IsOpticalMediaFile()) { @@ -10863,7 +10864,7 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t } else { - std::string nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo")); + std::string nfoFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), ".nfo")); if (overwrite || !CFile::Exists(nfoFile, false)) { @@ -11064,7 +11065,7 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t } else { - std::string nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo")); + std::string nfoFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), ".nfo")); if (overwrite || !CFile::Exists(nfoFile, false)) { From d3f3373dc9b430dc3e81c7caeed42c5c344b1731 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Sun, 30 Jun 2024 15:55:53 +0200 Subject: [PATCH 238/651] drop str prefix for string variables --- xbmc/utils/ArtUtils.cpp | 42 ++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/xbmc/utils/ArtUtils.cpp b/xbmc/utils/ArtUtils.cpp index 46251a1480810..c76192956e1a2 100644 --- a/xbmc/utils/ArtUtils.cpp +++ b/xbmc/utils/ArtUtils.cpp @@ -24,42 +24,42 @@ namespace KODI::ART std::string GetTBNFile(const CFileItem& item) { std::string thumbFile; - std::string strFile = item.GetPath(); + std::string file = item.GetPath(); if (item.IsStack()) { - std::string strPath, strReturn; - URIUtils::GetParentPath(item.GetPath(), strPath); - CFileItem item(CStackDirectory::GetFirstStackedFile(strFile), false); - std::string strTBNFile = GetTBNFile(item); - strReturn = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strTBNFile)); - if (CFile::Exists(strReturn)) - return strReturn; + std::string path, returnPath; + URIUtils::GetParentPath(item.GetPath(), path); + CFileItem item(CStackDirectory::GetFirstStackedFile(file), false); + const std::string TBNFile = GetTBNFile(item); + returnPath = URIUtils::AddFileToFolder(path, URIUtils::GetFileName(TBNFile)); + if (CFile::Exists(returnPath)) + return returnPath; - strFile = URIUtils::AddFileToFolder( - strPath, URIUtils::GetFileName(CStackDirectory::GetStackedTitlePath(strFile))); + const std::string& stackPath = CStackDirectory::GetStackedTitlePath(file); + file = URIUtils::AddFileToFolder(path, URIUtils::GetFileName(stackPath)); } - if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) + if (URIUtils::IsInRAR(file) || URIUtils::IsInZIP(file)) { - std::string strPath = URIUtils::GetDirectory(strFile); - std::string strParent; - URIUtils::GetParentPath(strPath, strParent); - strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(item.GetPath())); + const std::string path = URIUtils::GetDirectory(file); + std::string parent; + URIUtils::GetParentPath(path, parent); + file = URIUtils::AddFileToFolder(parent, URIUtils::GetFileName(item.GetPath())); } - CURL url(strFile); - strFile = url.GetFileName(); + CURL url(file); + file = url.GetFileName(); if (item.m_bIsFolder && !item.IsFileFolder()) - URIUtils::RemoveSlashAtEnd(strFile); + URIUtils::RemoveSlashAtEnd(file); - if (!strFile.empty()) + if (!file.empty()) { if (item.m_bIsFolder && !item.IsFileFolder()) - thumbFile = strFile + ".tbn"; // folder, so just add ".tbn" + thumbFile = file + ".tbn"; // folder, so just add ".tbn" else - thumbFile = URIUtils::ReplaceExtension(strFile, ".tbn"); + thumbFile = URIUtils::ReplaceExtension(file, ".tbn"); url.SetFileName(thumbFile); thumbFile = url.Get(); } From faac78cc53b00dcd9c888318fbdacbbfa665b9e6 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 28 Jun 2024 23:42:17 +0200 Subject: [PATCH 239/651] [PVR] CPVRChannelGroupsContainer: Replace raw pointers with std::shared_ptr. --- xbmc/interfaces/json-rpc/PVROperations.cpp | 5 +++-- xbmc/pvr/PVRPlaybackState.cpp | 6 +++--- xbmc/pvr/channels/PVRChannelGroups.cpp | 2 +- xbmc/pvr/channels/PVRChannelGroups.h | 2 +- xbmc/pvr/channels/PVRChannelGroupsContainer.cpp | 4 +--- xbmc/pvr/channels/PVRChannelGroupsContainer.h | 10 +++++----- xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp | 7 ++++--- xbmc/pvr/dialogs/GUIDialogPVRChannelsOSD.cpp | 4 ++-- xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp | 10 ++++++---- xbmc/pvr/filesystem/PVRGUIDirectory.cpp | 6 +++--- xbmc/pvr/guilib/PVRGUIActionsChannels.cpp | 4 ++-- xbmc/pvr/windows/GUIWindowPVRBase.cpp | 6 ++++-- 12 files changed, 35 insertions(+), 31 deletions(-) diff --git a/xbmc/interfaces/json-rpc/PVROperations.cpp b/xbmc/interfaces/json-rpc/PVROperations.cpp index 07c8d93222fd0..597d8b850deb3 100644 --- a/xbmc/interfaces/json-rpc/PVROperations.cpp +++ b/xbmc/interfaces/json-rpc/PVROperations.cpp @@ -65,8 +65,9 @@ JSONRPC_STATUS CPVROperations::GetChannelGroups(const std::string &method, ITran if (!channelGroupContainer) return FailedToExecute; - CPVRChannelGroups *channelGroups = channelGroupContainer->Get(parameterObject["channeltype"].asString().compare("radio") == 0); - if (channelGroups == NULL) + const std::shared_ptr channelGroups{ + channelGroupContainer->Get(parameterObject["channeltype"].asString().compare("radio") == 0)}; + if (!channelGroups) return FailedToExecute; int start, end; diff --git a/xbmc/pvr/PVRPlaybackState.cpp b/xbmc/pvr/PVRPlaybackState.cpp index e29887ddcefca..4879a119a64d4 100644 --- a/xbmc/pvr/PVRPlaybackState.cpp +++ b/xbmc/pvr/PVRPlaybackState.cpp @@ -97,8 +97,8 @@ void CPVRPlaybackState::ReInit() const std::shared_ptr groups = CServiceBroker::GetPVRManager().ChannelGroups(); - const CPVRChannelGroups* groupsTV = groups->GetTV(); - const CPVRChannelGroups* groupsRadio = groups->GetRadio(); + const std::shared_ptr groupsTV{groups->GetTV()}; + const std::shared_ptr groupsRadio{groups->GetRadio()}; m_activeGroupTV = groupsTV->GetLastOpenedGroup(); m_activeGroupRadio = groupsRadio->GetLastOpenedGroup(); @@ -555,7 +555,7 @@ namespace std::shared_ptr GetFirstNonDeletedAndNonHiddenChannelGroup( const std::shared_ptr& groupMember) { - CPVRChannelGroups* groups{ + const std::shared_ptr groups{ CServiceBroker::GetPVRManager().ChannelGroups()->Get(groupMember->IsRadio())}; if (groups) { diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp index 79a5283eb9749..6a31cad6672b0 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.cpp +++ b/xbmc/pvr/channels/PVRChannelGroups.cpp @@ -239,7 +239,7 @@ std::shared_ptr CPVRChannelGroups::GetChannelGroupMember } std::vector> CPVRChannelGroups::GetMembersAvailableForGroup( - const std::shared_ptr& group) + const std::shared_ptr& group) const { std::vector> result; diff --git a/xbmc/pvr/channels/PVRChannelGroups.h b/xbmc/pvr/channels/PVRChannelGroups.h index a3fc1fd754ea7..8df9c5c525936 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.h +++ b/xbmc/pvr/channels/PVRChannelGroups.h @@ -97,7 +97,7 @@ class CPVRChannelGroups : public ISettingCallback * @return The channel group members that could be added to the group */ std::vector> GetMembersAvailableForGroup( - const std::shared_ptr& group); + const std::shared_ptr& group) const; /*! * @brief Get a pointer to a channel group given its ID. diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp index 04ff915c33a6c..13f53da56e26e 100644 --- a/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp +++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp @@ -27,8 +27,6 @@ CPVRChannelGroupsContainer::CPVRChannelGroupsContainer() CPVRChannelGroupsContainer::~CPVRChannelGroupsContainer() { Unload(); - delete m_groupsRadio; - delete m_groupsTV; } bool CPVRChannelGroupsContainer::Update(const std::vector>& clients) @@ -68,7 +66,7 @@ void CPVRChannelGroupsContainer::Unload() m_groupsTV->Unload(); } -CPVRChannelGroups* CPVRChannelGroupsContainer::Get(bool bRadio) const +std::shared_ptr CPVRChannelGroupsContainer::Get(bool bRadio) const { return bRadio ? m_groupsRadio : m_groupsTV; } diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.h b/xbmc/pvr/channels/PVRChannelGroupsContainer.h index 8fb71c8e355a6..f0185f5331971 100644 --- a/xbmc/pvr/channels/PVRChannelGroupsContainer.h +++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.h @@ -60,20 +60,20 @@ class CPVRChannelGroupsContainer * @brief Get the TV channel groups. * @return The TV channel groups. */ - CPVRChannelGroups* GetTV() const { return Get(false); } + std::shared_ptr GetTV() const { return Get(false); } /*! * @brief Get the radio channel groups. * @return The radio channel groups. */ - CPVRChannelGroups* GetRadio() const { return Get(true); } + std::shared_ptr GetRadio() const { return Get(true); } /*! * @brief Get the radio or TV channel groups. * @param bRadio If true, get the radio channel groups. Get the TV channel groups otherwise. * @return The requested groups. */ - CPVRChannelGroups* Get(bool bRadio) const; + std::shared_ptr Get(bool bRadio) const; /*! * @brief Get the group containing all TV channels. @@ -169,8 +169,8 @@ class CPVRChannelGroupsContainer */ bool LoadFromDatabase(const std::vector>& clients); - CPVRChannelGroups* m_groupsRadio; /*!< all radio channel groups */ - CPVRChannelGroups* m_groupsTV; /*!< all TV channel groups */ + std::shared_ptr m_groupsRadio; /*!< all radio channel groups */ + std::shared_ptr m_groupsTV; /*!< all TV channel groups */ CCriticalSection m_critSection; bool m_bIsUpdating = false; }; diff --git a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp index d6ed495ab1669..ad9ae3e50a25e 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp @@ -771,8 +771,8 @@ bool CGUIDialogPVRChannelManager::OnContextButton(int itemNumber, CONTEXT_BUTTON PVR_ERROR ret = client->DeleteChannel(channel); if (ret == PVR_ERROR_NO_ERROR) { - CPVRChannelGroups* groups = - CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio); + const std::shared_ptr groups{ + CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)}; if (groups) { groups->UpdateFromClients({}); @@ -1081,7 +1081,8 @@ void CGUIDialogPVRChannelManager::SaveList() group->SortAndRenumber(); - auto channelGroups = CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio); + const std::shared_ptr channelGroups{ + CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)}; channelGroups->UpdateChannelNumbersFromAllChannelsGroup(); channelGroups->PersistAll(); pDlgProgress->Close(); diff --git a/xbmc/pvr/dialogs/GUIDialogPVRChannelsOSD.cpp b/xbmc/pvr/dialogs/GUIDialogPVRChannelsOSD.cpp index c349bd0623d2e..36ed331683f7a 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRChannelsOSD.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRChannelsOSD.cpp @@ -133,8 +133,8 @@ bool CGUIDialogPVRChannelsOSD::OnAction(const CAction& action) SaveControlStates(); // switch to next or previous group - const CPVRChannelGroups* groups = - CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_group->IsRadio()); + const std::shared_ptr groups{ + CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_group->IsRadio())}; const std::shared_ptr nextGroup = action.GetID() == ACTION_NEXT_CHANNELGROUP ? groups->GetNextGroup(*m_group) : groups->GetPreviousGroup(*m_group); diff --git a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp index 17c788aea847c..32bc313d67a12 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp @@ -161,7 +161,8 @@ bool CGUIDialogPVRGroupManager::ActionButtonNewGroup(const CGUIMessage& message) if (!strGroupName.empty()) { // add the group if it doesn't already exist - CPVRChannelGroups* groups{CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)}; + const std::shared_ptr groups{ + CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)}; const auto group = groups->AddGroup(strGroupName); if (group) { @@ -653,7 +654,8 @@ void CGUIDialogPVRGroupManager::Update() m_groupMembers->Add(std::make_shared(groupMember)); } - CPVRChannelGroups* groups{CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)}; + const std::shared_ptr groups{ + CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)}; const auto availableMembers = groups->GetMembersAvailableForGroup(m_selectedGroup); for (const auto& groupMember : availableMembers) @@ -685,8 +687,8 @@ void CGUIDialogPVRGroupManager::Clear() void CGUIDialogPVRGroupManager::ClearGroupThumbnails(const CFileItem& changedItem) { - const CPVRChannelGroups* groups{CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)}; - + const std::shared_ptr groups{ + CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)}; const std::shared_ptr changedMember{ changedItem.GetPVRChannelGroupMemberInfoTag()}; if (changedMember) diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp index 5bfa415dee8f7..63341bd70795c 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp @@ -439,8 +439,8 @@ bool CPVRGUIDirectory::GetChannelGroupsDirectory(bool bRadio, bool bExcludeHidden, CFileItemList& results) { - const CPVRChannelGroups* channelGroups = - CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio); + const std::shared_ptr channelGroups{ + CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio)}; if (channelGroups) { std::shared_ptr item; @@ -477,7 +477,7 @@ std::shared_ptr GetLastWatchedChannelGroupMember( std::shared_ptr GetFirstMatchingGroupMember( const std::shared_ptr& channel) { - CPVRChannelGroups* groups{ + const std::shared_ptr groups{ CServiceBroker::GetPVRManager().ChannelGroups()->Get(channel->IsRadio())}; if (groups) { diff --git a/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp index f13d331318713..033ab0102ba90 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp @@ -125,8 +125,8 @@ void CPVRChannelSwitchingInputHandler::SwitchToChannel(const CPVRChannelNumber& if (!groupMember) { // channel number present in any group? - const CPVRChannelGroups* groupAccess = - CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio); + const std::shared_ptr groupAccess{ + CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio)}; const std::vector> groups = groupAccess->GetMembers(true); for (const auto& currentGroup : groups) diff --git a/xbmc/pvr/windows/GUIWindowPVRBase.cpp b/xbmc/pvr/windows/GUIWindowPVRBase.cpp index c6ef99abcedb8..2af8531134cc6 100644 --- a/xbmc/pvr/windows/GUIWindowPVRBase.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRBase.cpp @@ -218,7 +218,8 @@ bool CGUIWindowPVRBase::ActivatePreviousChannelGroup() const std::shared_ptr channelGroup = GetChannelGroup(); if (channelGroup) { - const CPVRChannelGroups* groups = CServiceBroker::GetPVRManager().ChannelGroups()->Get(channelGroup->IsRadio()); + const std::shared_ptr groups{ + CServiceBroker::GetPVRManager().ChannelGroups()->Get(channelGroup->IsRadio())}; if (groups) { SetChannelGroup(groups->GetPreviousGroup(*channelGroup)); @@ -233,7 +234,8 @@ bool CGUIWindowPVRBase::ActivateNextChannelGroup() const std::shared_ptr channelGroup = GetChannelGroup(); if (channelGroup) { - const CPVRChannelGroups* groups = CServiceBroker::GetPVRManager().ChannelGroups()->Get(channelGroup->IsRadio()); + const std::shared_ptr groups{ + CServiceBroker::GetPVRManager().ChannelGroups()->Get(channelGroup->IsRadio())}; if (groups) { SetChannelGroup(groups->GetNextGroup(*channelGroup)); From f2171d9ecfb356591b4b4dae727bc0361114706f Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Sun, 30 Jun 2024 23:47:42 +0200 Subject: [PATCH 240/651] Remove unused variables in makefile --- tools/android/packaging/Makefile.in | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/android/packaging/Makefile.in b/tools/android/packaging/Makefile.in index ca3c5c784a468..d0e9d3cc7aca3 100644 --- a/tools/android/packaging/Makefile.in +++ b/tools/android/packaging/Makefile.in @@ -9,7 +9,6 @@ EXCLUDED_ADDONS = CMAKE_SOURCE_DIR = $(shell cd $(CURDIR)/../../..; pwd) APP_PACKAGE_DIR = $(subst .,/,@APP_PACKAGE@) -COPYDIRS = system addons media ifeq ($(KODI_ANDROID_STORE_FILE),) export KODI_ANDROID_STORE_FILE:=$(HOME)/.android/debug.keystore @@ -31,8 +30,6 @@ endif STLLIB=$(TOOLCHAIN)/sysroot/usr/lib/$(HOST)/libc++_shared.so SRCLIBS = $(addprefix $(PREFIX)/lib/,$(OBJS)) -DSTLIBS = $(CPU)/lib/lib@APP_NAME_LC@.so $(addprefix $(CPU)/lib/,$(OBJS)) -libs= $(DSTLIBS) all: apk From 907181bbdf74b186d41a7d3ac62dcd2f7bac8a6d Mon Sep 17 00:00:00 2001 From: Rechi Date: Mon, 1 Jul 2024 10:44:24 +1000 Subject: [PATCH 241/651] [clang-tidy] modernize-make-shared --- xbmc/pvr/channels/PVRChannelGroups.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp index 79a5283eb9749..65055c7e5b77d 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.cpp +++ b/xbmc/pvr/channels/PVRChannelGroups.cpp @@ -79,7 +79,7 @@ void CPVRChannelGroups::Unload() m_groups.clear(); m_allChannelsGroup.reset(); - m_channelGroupFactory.reset(new CPVRChannelGroupFactory); + m_channelGroupFactory = std::make_shared(); m_failedClientsForChannelGroups.clear(); } From a0d63a5af10b06b04490c86bfc25baf186776303 Mon Sep 17 00:00:00 2001 From: Rechi Date: Mon, 1 Jul 2024 10:44:24 +1000 Subject: [PATCH 242/651] [clang-tidy] performance-unnecessary-copy-initialization --- xbmc/pvr/channels/PVRChannelGroups.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp index 65055c7e5b77d..c6539b75332af 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.cpp +++ b/xbmc/pvr/channels/PVRChannelGroups.cpp @@ -468,7 +468,7 @@ std::shared_ptr CPVRChannelGroups::GetLastGroup() const std::unique_lock lock(m_critSection); for (auto it = m_groups.crbegin(); it != m_groups.crend(); ++it) { - const auto group{*it}; + const auto& group{*it}; if (!group->ShouldBeIgnored(m_groups)) return group; } @@ -548,7 +548,7 @@ std::shared_ptr CPVRChannelGroups::GetPreviousGroup( std::unique_lock lock(m_critSection); for (auto it = m_groups.crbegin(); it != m_groups.crend(); ++it) { - const auto currentGroup{*it}; + const auto& currentGroup{*it}; // return this entry if (bReturnNext && !currentGroup->IsHidden() && !currentGroup->ShouldBeIgnored(m_groups)) @@ -562,7 +562,7 @@ std::shared_ptr CPVRChannelGroups::GetPreviousGroup( // no match return last visible group for (auto it = m_groups.crbegin(); it != m_groups.crend(); ++it) { - const auto currentGroup{*it}; + const auto& currentGroup{*it}; if (!currentGroup->IsHidden() && !currentGroup->ShouldBeIgnored(m_groups)) return currentGroup; } @@ -581,7 +581,7 @@ std::shared_ptr CPVRChannelGroups::GetNextGroup( std::unique_lock lock(m_critSection); for (auto it = m_groups.cbegin(); it != m_groups.cend(); ++it) { - const auto currentGroup{*it}; + const auto& currentGroup{*it}; // return this entry if (bReturnNext && !currentGroup->IsHidden() && !currentGroup->ShouldBeIgnored(m_groups)) @@ -595,7 +595,7 @@ std::shared_ptr CPVRChannelGroups::GetNextGroup( // no match return first visible group for (auto it = m_groups.cbegin(); it != m_groups.cend(); ++it) { - const auto currentGroup{*it}; + const auto& currentGroup{*it}; if (!currentGroup->IsHidden() && !currentGroup->ShouldBeIgnored(m_groups)) return currentGroup; } From bb79a463cd36aabd82c433aed29862b9cf5decc0 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 23 Jun 2024 13:37:14 +1000 Subject: [PATCH 243/651] [cmake][modules] FindPCRE2 minor build option updates use PIC option when building, properly disable JIT for windows/darwinembedded, add newline option we have used previously in pcre1 --- cmake/modules/FindPCRE2.cmake | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/cmake/modules/FindPCRE2.cmake b/cmake/modules/FindPCRE2.cmake index 7e50d5f2c1e86..1fc4f6c302d63 100644 --- a/cmake/modules/FindPCRE2.cmake +++ b/cmake/modules/FindPCRE2.cmake @@ -12,7 +12,7 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) macro(buildPCRE2) set(PCRE2_VERSION ${${MODULE}_VER}) if(WIN32) - set(PCRE_DEBUG_POSTFIX d) + set(PCRE2_DEBUG_POSTFIX d) endif() set(patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-all-enable_docs_pc.patch" @@ -20,19 +20,23 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) generate_patchcommand("${patches}") + if(CORE_SYSTEM_NAME STREQUAL darwin_embedded OR WINDOWS_STORE) + set(EXTRA_ARGS -DPCRE2_SUPPORT_JIT=OFF) + else() + set(EXTRA_ARGS -DPCRE2_SUPPORT_JIT=ON) + endif() + set(CMAKE_ARGS -DBUILD_STATIC_LIBS=ON + -DPCRE2_STATIC_PIC=ON -DPCRE2_BUILD_PCRE2_8=ON -DPCRE2_BUILD_PCRE2_16=OFF -DPCRE2_BUILD_PCRE2_32=OFF - -DPCRE2_SUPPORT_JIT=ON + -DPCRE_NEWLINE=ANYCRLF -DPCRE2_SUPPORT_UNICODE=ON -DPCRE2_BUILD_PCRE2GREP=OFF -DPCRE2_BUILD_TESTS=OFF - -DENABLE_DOCS=OFF) - - if(CORE_SYSTEM_NAME STREQUAL darwin_embedded) - list(APPEND CMAKE_ARGS -DPCRE2_SUPPORT_JIT=OFF) - endif() + -DENABLE_DOCS=OFF + ${EXTRA_ARGS}) set(${CMAKE_FIND_PACKAGE_NAME}_COMPILEDEFINITIONS PCRE2_STATIC) From dd5719c41a69ea970bf52e3d79ad49fcee015728 Mon Sep 17 00:00:00 2001 From: Kolja Lampe Date: Tue, 2 Jul 2024 09:25:18 +0200 Subject: [PATCH 244/651] Don't change fileending to png --- cmake/scripts/linux/Install.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index 5b4216badb30c..8c97f9074bf22 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -135,7 +135,7 @@ install(FILES ${CMAKE_SOURCE_DIR}/tools/Linux/packaging/media/icon256x256.png DESTINATION ${datarootdir}/icons/hicolor/256x256/apps COMPONENT kodi) install(FILES ${CMAKE_SOURCE_DIR}/tools/Linux/packaging/media/iconScalable.svg - RENAME ${APP_NAME_LC}.png + RENAME ${APP_NAME_LC}.svg DESTINATION ${datarootdir}/icons/hicolor/scalable/apps COMPONENT kodi) From 7e9608909d74d8a386c7a26f8e36c0ec6b23027f Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 2 Jul 2024 11:14:20 +0100 Subject: [PATCH 245/651] [GUI][X11] Fix deadlock on dialog renderloop --- xbmc/guilib/GUIWindowManager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xbmc/guilib/GUIWindowManager.cpp b/xbmc/guilib/GUIWindowManager.cpp index 993a34c7c91cc..fac8742681cb2 100644 --- a/xbmc/guilib/GUIWindowManager.cpp +++ b/xbmc/guilib/GUIWindowManager.cpp @@ -1475,7 +1475,10 @@ bool CGUIWindowManager::ProcessRenderLoop(bool renderOnly) m_iNested++; if (!renderOnly) m_pCallback->Process(); - m_pCallback->FrameMove(!renderOnly); + { + CSingleExit leaveIt(CServiceBroker::GetWinSystem()->GetGfxContext()); + m_pCallback->FrameMove(!renderOnly); + } m_pCallback->Render(); m_iNested--; } From 8ade54d7ea0ed055be79bd043f995f14354a2b1d Mon Sep 17 00:00:00 2001 From: Wolfgang Haupt Date: Tue, 2 Jul 2024 13:53:15 +0200 Subject: [PATCH 246/651] VP: Allow subsecond queue sizes Signed-off-by: Wolfgang Haupt --- xbmc/cores/VideoPlayer/DVDMessageQueue.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/cores/VideoPlayer/DVDMessageQueue.h b/xbmc/cores/VideoPlayer/DVDMessageQueue.h index 0e745512ac3c7..52dc194a63f18 100644 --- a/xbmc/cores/VideoPlayer/DVDMessageQueue.h +++ b/xbmc/cores/VideoPlayer/DVDMessageQueue.h @@ -84,7 +84,7 @@ class CDVDMessageQueue int GetLevel() const; void SetMaxDataSize(int iMaxDataSize) { m_iMaxDataSize = iMaxDataSize; } - void SetMaxTimeSize(double sec) { m_TimeSize = 1.0 / std::max(1.0, sec); } + void SetMaxTimeSize(double sec) { m_TimeSize = 1.0 / sec; } int GetMaxDataSize() const { return m_iMaxDataSize; } double GetMaxTimeSize() const { return m_TimeSize; } bool IsInited() const { return m_bInitialized; } From 0a730dd0b30aab5d7990e55e0b08d5ab4c407b4b Mon Sep 17 00:00:00 2001 From: sarbes Date: Tue, 2 Jul 2024 15:01:45 +0200 Subject: [PATCH 247/651] Upload XBT textures with right dimensions --- xbmc/guilib/Texture.cpp | 16 +++++++++++++++- xbmc/guilib/TextureBase.cpp | 14 -------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/xbmc/guilib/Texture.cpp b/xbmc/guilib/Texture.cpp index 91b9db90d330d..31a6029636108 100644 --- a/xbmc/guilib/Texture.cpp +++ b/xbmc/guilib/Texture.cpp @@ -246,7 +246,21 @@ bool CTexture::LoadIImage(IImage* pImage, if (pImage->Width() == 0 || pImage->Height() == 0) return false; - Allocate(pImage->Width(), pImage->Height(), XB_FMT_A8R8G8B8); + // align all textures so that they have an even width + // in some circumstances when we downsize a thumbnail + // which has an uneven number of pixels in width + // we crash in CPicture::ScaleImage in ffmpegs swscale + // because it tries to access beyond the source memory + // (happens on osx and ios) + // UPDATE: don't just update to be on an even width; + // ffmpegs swscale relies on a 16-byte stride on some systems + // so the textureWidth needs to be a multiple of 16. see ffmpeg + // swscale headers for more info. + unsigned int textureWidth = ((pImage->Width() + 15) / 16) * 16; + + Allocate(textureWidth, pImage->Height(), XB_FMT_A8R8G8B8); + + m_imageWidth = std::min(m_imageWidth, textureWidth); if (m_pixels == nullptr) return false; diff --git a/xbmc/guilib/TextureBase.cpp b/xbmc/guilib/TextureBase.cpp index 5c2c01e22b359..b0c3ce11b08a5 100644 --- a/xbmc/guilib/TextureBase.cpp +++ b/xbmc/guilib/TextureBase.cpp @@ -45,20 +45,6 @@ void CTextureBase::Allocate(uint32_t width, uint32_t height, XB_FMT format) m_textureWidth = ((m_textureWidth + 3) / 4) * 4; m_textureHeight = ((m_textureHeight + 3) / 4) * 4; } - else - { - // align all textures so that they have an even width - // in some circumstances when we downsize a thumbnail - // which has an uneven number of pixels in width - // we crash in CPicture::ScaleImage in ffmpegs swscale - // because it tries to access beyond the source memory - // (happens on osx and ios) - // UPDATE: don't just update to be on an even width; - // ffmpegs swscale relies on a 16-byte stride on some systems - // so the textureWidth needs to be a multiple of 16. see ffmpeg - // swscale headers for more info. - m_textureWidth = ((m_textureWidth + 15) / 16) * 16; - } // check for max texture size m_textureWidth = std::min(m_textureWidth, CServiceBroker::GetRenderSystem()->GetMaxTextureSize()); From 5131f13a052af44977246467da4aecdcd3b46a50 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Wed, 3 Jul 2024 16:23:59 -0700 Subject: [PATCH 248/651] RetroPlayer: Move FlatBuffer types to sub-namespace --- xbmc/cores/RetroPlayer/messages/savestate.fbs | 2 +- xbmc/cores/RetroPlayer/messages/video.fbs | 2 +- .../savestates/SavestateFlatBuffer.cpp | 80 +++++++++---------- .../savestates/SavestateFlatBuffer.h | 6 +- 4 files changed, 46 insertions(+), 44 deletions(-) diff --git a/xbmc/cores/RetroPlayer/messages/savestate.fbs b/xbmc/cores/RetroPlayer/messages/savestate.fbs index 7f8d8056e948a..325ab1c383a91 100644 --- a/xbmc/cores/RetroPlayer/messages/savestate.fbs +++ b/xbmc/cores/RetroPlayer/messages/savestate.fbs @@ -8,7 +8,7 @@ include "video.fbs"; -namespace KODI.RETRO; +namespace KODI.RETRO.SAVESTATE; // Savestate schema // Version 3 diff --git a/xbmc/cores/RetroPlayer/messages/video.fbs b/xbmc/cores/RetroPlayer/messages/video.fbs index f83ba5fca2066..91bb5f10da69e 100644 --- a/xbmc/cores/RetroPlayer/messages/video.fbs +++ b/xbmc/cores/RetroPlayer/messages/video.fbs @@ -6,7 +6,7 @@ // See LICENSES/README.md for more information. // -namespace KODI.RETRO; +namespace KODI.RETRO.SAVESTATE; enum PixelFormat : uint8 { /// @brief Value for unknown pixel formats diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp index e9f19ca30a9c2..7a4257b675e2a 100644 --- a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp +++ b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp @@ -34,31 +34,31 @@ const size_t INITIAL_FLATBUFFER_SIZE = 1024; /*! * \brief Translate the save type (RetroPlayer to FlatBuffers) */ -SaveType TranslateType(SAVE_TYPE type) +SAVESTATE::SaveType TranslateType(SAVE_TYPE type) { switch (type) { case SAVE_TYPE::AUTO: - return SaveType_Auto; + return SAVESTATE::SaveType_Auto; case SAVE_TYPE::MANUAL: - return SaveType_Manual; + return SAVESTATE::SaveType_Manual; default: break; } - return SaveType_Unknown; + return SAVESTATE::SaveType_Unknown; } /*! * \brief Translate the save type (FlatBuffers to RetroPlayer) */ -SAVE_TYPE TranslateType(SaveType type) +SAVE_TYPE TranslateType(SAVESTATE::SaveType type) { switch (type) { - case SaveType_Auto: + case SAVESTATE::SaveType_Auto: return SAVE_TYPE::AUTO; - case SaveType_Manual: + case SAVESTATE::SaveType_Manual: return SAVE_TYPE::MANUAL; default: break; @@ -70,67 +70,67 @@ SAVE_TYPE TranslateType(SaveType type) /*! * \brief Translate the video pixel format (RetroPlayer to FlatBuffers) */ -PixelFormat TranslatePixelFormat(AVPixelFormat pixelFormat) +SAVESTATE::PixelFormat TranslatePixelFormat(AVPixelFormat pixelFormat) { switch (pixelFormat) { case AV_PIX_FMT_RGBA: - return PixelFormat_RGBA_8888; + return SAVESTATE::PixelFormat_RGBA_8888; case AV_PIX_FMT_0RGB32: #if defined(__BIG_ENDIAN__) - return PixelFormat_XRGB_8888; + return SAVESTATE::PixelFormat_XRGB_8888; #else - return PixelFormat_BGRX_8888; + return SAVESTATE::PixelFormat_BGRX_8888; #endif case AV_PIX_FMT_RGB565: #if defined(__BIG_ENDIAN__) - return PixelFormat_RGB_565_BE; + return SAVESTATE::PixelFormat_RGB_565_BE; #else - return PixelFormat_RGB_565_LE; + return SAVESTATE::PixelFormat_RGB_565_LE; #endif case AV_PIX_FMT_RGB555: #if defined(__BIG_ENDIAN__) - return PixelFormat_RGB_555_BE; + return SAVESTATE::PixelFormat_RGB_555_BE; #else - return PixelFormat_RGB_555_LE; + return SAVESTATE::PixelFormat_RGB_555_LE; #endif default: break; } - return PixelFormat_Unknown; + return SAVESTATE::PixelFormat_Unknown; } /*! * \brief Translate the video pixel format (FlatBuffers to RetroPlayer) */ -AVPixelFormat TranslatePixelFormat(PixelFormat pixelFormat) +AVPixelFormat TranslatePixelFormat(SAVESTATE::PixelFormat pixelFormat) { switch (pixelFormat) { - case PixelFormat_RGBA_8888: + case SAVESTATE::PixelFormat_RGBA_8888: return AV_PIX_FMT_RGBA; - case PixelFormat_XRGB_8888: + case SAVESTATE::PixelFormat_XRGB_8888: return AV_PIX_FMT_0RGB; - case PixelFormat_BGRX_8888: + case SAVESTATE::PixelFormat_BGRX_8888: return AV_PIX_FMT_BGR0; - case PixelFormat_RGB_565_BE: + case SAVESTATE::PixelFormat_RGB_565_BE: return AV_PIX_FMT_RGB565BE; - case PixelFormat_RGB_565_LE: + case SAVESTATE::PixelFormat_RGB_565_LE: return AV_PIX_FMT_RGB565LE; - case PixelFormat_RGB_555_BE: + case SAVESTATE::PixelFormat_RGB_555_BE: return AV_PIX_FMT_RGB555BE; - case PixelFormat_RGB_555_LE: + case SAVESTATE::PixelFormat_RGB_555_LE: return AV_PIX_FMT_RGB555LE; default: @@ -143,39 +143,39 @@ AVPixelFormat TranslatePixelFormat(PixelFormat pixelFormat) /*! * \brief Translate the video rotation (RetroPlayer to FlatBuffers) */ -VideoRotation TranslateRotation(unsigned int rotationCCW) +SAVESTATE::VideoRotation TranslateRotation(unsigned int rotationCCW) { switch (rotationCCW) { case 0: - return VideoRotation_CCW_0; + return SAVESTATE::VideoRotation_CCW_0; case 90: - return VideoRotation_CCW_90; + return SAVESTATE::VideoRotation_CCW_90; case 180: - return VideoRotation_CCW_180; + return SAVESTATE::VideoRotation_CCW_180; case 270: - return VideoRotation_CCW_270; + return SAVESTATE::VideoRotation_CCW_270; default: break; } - return VideoRotation_CCW_0; + return SAVESTATE::VideoRotation_CCW_0; } /*! * \brief Translate the video rotation (RetroPlayer to FlatBuffers) */ -unsigned int TranslateRotation(VideoRotation rotationCCW) +unsigned int TranslateRotation(SAVESTATE::VideoRotation rotationCCW) { switch (rotationCCW) { - case VideoRotation_CCW_0: + case SAVESTATE::VideoRotation_CCW_0: return 0; - case VideoRotation_CCW_90: + case SAVESTATE::VideoRotation_CCW_90: return 90; - case VideoRotation_CCW_180: + case SAVESTATE::VideoRotation_CCW_180: return 180; - case VideoRotation_CCW_270: + case SAVESTATE::VideoRotation_CCW_270: return 270; default: break; @@ -529,7 +529,7 @@ uint8_t* CSavestateFlatBuffer::GetMemoryBuffer(size_t size) void CSavestateFlatBuffer::Finalize() { // Helper class to build the nested Savestate table - SavestateBuilder savestateBuilder(*m_builder); + SAVESTATE::SavestateBuilder savestateBuilder(*m_builder); savestateBuilder.add_version(SCHEMA_VERSION); @@ -612,15 +612,15 @@ void CSavestateFlatBuffer::Finalize() auto savestate = savestateBuilder.Finish(); FinishSavestateBuffer(*m_builder, savestate); - m_savestate = GetSavestate(m_builder->GetBufferPointer()); + m_savestate = SAVESTATE::GetSavestate(m_builder->GetBufferPointer()); } bool CSavestateFlatBuffer::Deserialize(std::vector data) { flatbuffers::Verifier verifier(data.data(), data.size()); - if (VerifySavestateBuffer(verifier)) + if (SAVESTATE::VerifySavestateBuffer(verifier)) { - const Savestate* savestate = GetSavestate(data.data()); + const SAVESTATE::Savestate* savestate = SAVESTATE::GetSavestate(data.data()); if (savestate->version() < SCHEMA_MIN_VERSION) { @@ -631,7 +631,7 @@ bool CSavestateFlatBuffer::Deserialize(std::vector data) else { m_data = std::move(data); - m_savestate = GetSavestate(m_data.data()); + m_savestate = SAVESTATE::GetSavestate(m_data.data()); return true; } } diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h index fa42a9bad7034..7783b56946dc9 100644 --- a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h +++ b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.h @@ -18,8 +18,10 @@ namespace KODI { namespace RETRO { +namespace SAVESTATE +{ struct Savestate; -struct SavestateBuilder; +} class CSavestateFlatBuffer : public ISavestate { @@ -95,7 +97,7 @@ class CSavestateFlatBuffer : public ISavestate /*! * \brief FlatBuffer struct used for accessing data */ - const Savestate* m_savestate = nullptr; + const SAVESTATE::Savestate* m_savestate = nullptr; using StringOffset = flatbuffers::Offset; using VectorOffset = flatbuffers::Offset>; From 87004879cc1d242314de07b5c83a9fd574901c0e Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Wed, 26 Jun 2024 20:27:51 -0700 Subject: [PATCH 249/651] Android: Add missing mutex on object create/read/destroy --- .../android/peripherals/PeripheralBusAndroid.cpp | 9 ++++++++- xbmc/platform/android/peripherals/PeripheralBusAndroid.h | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp b/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp index f8282eeb55d0a..a785c9043b61c 100644 --- a/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp +++ b/xbmc/platform/android/peripherals/PeripheralBusAndroid.cpp @@ -112,6 +112,8 @@ bool CPeripheralBusAndroid::InitializeProperties(CPeripheral& peripheral) joystick.SetButtonCount(state.GetButtonCount()); joystick.SetAxisCount(state.GetAxisCount()); + std::unique_lock lock(m_critSectionStates); + // remember the joystick state m_joystickStates.insert(std::make_pair(deviceId, std::move(state))); @@ -134,6 +136,8 @@ bool CPeripheralBusAndroid::InitializeButtonMap(const CPeripheral& peripheral, return false; } + std::unique_lock lock(m_critSectionStates); + // get the joystick state auto it = m_joystickStates.find(deviceId); if (it == m_joystickStates.end()) @@ -325,7 +329,10 @@ void CPeripheralBusAndroid::OnInputDeviceRemoved(int deviceId) if (removed) { - m_joystickStates.erase(deviceId); + { + std::unique_lock lock(m_critSectionStates); + m_joystickStates.erase(deviceId); + } OnDeviceRemoved(deviceLocation); } diff --git a/xbmc/platform/android/peripherals/PeripheralBusAndroid.h b/xbmc/platform/android/peripherals/PeripheralBusAndroid.h index 5ee9480970d8b..59bc261e543b6 100644 --- a/xbmc/platform/android/peripherals/PeripheralBusAndroid.h +++ b/xbmc/platform/android/peripherals/PeripheralBusAndroid.h @@ -66,7 +66,7 @@ class CPeripheralBusAndroid : public CPeripheralBus, mutable std::map m_joystickStates; PeripheralScanResults m_scanResults; - CCriticalSection m_critSectionStates; + mutable CCriticalSection m_critSectionStates; CCriticalSection m_critSectionResults; }; using PeripheralBusAndroidPtr = std::shared_ptr; From 04b710232ea16ec1ccf4f9a40b66bedf535ccef1 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Wed, 26 Jun 2024 22:38:45 -0700 Subject: [PATCH 250/651] Android: Prevent duplicate mapping of surjective keys --- xbmc/platform/android/peripherals/AndroidJoystickState.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xbmc/platform/android/peripherals/AndroidJoystickState.cpp b/xbmc/platform/android/peripherals/AndroidJoystickState.cpp index 9e622d4d73af0..3b1099dd3b375 100644 --- a/xbmc/platform/android/peripherals/AndroidJoystickState.cpp +++ b/xbmc/platform/android/peripherals/AndroidJoystickState.cpp @@ -469,6 +469,10 @@ bool CAndroidJoystickState::MapButton(JOYSTICK::IButtonMap& buttonMap, int butto if (controllerButton.empty()) return false; + // Check if feature is already mapped + if (buttonMap.GetFeatureType(controllerButton) != JOYSTICK::FEATURE_TYPE::UNKNOWN) + return false; + // Map the button CLog::Log(LOGDEBUG, "Automatically mapping {} to {}", controllerButton, buttonPrimitive.ToString()); From 317a652f2af3d6710a06b3791739002b05672b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Sat, 6 Jul 2024 20:03:41 +0200 Subject: [PATCH 251/651] RecursiveMutex: Prevent initialization-order-fiasco of mutex attribute ASAN error: ==226990==ERROR: AddressSanitizer: initialization-order-fiasco on address 0x5a8f25b81ce0 at pc 0x5a8f17dd10ae bp 0x7ffe5391ce50 sp 0x7ffe5391ce48 READ of size 1 at 0x5a8f25b81ce0 thread T0 #0 0x5a8f17dd10ad in XbmcThreads::CRecursiveMutex::getRecursiveAttr() xbmc/platform/posix/threads/RecursiveMutex.cpp:37:8 #1 0x5a8f17817a4e in XbmcThreads::CRecursiveMutex::CRecursiveMutex() xbmc/platform/posix/threads/RecursiveMutex.h:35:60 #2 0x5a8f17817998 in XbmcThreads::CountingLockable::CountingLockable() xbmc/threads/Lockables.h:47:12 #3 0x5a8f1780dae8 in CCriticalSection::CCriticalSection() xbmc/threads/CriticalSection.h:16:7 #4 0x5a8f1b27fd98 in CComponentContainer::CComponentContainer() xbmc/utils/ComponentContainer.h:26:7 #5 0x5a8f1b1f3273 in CApplication::CApplication() xbmc/application/Application.cpp:217:15 #6 0x5a8f17918fe0 in xbmcutil::GlobalsSingleton::getInstance() xbmc/utils/GlobalsHandling.h:147:23 #7 0x5a8f17918f0f in __cxx_global_var_init.1 xbmc/application/Application.h:250:1 #8 0x5a8f17918f49 in _GLOBAL__sub_I_UPnPRenderer.cpp xbmc/network/upnp/UPnPRenderer.cpp #9 0x7c5016639dc3 in __libc_start_main (/usr/lib/libc.so.6+0x25dc3) (BuildId: 32a656aa5562eece8c59a585f5eacd6cf5e2307b) #10 0x5a8f17636814 in _start (/home/mark/Coding/Repos/kodi-git/build_clang_debug_sanitizer/kodi.bin+0x9fa3814) (BuildId: 6cfd7dacce8a4a587ad47d2f082a4e50dad4c176) 0x5a8f25b81ce0 is located 32 bytes before global variable 'XbmcThreads::recursiveAttr' defined in 'xbmc/platform/posix/threads/RecursiveMutex.cpp' (0x5a8f25b81d00) of size 4 registered at: #0 0x5a8f17651629 in __asan_register_globals.part.0 (/home/mark/Coding/Repos/kodi-git/build_clang_debug_sanitizer/kodi.bin+0x9fbe629) (BuildId: 6cfd7dacce8a4a587ad47d2f082a4e50dad4c176) #1 0x5a8f17651cce in __asan_register_elf_globals (/home/mark/Coding/Repos/kodi-git/build_clang_debug_sanitizer/kodi.bin+0x9fbecce) (BuildId: 6cfd7dacce8a4a587ad47d2f082a4e50dad4c176) #2 0x7c5016639dc3 in __libc_start_main (/usr/lib/libc.so.6+0x25dc3) (BuildId: 32a656aa5562eece8c59a585f5eacd6cf5e2307b) #3 0x5a8f17636814 in _start (/home/mark/Coding/Repos/kodi-git/build_clang_debug_sanitizer/kodi.bin+0x9fa3814) (BuildId: 6cfd7dacce8a4a587ad47d2f082a4e50dad4c176) 0x5a8f25b81ce0 is located 0 bytes inside of global variable 'XbmcThreads::recursiveAttrSet' defined in 'xbmc/platform/posix/threads/RecursiveMutex.cpp' (0x5a8f25b81ce0) of size 1 'XbmcThreads::recursiveAttrSet' is ascii string '' registered at: #0 0x5a8f17651629 in __asan_register_globals.part.0 (/home/mark/Coding/Repos/kodi-git/build_clang_debug_sanitizer/kodi.bin+0x9fbe629) (BuildId: 6cfd7dacce8a4a587ad47d2f082a4e50dad4c176) #1 0x5a8f17651cce in __asan_register_elf_globals (/home/mark/Coding/Repos/kodi-git/build_clang_debug_sanitizer/kodi.bin+0x9fbecce) (BuildId: 6cfd7dacce8a4a587ad47d2f082a4e50dad4c176) #2 0x7c5016639dc3 in __libc_start_main (/usr/lib/libc.so.6+0x25dc3) (BuildId: 32a656aa5562eece8c59a585f5eacd6cf5e2307b) #3 0x5a8f17636814 in _start (/home/mark/Coding/Repos/kodi-git/build_clang_debug_sanitizer/kodi.bin+0x9fa3814) (BuildId: 6cfd7dacce8a4a587ad47d2f082a4e50dad4c176) SUMMARY: AddressSanitizer: initialization-order-fiasco xbmc/platform/posix/threads/RecursiveMutex.cpp:37:8 in XbmcThreads::CRecursiveMutex::getRecursiveAttr() Shadow bytes around the buggy address: 0x5a8f25b81a00: f6 f6 f6 f6 f6 f6 f6 f6 f6 f6 f6 f6 00 00 00 00 0x5a8f25b81a80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x5a8f25b81b00: 00 00 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 00 00 00 00 0x5a8f25b81b80: f6 f6 f6 f6 00 00 00 00 f6 f6 f6 f6 f6 f6 f6 f6 0x5a8f25b81c00: f6 f6 f6 f6 f6 f6 f6 f6 f6 f6 f6 f6 f6 f6 f6 f6 =>0x5a8f25b81c80: 00 00 00 00 f6 f6 f6 f6 00 00 00 00[f6]f6 f6 f6 0x5a8f25b81d00: 04 f9 f9 f9 01 f9 f9 f9 00 00 00 00 f6 f6 f6 f6 0x5a8f25b81d80: 00 00 00 00 f6 f6 f6 f6 f6 f6 f6 f6 f6 f6 f6 f6 0x5a8f25b81e00: 00 00 00 00 f6 f6 f6 f6 00 00 00 00 f9 f9 f9 f9 0x5a8f25b81e80: 00 f9 f9 f9 00 f9 f9 f9 f6 f6 f6 f6 f6 f6 f6 f6 0x5a8f25b81f00: f6 f6 f6 f6 00 00 00 00 00 00 00 00 00 00 00 00 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb --- .../platform/posix/threads/RecursiveMutex.cpp | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/xbmc/platform/posix/threads/RecursiveMutex.cpp b/xbmc/platform/posix/threads/RecursiveMutex.cpp index 652bc3722eece..19a940e518bca 100644 --- a/xbmc/platform/posix/threads/RecursiveMutex.cpp +++ b/xbmc/platform/posix/threads/RecursiveMutex.cpp @@ -11,31 +11,18 @@ namespace XbmcThreads { -static pthread_mutexattr_t recursiveAttr; - -static bool SetRecursiveAttr() +pthread_mutexattr_t& CRecursiveMutex::getRecursiveAttr() { - static bool alreadyCalled = false; - - if (!alreadyCalled) + static pthread_mutexattr_t recursiveAttr = []() { + pthread_mutexattr_t recursiveAttr; pthread_mutexattr_init(&recursiveAttr); pthread_mutexattr_settype(&recursiveAttr, PTHREAD_MUTEX_RECURSIVE); #if !defined(TARGET_ANDROID) pthread_mutexattr_setprotocol(&recursiveAttr, PTHREAD_PRIO_INHERIT); #endif - alreadyCalled = true; - } - - return true; // note, we never call destroy. -} - -static bool recursiveAttrSet = SetRecursiveAttr(); - -pthread_mutexattr_t& CRecursiveMutex::getRecursiveAttr() -{ - if (!recursiveAttrSet) // this is only possible in the single threaded startup code - recursiveAttrSet = SetRecursiveAttr(); + return recursiveAttr; + }(); return recursiveAttr; } From 648c9a21f674bd71405e8e54111cd682e461ebd8 Mon Sep 17 00:00:00 2001 From: Martin Vallevand Date: Sun, 7 Jul 2024 08:26:10 -0400 Subject: [PATCH 252/651] Remove duplicated EpgEventTitle from PVR lists In the common PVRListItemLayout if the ListItem.Label is the same as the ListItem.EpgEventTitle the sub-labels will show the duplicated title and give priority screen space to it. This skips those displays --- addons/skin.estuary/xml/Variables.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 6a9b291a76059..a40f0e31e0845 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -654,15 +654,15 @@ [COLOR grey]$INFO[ListItem.Timertype][/COLOR] - $INFO[ListItem.EpgEventTitle] | [COLOR grey]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/COLOR] + $INFO[ListItem.EpgEventTitle] | [COLOR grey]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/COLOR] [COLOR grey]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/COLOR] - $INFO[ListItem.EpgEventTitle] + $INFO[ListItem.EpgEventTitle] $INFO[ListItem.Timertype] - $INFO[ListItem.EpgEventTitle] | $VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName] + $INFO[ListItem.EpgEventTitle] | [COLOR grey]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/COLOR] $VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName] - $INFO[ListItem.EpgEventTitle] + $INFO[ListItem.EpgEventTitle] $INFO[ListItem.StartDate,[COLOR grey]$LOCALIZE[552]:[/COLOR] ,[CR]]$INFO[ListItem.StartTime,[COLOR grey]$LOCALIZE[555]:[/COLOR] ,[CR]]$INFO[ListItem.Duration,[COLOR grey]$LOCALIZE[180]:[/COLOR] ] From 7572e2764cbdd5f686bb96fad06789e92958ae3e Mon Sep 17 00:00:00 2001 From: sarbes Date: Sun, 7 Jul 2024 16:32:33 +0200 Subject: [PATCH 253/651] Fix texture edge clamping (#25444) --- xbmc/guilib/TextureBase.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/guilib/TextureBase.cpp b/xbmc/guilib/TextureBase.cpp index 5c2c01e22b359..63c510ff94922 100644 --- a/xbmc/guilib/TextureBase.cpp +++ b/xbmc/guilib/TextureBase.cpp @@ -127,6 +127,7 @@ void CTextureBase::ClampToEdge() for (uint32_t x = imagePitch; x < texturePitch; x += blockSize) memcpy(dst + x, src, blockSize); dst += texturePitch; + src += texturePitch; } } From 35a5f956576b9316c61c9119f1e2119299e2597a Mon Sep 17 00:00:00 2001 From: CrystalP Date: Thu, 18 Apr 2024 09:42:48 -0400 Subject: [PATCH 254/651] [videodb] Remove KeepId from DeleteMovie DeleteMovie behaves very differently with KeepId=true and turns it into a "DeleteStreams" function. To simplify the logic, remove the parameter and make the only caller do the stream deletion directly. --- xbmc/video/VideoDatabase.cpp | 68 ++++++++----------- xbmc/video/VideoDatabase.h | 1 - xbmc/video/jobs/VideoLibraryRefreshingJob.cpp | 2 +- 3 files changed, 31 insertions(+), 40 deletions(-) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 686dbbbe3de38..432135b41cf26 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -2624,7 +2624,10 @@ int CVideoDatabase::SetDetailsForMovie(CVideoInfoTag& details, idMovie = GetMovieId(filePath); if (idMovie > -1) - DeleteMovie(idMovie, true); // true to keep the table entry + { + const int idFile{GetDbId(PrepareSQL("SELECT idFile FROM movie WHERE idMovie=%i", idMovie))}; + DeleteStreamDetails(idFile); + } else { // only add a new movie if we don't already have a valid idMovie @@ -3701,9 +3704,7 @@ void CVideoDatabase::DeleteBookMarkForEpisode(const CVideoInfoTag& tag) } //******************************************************************************************************************************** -void CVideoDatabase::DeleteMovie(int idMovie, - bool bKeepId /* = false */, - DeleteMovieCascadeAction ca /* = ALL_ASSETS */) +void CVideoDatabase::DeleteMovie(int idMovie, DeleteMovieCascadeAction ca /* = ALL_ASSETS */) { if (idMovie < 0) return; @@ -3717,55 +3718,46 @@ void CVideoDatabase::DeleteMovie(int idMovie, BeginTransaction(); - int idFile = GetDbId(PrepareSQL("SELECT idFile FROM movie WHERE idMovie=%i", idMovie)); + const int idFile{GetDbId(PrepareSQL("SELECT idFile FROM movie WHERE idMovie=%i", idMovie))}; DeleteStreamDetails(idFile); - // keep the movie table entry, linking to tv shows, and bookmarks - // so we can update the data in place - // the ancillary tables are still purged - if (!bKeepId) - { - const std::string path = GetSingleValue(PrepareSQL( - "SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", - idFile)); - if (!path.empty()) - InvalidatePathHash(path); + const std::string path = GetSingleValue(PrepareSQL( + "SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", + idFile)); + if (!path.empty()) + InvalidatePathHash(path); - const std::string strSQL = PrepareSQL("delete from movie where idMovie=%i", idMovie); - m_pDS->exec(strSQL); + const std::string strSQL{PrepareSQL("DELETE FROM movie WHERE idMovie=%i", idMovie)}; + m_pDS->exec(strSQL); - if (ca == DeleteMovieCascadeAction::ALL_ASSETS) - { - // The default version of the movie was removed by a delete trigger. - // Clean up the other assets attached to the movie, if any. + if (ca == DeleteMovieCascadeAction::ALL_ASSETS) + { + // The default version of the movie was removed by a delete trigger. + // Clean up the other assets attached to the movie, if any. - // need local dataset due to nested DeleteVideoAsset query - std::unique_ptr pDS{m_pDB->CreateDataset()}; + // need local dataset due to nested DeleteVideoAsset query + const std::unique_ptr pDS{m_pDB->CreateDataset()}; - pDS->query( - PrepareSQL("SELECT idFile FROM videoversion WHERE idMedia=%i AND media_type='%s'", - idMovie, MediaTypeMovie)); + pDS->query(PrepareSQL("SELECT idFile FROM videoversion WHERE idMedia=%i AND media_type='%s'", + idMovie, MediaTypeMovie)); - while (!pDS->eof()) + while (!pDS->eof()) + { + if (!DeleteVideoAsset(pDS->fv(0).get_asInt())) { - if (!DeleteVideoAsset(pDS->fv(0).get_asInt())) - { - RollbackTransaction(); - pDS->close(); - return; - } - pDS->next(); + RollbackTransaction(); + pDS->close(); + return; } - pDS->close(); + pDS->next(); } + pDS->close(); } //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore - if (!bKeepId) - AnnounceRemove(MediaTypeMovie, idMovie); + AnnounceRemove(MediaTypeMovie, idMovie); CommitTransaction(); - } catch (...) { diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index 9c220a47b7828..59d7dc5f9e99b 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -604,7 +604,6 @@ class CVideoDatabase : public CDatabase int UpdateDetailsForMovie(int idMovie, CVideoInfoTag& details, const std::map &artwork, const std::set &updatedDetails); void DeleteMovie(int idMovie, - bool bKeepId = false, DeleteMovieCascadeAction action = DeleteMovieCascadeAction::ALL_ASSETS); void DeleteTvShow(int idTvShow, bool bKeepId = false); void DeleteTvShow(const std::string& strPath); diff --git a/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp b/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp index 958a6f970c0ea..35b5c09445566 100644 --- a/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp +++ b/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp @@ -333,7 +333,7 @@ bool CVideoLibraryRefreshingJob::Work(CVideoDatabase &db) if (origDbId > 0) { if (scraper->Content() == CONTENT_MOVIES) - db.DeleteMovie(origDbId, false, DeleteMovieCascadeAction::DEFAULT_VERSION); + db.DeleteMovie(origDbId, DeleteMovieCascadeAction::DEFAULT_VERSION); else if (scraper->Content() == CONTENT_MUSICVIDEOS) db.DeleteMusicVideo(origDbId); else if (scraper->Content() == CONTENT_TVSHOWS) From 91bec7604e04db52437cd7fc9853f6bf37dce4a5 Mon Sep 17 00:00:00 2001 From: CrystalP Date: Thu, 18 Apr 2024 09:59:15 -0400 Subject: [PATCH 255/651] [videodb] Remove redundant deletion of stream details when setting movie details SetStreamDetailsForFileId clears existing details unconditionnally, then adds the new ones. Added doxygen. --- xbmc/video/VideoDatabase.cpp | 9 +-------- xbmc/video/VideoDatabase.h | 5 +++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 432135b41cf26..4d09ed4553384 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -2623,16 +2623,9 @@ int CVideoDatabase::SetDetailsForMovie(CVideoInfoTag& details, if (idMovie < 0) idMovie = GetMovieId(filePath); - if (idMovie > -1) - { - const int idFile{GetDbId(PrepareSQL("SELECT idFile FROM movie WHERE idMovie=%i", idMovie))}; - DeleteStreamDetails(idFile); - } - else + if (idMovie == -1) { // only add a new movie if we don't already have a valid idMovie - // (DeleteMovie is called with bKeepId == true so the movie won't - // be removed from the movie table) idMovie = AddNewMovie(details); if (idMovie < 0) { diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index 59d7dc5f9e99b..d5db8f459b9b1 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -591,6 +591,11 @@ class CVideoDatabase : public CDatabase const std::map& artwork, int idMVideo = -1); int SetStreamDetailsForFile(const CStreamDetails& details, const std::string& strFileNameAndPath); + /*! + * \brief Clear any existing stream details and add the new provided details to a file. + * \param[in] details New stream details + * \param[in] idFile Identifier of the file + */ void SetStreamDetailsForFileId(const CStreamDetails& details, int idFile); bool SetSingleValue(VideoDbContentType type, int dbId, int dbField, const std::string& strValue); From dc3b6c1a0e3642cb79a7d062749d49ea51bd2f70 Mon Sep 17 00:00:00 2001 From: CrystalP Date: Fri, 19 Apr 2024 09:40:57 -0400 Subject: [PATCH 256/651] [videodb] Preserve path hash when turning a movie into a version This is a small optimization: the path hash is deleted when a movie is turned into a version, and during the next libray scan, the folder of the movie is scanned (unnecessary) and the same hash is recreated. The file of the version hasn't moved or changed, the hash is still valid, might as well not delete it in the first place and save the directory scan. --- xbmc/video/VideoDatabase.cpp | 20 +++++++++++++------- xbmc/video/VideoDatabase.h | 9 ++++++++- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 4d09ed4553384..cc7bda1fd743a 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -3697,7 +3697,9 @@ void CVideoDatabase::DeleteBookMarkForEpisode(const CVideoInfoTag& tag) } //******************************************************************************************************************************** -void CVideoDatabase::DeleteMovie(int idMovie, DeleteMovieCascadeAction ca /* = ALL_ASSETS */) +void CVideoDatabase::DeleteMovie(int idMovie, + DeleteMovieCascadeAction ca /* = ALL_ASSETS */, + DeleteMovieHashAction hashAction /* = HASH_DELETE */) { if (idMovie < 0) return; @@ -3714,11 +3716,14 @@ void CVideoDatabase::DeleteMovie(int idMovie, DeleteMovieCascadeAction ca /* = A const int idFile{GetDbId(PrepareSQL("SELECT idFile FROM movie WHERE idMovie=%i", idMovie))}; DeleteStreamDetails(idFile); - const std::string path = GetSingleValue(PrepareSQL( - "SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", - idFile)); - if (!path.empty()) - InvalidatePathHash(path); + if (hashAction == DeleteMovieHashAction::HASH_DELETE) + { + const std::string path = GetSingleValue(PrepareSQL( + "SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", + idFile)); + if (!path.empty()) + InvalidatePathHash(path); + } const std::string strSQL{PrepareSQL("DELETE FROM movie WHERE idMovie=%i", idMovie)}; m_pDS->exec(strSQL); @@ -12404,7 +12409,8 @@ bool CVideoDatabase::ConvertVideoToVersion(VideoDbContentType itemType, SetVideoVersionDefaultArt(idFile, dbIdSource, itemType); if (itemType == VideoDbContentType::MOVIES) - DeleteMovie(dbIdSource); + DeleteMovie(dbIdSource, DeleteMovieCascadeAction::ALL_ASSETS, + DeleteMovieHashAction::HASH_PRESERVE); } // Rename the default version diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index d5db8f459b9b1..50b7feffdefc9 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -414,6 +414,12 @@ enum class DeleteMovieCascadeAction ALL_ASSETS }; +enum class DeleteMovieHashAction +{ + HASH_DELETE, + HASH_PRESERVE +}; + #define COMPARE_PERCENTAGE 0.90f // 90% #define COMPARE_PERCENTAGE_MIN 0.50f // 50% @@ -609,7 +615,8 @@ class CVideoDatabase : public CDatabase int UpdateDetailsForMovie(int idMovie, CVideoInfoTag& details, const std::map &artwork, const std::set &updatedDetails); void DeleteMovie(int idMovie, - DeleteMovieCascadeAction action = DeleteMovieCascadeAction::ALL_ASSETS); + DeleteMovieCascadeAction action = DeleteMovieCascadeAction::ALL_ASSETS, + DeleteMovieHashAction hashAction = DeleteMovieHashAction::HASH_DELETE); void DeleteTvShow(int idTvShow, bool bKeepId = false); void DeleteTvShow(const std::string& strPath); void DeleteSeason(int idSeason, bool bKeepId = false); From 95f0748b158bdac2df3ccf75e59bc25293b8da82 Mon Sep 17 00:00:00 2001 From: CastagnaIT Date: Mon, 8 Jul 2024 07:49:45 +0200 Subject: [PATCH 257/651] [DVDFactorySubtitle] Improved ASS format detection --- .../DVDSubtitles/DVDFactorySubtitle.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDFactorySubtitle.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDFactorySubtitle.cpp index 51270337c6f38..6f922cd02f57e 100644 --- a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDFactorySubtitle.cpp +++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDFactorySubtitle.cpp @@ -20,6 +20,12 @@ #include #include +#include + +namespace +{ +constexpr const char* ASS_SCRIPT_TYPE_RE = "^ScriptType:\\s*v4\\.00\\+?"; +} CDVDSubtitleParser* CDVDFactorySubtitle::CreateParser(std::string& strFile) { @@ -32,6 +38,8 @@ CDVDSubtitleParser* CDVDFactorySubtitle::CreateParser(std::string& strFile) return nullptr; } + const std::regex reAssScriptType{ASS_SCRIPT_TYPE_RE}; + for (int t = 0; t < 256; t++) { if (pStream->ReadLine(line)) @@ -55,10 +63,9 @@ CDVDSubtitleParser* CDVDFactorySubtitle::CreateParser(std::string& strFile) return new CDVDSubtitleParserVplayer(std::move(pStream), strFile); } else if (!StringUtils::CompareNoCase(line, "!: This is a Sub Station Alpha v", 32) || - !StringUtils::CompareNoCase(line, "ScriptType: v4.00", 17) || - !StringUtils::CompareNoCase(line, "Dialogue: Marked", 16) || - !StringUtils::CompareNoCase(line, "Dialogue: ", 10) || - !StringUtils::CompareNoCase(line, "[Events]", 8)) + std::regex_match(line, reAssScriptType) || + !StringUtils::CompareNoCase(line, "[Events]", 8) || + !StringUtils::CompareNoCase(line, "Dialogue:", 9)) { return new CDVDSubtitleParserSSA(std::move(pStream), strFile); } From 9dacccd1940abd5fa0c6dc5007aecdd5ffb114b5 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Mon, 24 Jun 2024 14:34:22 +0100 Subject: [PATCH 258/651] [EDL] Use std::chrono --- xbmc/cores/DataCacheCore.cpp | 9 +- xbmc/cores/DataCacheCore.h | 20 +-- xbmc/cores/EdlEdit.h | 6 +- xbmc/cores/VideoPlayer/Edl.cpp | 154 ++++++++++++-------- xbmc/cores/VideoPlayer/Edl.h | 35 ++--- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 90 +++++++----- xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp | 123 +++++++++------- xbmc/guilib/guiinfo/PlayerGUIInfo.cpp | 13 +- xbmc/pvr/PVREdl.cpp | 6 +- 9 files changed, 259 insertions(+), 197 deletions(-) diff --git a/xbmc/cores/DataCacheCore.cpp b/xbmc/cores/DataCacheCore.cpp index 8eb961733b8c1..4a01f8fbb9476 100644 --- a/xbmc/cores/DataCacheCore.cpp +++ b/xbmc/cores/DataCacheCore.cpp @@ -11,6 +11,7 @@ #include "ServiceBroker.h" #include "cores/EdlEdit.h" +#include #include #include @@ -271,25 +272,25 @@ const std::vector& CDataCacheCore::GetEditList() const return m_contentInfo.GetEditList(); } -void CDataCacheCore::SetCuts(const std::vector& cuts) +void CDataCacheCore::SetCuts(const std::vector& cuts) { std::unique_lock lock(m_contentSection); m_contentInfo.SetCuts(cuts); } -const std::vector& CDataCacheCore::GetCuts() const +const std::vector& CDataCacheCore::GetCuts() const { std::unique_lock lock(m_contentSection); return m_contentInfo.GetCuts(); } -void CDataCacheCore::SetSceneMarkers(const std::vector& sceneMarkers) +void CDataCacheCore::SetSceneMarkers(const std::vector& sceneMarkers) { std::unique_lock lock(m_contentSection); m_contentInfo.SetSceneMarkers(sceneMarkers); } -const std::vector& CDataCacheCore::GetSceneMarkers() const +const std::vector& CDataCacheCore::GetSceneMarkers() const { std::unique_lock lock(m_contentSection); return m_contentInfo.GetSceneMarkers(); diff --git a/xbmc/cores/DataCacheCore.h b/xbmc/cores/DataCacheCore.h index c09d7fcf10d08..5a4b1509e9fff 100644 --- a/xbmc/cores/DataCacheCore.h +++ b/xbmc/cores/DataCacheCore.h @@ -86,25 +86,25 @@ class CDataCacheCore * @brief Set the list of cut markers in cache. * @return The list of cuts or an empty list if no cuts exist */ - void SetCuts(const std::vector& cuts); + void SetCuts(const std::vector& cuts); /*! * @brief Get the list of cut markers from cache. * @return The list of cut markers or an empty vector if no cuts exist. */ - const std::vector& GetCuts() const; + const std::vector& GetCuts() const; /*! * @brief Set the list of scene markers in cache. * @return The list of scene markers or an empty list if no scene markers exist */ - void SetSceneMarkers(const std::vector& sceneMarkers); + void SetSceneMarkers(const std::vector& sceneMarkers); /*! * @brief Get the list of scene markers markers from cache. * @return The list of scene markers or an empty vector if no scene exist. */ - const std::vector& GetSceneMarkers() const; + const std::vector& GetSceneMarkers() const; void SetChapters(const std::vector>& chapters); @@ -242,19 +242,19 @@ class CDataCacheCore * @brief Save the list of cut markers in cache. * @param cuts the list of cut markers to store in cache */ - void SetCuts(const std::vector& cuts) { m_cuts = cuts; } + void SetCuts(const std::vector& cuts) { m_cuts = cuts; } /*! * @brief Get the list of cut markers in cache. * @return the list of cut markers in cache */ - const std::vector& GetCuts() const { return m_cuts; } + const std::vector& GetCuts() const { return m_cuts; } /*! * @brief Save the list of scene markers in cache. * @param sceneMarkers the list of scene markers to store in cache */ - void SetSceneMarkers(const std::vector& sceneMarkers) + void SetSceneMarkers(const std::vector& sceneMarkers) { m_sceneMarkers = sceneMarkers; } @@ -263,7 +263,7 @@ class CDataCacheCore * @brief Get the list of scene markers in cache. * @return the list of scene markers in cache */ - const std::vector& GetSceneMarkers() const { return m_sceneMarkers; } + const std::vector& GetSceneMarkers() const { return m_sceneMarkers; } /*! * @brief Save the chapter list in cache. @@ -297,9 +297,9 @@ class CDataCacheCore /*!< name and position for chapters */ std::vector> m_chapters; /*!< position for EDL cuts */ - std::vector m_cuts; + std::vector m_cuts; /*!< position for EDL scene markers */ - std::vector m_sceneMarkers; + std::vector m_sceneMarkers; } m_contentInfo; CCriticalSection m_renderSection; diff --git a/xbmc/cores/EdlEdit.h b/xbmc/cores/EdlEdit.h index a06ed1572a1fc..74ad3521beb86 100644 --- a/xbmc/cores/EdlEdit.h +++ b/xbmc/cores/EdlEdit.h @@ -8,6 +8,8 @@ #pragma once +#include + namespace EDL { @@ -23,8 +25,8 @@ enum class Action struct Edit { - int start = 0; // ms - int end = 0; // ms + std::chrono::milliseconds start{0}; + std::chrono::milliseconds end{0}; Action action = Action::CUT; }; diff --git a/xbmc/cores/VideoPlayer/Edl.cpp b/xbmc/cores/VideoPlayer/Edl.cpp index 025261144259a..68cd40bf631c1 100644 --- a/xbmc/cores/VideoPlayer/Edl.cpp +++ b/xbmc/cores/VideoPlayer/Edl.cpp @@ -20,6 +20,8 @@ #include "utils/XBMCTinyXML2.h" #include "utils/log.h" +#include + #include "PlatformDefs.h" #define COMSKIP_HEADER "FILE PROCESSING COMPLETE" @@ -27,6 +29,7 @@ #define VIDEOREDO_TAG_CUT "" #define VIDEOREDO_TAG_SCENE " fieldParts = StringUtils::Split(strFields[i], '.'); if (fieldParts.size() == 1) // No ms { - editStartEnd[i] = StringUtils::TimeStringToSeconds(fieldParts[0]) * - static_cast(1000); // seconds to ms + editStartEnd[i] = std::chrono::duration_cast( + std::chrono::seconds(StringUtils::TimeStringToSeconds(fieldParts[0]))); } else if (fieldParts.size() == 2) // Has ms. Everything after the dot (.) is ms { @@ -179,9 +182,12 @@ bool CEdl::ReadEdl(const std::string& mediaFilePath, float fps) { fieldParts[1] = fieldParts[1].substr(0, 3); } - editStartEnd[i] = - static_cast(StringUtils::TimeStringToSeconds(fieldParts[0])) * 1000 + - std::atoi(fieldParts[1].c_str()); // seconds to ms + int additionalMs{0}; + editStartEnd[i] = std::chrono::duration_cast( + std::chrono::seconds(StringUtils::TimeStringToSeconds(fieldParts[0]))); + std::from_chars(fieldParts[1].data(), fieldParts[1].data() + fieldParts[1].size(), + additionalMs); + editStartEnd[i] += std::chrono::milliseconds(additionalMs); } else { @@ -193,8 +199,10 @@ bool CEdl::ReadEdl(const std::string& mediaFilePath, float fps) { if (fps > 0.0f) { - editStartEnd[i] = static_cast(std::atol(strFields[i].substr(1).c_str()) / fps * - 1000); // frame number to ms + std::chrono::duration> durationInSeconds{ + std::atol(strFields[i].substr(1).c_str()) / fps}; + editStartEnd[i] = + std::chrono::duration_cast(durationInSeconds); } else { @@ -207,7 +215,8 @@ bool CEdl::ReadEdl(const std::string& mediaFilePath, float fps) } else // Plain old seconds in float format, e.g. 123.45 { - editStartEnd[i] = std::lround(std::atof(strFields[i].c_str()) * 1000); // seconds to ms + editStartEnd[i] = + std::chrono::milliseconds{std::lround(std::atof(strFields[i].c_str()) * 1000)}; } } @@ -344,8 +353,13 @@ bool CEdl::ReadComskip(const std::string& mediaFilePath, float fps) if (sscanf(szBuffer, "%lf %lf", &dStartFrame, &dEndFrame) == 2) { Edit edit; - edit.start = std::lround(dStartFrame / static_cast(fFrameRate) * 1000.0); - edit.end = std::lround(dEndFrame / static_cast(fFrameRate) * 1000.0); + edit.start = std::chrono::duration_cast( + std::chrono::duration>{dStartFrame / + static_cast(fFrameRate)}); + edit.end = std::chrono::duration_cast( + std::chrono::duration_cast( + std::chrono::duration>{dEndFrame / + static_cast(fFrameRate)})); edit.action = Action::COMM_BREAK; bValid = AddEdit(edit); } @@ -427,8 +441,8 @@ bool CEdl::ReadVideoReDo(const std::string& mediaFilePath) * Times need adjusting by 1/10,000 to get ms. */ Edit edit; - edit.start = std::lround(dStart / 10000); - edit.end = std::lround(dEnd / 10000); + edit.start = std::chrono::milliseconds(std::lround(dStart / 10000)); + edit.end = std::chrono::milliseconds(std::lround(dEnd / 10000)); edit.action = Action::CUT; bValid = AddEdit(edit); } @@ -440,8 +454,8 @@ bool CEdl::ReadVideoReDo(const std::string& mediaFilePath) int iScene; double dSceneMarker; if (sscanf(szBuffer + strlen(VIDEOREDO_TAG_SCENE), " %i>%lf", &iScene, &dSceneMarker) == 2) - bValid = AddSceneMarker( - std::lround(dSceneMarker / 10000)); // Times need adjusting by 1/10,000 to get ms. + bValid = AddSceneMarker(std::chrono::milliseconds( + std::lround(dSceneMarker / 10000))); // Times need adjusting by 1/10,000 to get ms. else bValid = false; } @@ -528,8 +542,10 @@ bool CEdl::ReadBeyondTV(const std::string& mediaFilePath) * atof() returns 0 if there were any problems and will subsequently be rejected in AddEdit(). */ Edit edit; - edit.start = std::lround((std::atof(start->FirstChild()->Value()) / 10000)); - edit.end = std::lround((std::atof(end->FirstChild()->Value()) / 10000)); + edit.start = + std::chrono::milliseconds(std::lround((std::atof(start->FirstChild()->Value()) / 10000))); + edit.end = + std::chrono::milliseconds(std::lround((std::atof(end->FirstChild()->Value()) / 10000))); edit.action = Action::COMM_BREAK; valid = AddEdit(edit); } @@ -616,7 +632,7 @@ bool CEdl::AddEdit(const Edit& newEdit) return false; } - if (edit.start < 0) + if (edit.start < 0ms) { CLog::Log(LOGERROR, "{} - Before start! [{} - {}], {}", __FUNCTION__, MillisecondsToTimeString(edit.start), MillisecondsToTimeString(edit.end), @@ -658,20 +674,24 @@ bool CEdl::AddEdit(const Edit& newEdit) * the start (autowait) and automatically rewind by a bit (autowind) at the end of the commercial * break. */ - int autowait = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iEdlCommBreakAutowait * 1000; // seconds -> ms - int autowind = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iEdlCommBreakAutowind * 1000; // seconds -> ms - - if (edit.start > 0) // Only autowait if not at the start. + auto autowait = std::chrono::duration_cast(std::chrono::seconds( + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iEdlCommBreakAutowait)); + std::chrono::milliseconds autowind = std::chrono::duration_cast( + std::chrono::seconds(CServiceBroker::GetSettingsComponent() + ->GetAdvancedSettings() + ->m_iEdlCommBreakAutowind)); + + if (edit.start > 0ms) // Only autowait if not at the start. { /* get the edit length so we don't start skipping after the end */ - int editLength = edit.end - edit.start; + std::chrono::milliseconds editLength = edit.end - edit.start; /* add the lesser of the edit length or the autowait to the start */ edit.start += autowait > editLength ? editLength : autowait; } if (edit.end > edit.start) // Only autowind if there is any edit time remaining. { /* get the remaining edit length so we don't rewind to before the start */ - int editLength = edit.end - edit.start; + std::chrono::milliseconds editLength = edit.end - edit.start; /* subtract the lesser of the edit length or the autowind from the end */ edit.end -= autowind > editLength ? editLength : autowind; } @@ -709,7 +729,7 @@ bool CEdl::AddEdit(const Edit& newEdit) return true; } -bool CEdl::AddSceneMarker(int iSceneMarker) +bool CEdl::AddSceneMarker(std::chrono::milliseconds iSceneMarker) { const auto edit = InEdit(iSceneMarker); if (edit && edit.value()->action == Action::CUT) // Only works for current cuts. @@ -729,10 +749,10 @@ bool CEdl::HasEdits() const bool CEdl::HasCuts() const { - return m_totalCutTime > 0; + return m_totalCutTime > 0ms; } -int CEdl::GetTotalCutTime() const +std::chrono::milliseconds CEdl::GetTotalCutTime() const { return m_totalCutTime; // ms } @@ -741,7 +761,7 @@ const std::vector CEdl::GetEditList() const { // the sum of cut durations while we iterate over them // note: edits are ordered by start time - int surpassedSumOfCutDurations{0}; + std::chrono::milliseconds surpassedSumOfCutDurations{0ms}; std::vector editList; // @note we should not modify the original edits since @@ -757,7 +777,7 @@ const std::vector CEdl::GetEditList() const continue; } - // substract the duration of already surpassed cuts + // subtract the duration of already surpassed cuts edit.start -= surpassedSumOfCutDurations; edit.end -= surpassedSumOfCutDurations; editList.emplace_back(edit); @@ -766,10 +786,10 @@ const std::vector CEdl::GetEditList() const return editList; } -const std::vector CEdl::GetCutMarkers() const +const std::vector CEdl::GetCutMarkers() const { - int surpassedSumOfCutDurations{0}; - std::vector cutList; + std::chrono::milliseconds surpassedSumOfCutDurations{0}; + std::vector cutList; for (const EDL::Edit& edit : m_vecEdits) { if (edit.action != Action::CUT) @@ -781,23 +801,23 @@ const std::vector CEdl::GetCutMarkers() const return cutList; } -const std::vector CEdl::GetSceneMarkers() const +const std::vector CEdl::GetSceneMarkers() const { - std::vector sceneMarkers; + std::vector sceneMarkers; sceneMarkers.reserve(m_vecSceneMarkers.size()); - for (const int& scene : m_vecSceneMarkers) + for (const std::chrono::milliseconds& scene : m_vecSceneMarkers) { sceneMarkers.emplace_back(GetTimeWithoutCuts(scene)); } return sceneMarkers; } -int CEdl::GetTimeWithoutCuts(int seek) const +std::chrono::milliseconds CEdl::GetTimeWithoutCuts(std::chrono::milliseconds seek) const { if (!HasCuts()) return seek; - int cutTime = 0; + std::chrono::milliseconds cutTime = 0ms; for (const EDL::Edit& edit : m_vecEdits) { if (edit.action != Action::CUT) @@ -806,8 +826,8 @@ int CEdl::GetTimeWithoutCuts(int seek) const // inside cut if (seek >= edit.start && seek <= edit.end) { - // decrease cut lenght by 1 ms to jump over the end boundary. - cutTime += seek - edit.start - 1; + // decrease cut length by 1 ms to jump over the end boundary. + cutTime += seek - edit.start - 1ms; } // cut has already been passed over else if (seek >= edit.start) @@ -818,16 +838,16 @@ int CEdl::GetTimeWithoutCuts(int seek) const return seek - cutTime; } -double CEdl::GetTimeAfterRestoringCuts(double seek) const +std::chrono::milliseconds CEdl::GetTimeAfterRestoringCuts(std::chrono::milliseconds seek) const { if (!HasCuts()) return seek; for (const EDL::Edit& edit : m_vecEdits) { - double cutDuration = static_cast(edit.end - edit.start); + std::chrono::milliseconds cutDuration = edit.end - edit.start; // add 1 ms to jump over the start boundary - if (edit.action == Action::CUT && seek > edit.start + 1) + if (edit.action == Action::CUT && seek > edit.start + 1ms) { seek += cutDuration; } @@ -840,7 +860,7 @@ bool CEdl::HasSceneMarker() const return !m_vecSceneMarkers.empty(); } -std::optional> CEdl::InEdit(const int seekTime) +std::optional> CEdl::InEdit(std::chrono::milliseconds seekTime) { for (size_t i = 0; i < m_vecEdits.size(); ++i) { @@ -854,19 +874,19 @@ std::optional> CEdl::InEdit(const int seekTime) return std::nullopt; } -int CEdl::GetLastEditTime() const +std::optional CEdl::GetLastEditTime() const { return m_lastEditTime; } -void CEdl::SetLastEditTime(int editTime) +void CEdl::SetLastEditTime(std::chrono::milliseconds editTime) { m_lastEditTime = editTime; } void CEdl::ResetLastEditTime() { - m_lastEditTime = -1; + m_lastEditTime = std::nullopt; } void CEdl::SetLastEditActionType(EDL::Action action) @@ -879,15 +899,17 @@ EDL::Action CEdl::GetLastEditActionType() const return m_lastEditActionType; } -std::optional CEdl::GetNextSceneMarker(Direction direction, int clock) +std::optional CEdl::GetNextSceneMarker(Direction direction, + std::chrono::milliseconds clock) { if (!HasSceneMarker()) return std::nullopt; - std::optional sceneMarker; - const int seekTime = GetTimeAfterRestoringCuts(clock); + std::optional sceneMarker; + const std::chrono::milliseconds seekTime = GetTimeAfterRestoringCuts(clock); - int diff = 10 * 60 * 60 * 1000; // 10 hours to ms. + std::chrono::milliseconds diff = + std::chrono::milliseconds(10 * 60 * 60 * 1000); // 10 hours to ms. if (direction == Direction::FORWARD) // Find closest scene forwards { @@ -928,11 +950,11 @@ std::optional CEdl::GetNextSceneMarker(Direction direction, int clock) return sceneMarker; } -std::string CEdl::MillisecondsToTimeString(int milliSeconds) +std::string CEdl::MillisecondsToTimeString(std::chrono::milliseconds milliSeconds) { std::string strTimeString = StringUtils::SecondsToTimeString( - static_cast(milliSeconds / 1000), TIME_FORMAT_HH_MM_SS); // milliseconds to seconds - strTimeString += StringUtils::Format(".{:03}", milliSeconds % 1000); + std::chrono::duration_cast(milliSeconds).count(), TIME_FORMAT_HH_MM_SS); + strTimeString += StringUtils::Format(".{:03}", milliSeconds.count() % 1000); return strTimeString; } @@ -945,7 +967,7 @@ void CEdl::MergeShortCommBreaks() * the algorithms below. */ if (!m_vecEdits.empty() && m_vecEdits[0].action == Action::COMM_BREAK && - (m_vecEdits[0].end - m_vecEdits[0].start) < 5 * 1000) // 5 seconds + (m_vecEdits[0].end - m_vecEdits[0].start) < 5s) { CLog::Log(LOGDEBUG, "{} - Removing short commercial break at start [{} - {}]. <5 seconds", __FUNCTION__, MillisecondsToTimeString(m_vecEdits[0].start), @@ -961,9 +983,11 @@ void CEdl::MergeShortCommBreaks() if ((m_vecEdits[i].action == Action::COMM_BREAK && m_vecEdits[i + 1].action == Action::COMM_BREAK) && (m_vecEdits[i + 1].end - m_vecEdits[i].start < - advancedSettings->m_iEdlMaxCommBreakLength * 1000) // s to ms - && (m_vecEdits[i + 1].start - m_vecEdits[i].end < - advancedSettings->m_iEdlMaxCommBreakGap * 1000)) // s to ms + std::chrono::duration_cast( + std::chrono::seconds(advancedSettings->m_iEdlMaxCommBreakLength))) && + (m_vecEdits[i + 1].start - m_vecEdits[i].end < + std::chrono::duration_cast( + std::chrono::seconds(advancedSettings->m_iEdlMaxCommBreakGap)))) { Edit commBreak; commBreak.action = Action::COMM_BREAK; @@ -995,12 +1019,13 @@ void CEdl::MergeShortCommBreaks() * the maximum commercial break length being triggered. */ if (!m_vecEdits.empty() && m_vecEdits[0].action == Action::COMM_BREAK && - m_vecEdits[0].start < advancedSettings->m_iEdlMaxStartGap * 1000) + m_vecEdits[0].start < std::chrono::duration_cast( + std::chrono::seconds(advancedSettings->m_iEdlMaxStartGap))) { CLog::Log(LOGDEBUG, "{} - Expanding first commercial break back to start [{} - {}].", __FUNCTION__, MillisecondsToTimeString(m_vecEdits[0].start), MillisecondsToTimeString(m_vecEdits[0].end)); - m_vecEdits[0].start = 0; + m_vecEdits[0].start = 0ms; } /* @@ -1008,9 +1033,10 @@ void CEdl::MergeShortCommBreaks() */ for (size_t i = 0; i < m_vecEdits.size(); ++i) { - if (m_vecEdits[i].action == Action::COMM_BREAK && m_vecEdits[i].start > 0 && + if (m_vecEdits[i].action == Action::COMM_BREAK && m_vecEdits[i].start > 0ms && (m_vecEdits[i].end - m_vecEdits[i].start) < - advancedSettings->m_iEdlMinCommBreakLength * 1000) + std::chrono::duration_cast( + std::chrono::seconds(advancedSettings->m_iEdlMinCommBreakLength))) { CLog::Log(LOGDEBUG, "{} - Removing short commercial break [{} - {}]. Minimum length: {} seconds", @@ -1033,7 +1059,7 @@ void CEdl::AddSceneMarkersAtStartAndEndOfEdits() if (edit.action == Action::COMM_BREAK) { // Don't add a scene marker at the start. - if (edit.start > 0) + if (edit.start > 0ms) AddSceneMarker(edit.start); AddSceneMarker(edit.end); } diff --git a/xbmc/cores/VideoPlayer/Edl.h b/xbmc/cores/VideoPlayer/Edl.h index 01abf3a3db059..eaf5e295d1ece 100644 --- a/xbmc/cores/VideoPlayer/Edl.h +++ b/xbmc/cores/VideoPlayer/Edl.h @@ -45,7 +45,7 @@ class CEdl * because of EDL cuts * @return the total cut time */ - int GetTotalCutTime() const; + std::chrono::milliseconds GetTotalCutTime() const; /*! * @brief Providing a given seek time, return the actual time without @@ -55,7 +55,7 @@ class CEdl * @param seek the desired seek time * @return the seek time without considering EDL cut blocks */ - int GetTimeWithoutCuts(int seek) const; + std::chrono::milliseconds GetTimeWithoutCuts(std::chrono::milliseconds seekTime) const; /*! * @brief Provided a given seek time, return the time after correction with @@ -66,7 +66,7 @@ class CEdl * @return the seek time after applying the cut blocks already surpassed by the * provided seek time */ - double GetTimeAfterRestoringCuts(double seek) const; + std::chrono::milliseconds GetTimeAfterRestoringCuts(std::chrono::milliseconds seekTime) const; /*! * @brief Get the raw EDL edit list. @@ -90,7 +90,7 @@ class CEdl * has multiple cuts, the positions of subsquent cuts are automatically corrected by * substracting the previous cut durations. */ - const std::vector GetCutMarkers() const; + const std::vector GetCutMarkers() const; /*! * @brief Get the list of EDL scene markers. @@ -99,7 +99,7 @@ class CEdl * has multiple cuts, the positions of scene markers are automatically corrected by * substracting the surpassed cut durations until the scene marker point. */ - const std::vector GetSceneMarkers() const; + const std::vector GetSceneMarkers() const; /*! * @brief Check if for the provided seek time is contained within an EDL @@ -109,24 +109,24 @@ class CEdl * @param seekTime The seek time (on the original timeline) * @return a pointer to the edit struct if seekTime is within an edit, nullopt otherwise */ - std::optional> InEdit(int seekTime); + std::optional> InEdit(std::chrono::milliseconds seekTime); /*! * @brief Get the last processed edit time (set during playback when a given * edit is surpassed) - * @return The last processed edit time (ms) or -1 if not any + * @return The last processed edit time or nullopt if not any */ - int GetLastEditTime() const; + std::optional GetLastEditTime() const; /*! * @brief Set the last processed edit time (set during playback when a given * edit is surpassed) - * @param editTime The last processed EDL edit time (ms) + * @param editTime The last processed EDL edit time */ - void SetLastEditTime(int editTime); + void SetLastEditTime(std::chrono::milliseconds editTime); /*! - * @brief Reset the last recorded edit time (-1) + * @brief Reset the last recorded edit time (nullopt) */ void ResetLastEditTime(); @@ -149,20 +149,21 @@ class CEdl * @param clock the current position of the clock * @return the position of the scenemarker (nullopt if none) */ - std::optional GetNextSceneMarker(Direction direction, int clock); + std::optional GetNextSceneMarker(Direction direction, + std::chrono::milliseconds clockTime); - static std::string MillisecondsToTimeString(int milliSeconds); + static std::string MillisecondsToTimeString(std::chrono::milliseconds milliSeconds); private: // total cut time (edl cuts) in ms - int m_totalCutTime; + std::chrono::milliseconds m_totalCutTime; std::vector m_vecEdits; - std::vector m_vecSceneMarkers; + std::vector m_vecSceneMarkers; /*! * @brief Last processed EDL edit time (ms) */ - int m_lastEditTime; + std::optional m_lastEditTime; /*! * @brief Last processed EDL edit action type @@ -182,7 +183,7 @@ class CEdl */ bool AddEdit(const EDL::Edit& newEdit); - bool AddSceneMarker(int sceneMarker); + bool AddSceneMarker(std::chrono::milliseconds sceneMarker); void MergeShortCommBreaks(); diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index 77b15e6eebfb7..1deb7d2ba7dd5 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -65,6 +65,7 @@ #include "video/VideoInfoTag.h" #include "windowing/WinSystem.h" +#include #include #include #include @@ -1281,22 +1282,24 @@ void CVideoPlayer::Prepare() * if there was a start time specified as part of the "Start from where last stopped" (aka * auto-resume) feature or if there is an EDL cut or commercial break that starts at time 0. */ - int starttime = 0; + std::chrono::milliseconds starttime = 0ms; if (m_playerOptions.starttime > 0 || m_playerOptions.startpercent > 0) { if (m_playerOptions.startpercent > 0 && m_pDemuxer) { - int playerStartTime = static_cast((static_cast( - m_pDemuxer->GetStreamLength() * (m_playerOptions.startpercent / 100.0)))); + std::chrono::milliseconds playerStartTime = + std::chrono::milliseconds(static_cast((static_cast( + m_pDemuxer->GetStreamLength() * (m_playerOptions.startpercent / 100.0))))); starttime = m_Edl.GetTimeAfterRestoringCuts(playerStartTime); } else { - starttime = m_Edl.GetTimeAfterRestoringCuts( - static_cast(m_playerOptions.starttime * 1000)); // s to ms + starttime = + m_Edl.GetTimeAfterRestoringCuts(std::chrono::duration_cast( + std::chrono::seconds(static_cast(m_playerOptions.starttime)))); } CLog::Log(LOGDEBUG, "{} - Start position set to last stopped position: {}", __FUNCTION__, - starttime); + starttime.count()); } else { @@ -1312,7 +1315,7 @@ void CVideoPlayer::Prepare() { starttime = edit->end; CLog::Log(LOGDEBUG, "{} - Start position set to end of first cut: {}", __FUNCTION__, - starttime); + starttime.count()); } else if (edit->action == EDL::Action::COMM_BREAK) { @@ -1320,7 +1323,7 @@ void CVideoPlayer::Prepare() { starttime = edit->end; CLog::Log(LOGDEBUG, "{} - Start position set to end of first commercial break: {}", - __FUNCTION__, starttime); + __FUNCTION__, starttime.count()); } const std::shared_ptr advancedSettings = @@ -1328,37 +1331,39 @@ void CVideoPlayer::Prepare() if (advancedSettings && advancedSettings->m_EdlDisplayCommbreakNotifications) { const std::string timeString = - StringUtils::SecondsToTimeString(edit->end / 1000, TIME_FORMAT_MM_SS); + StringUtils::SecondsToTimeString(edit->end.count(), TIME_FORMAT_MM_SS); CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25011), timeString); } } } } - if (starttime > 0) + if (starttime > 0ms) { double startpts = DVD_NOPTS_VALUE; if (m_pDemuxer) { - if (m_pDemuxer->SeekTime(starttime, true, &startpts)) + if (m_pDemuxer->SeekTime(starttime.count(), true, &startpts)) { - FlushBuffers(starttime / 1000 * AV_TIME_BASE, true, true); - CLog::Log(LOGDEBUG, "{} - starting demuxer from: {}", __FUNCTION__, starttime); + FlushBuffers(starttime.count() / 1000 * AV_TIME_BASE, true, true); + CLog::Log(LOGDEBUG, "{} - starting demuxer from: {}", __FUNCTION__, starttime.count()); } else - CLog::Log(LOGDEBUG, "{} - failed to start demuxing from: {}", __FUNCTION__, starttime); + CLog::Log(LOGDEBUG, "{} - failed to start demuxing from: {}", __FUNCTION__, + starttime.count()); } if (m_pSubtitleDemuxer) { - if(m_pSubtitleDemuxer->SeekTime(starttime, true, &startpts)) - CLog::Log(LOGDEBUG, "{} - starting subtitle demuxer from: {}", __FUNCTION__, starttime); + if (m_pSubtitleDemuxer->SeekTime(starttime.count(), true, &startpts)) + CLog::Log(LOGDEBUG, "{} - starting subtitle demuxer from: {}", __FUNCTION__, + starttime.count()); else CLog::Log(LOGDEBUG, "{} - failed to start subtitle demuxing from: {}", __FUNCTION__, - starttime); + starttime.count()); } - m_clock.Discontinuity(DVD_MSEC_TO_TIME(starttime)); + m_clock.Discontinuity(DVD_MSEC_TO_TIME(starttime.count())); } UpdatePlayState(0); @@ -1712,7 +1717,8 @@ void CVideoPlayer::ProcessAudioData(CDemuxStream* pStream, DemuxPacket* pPacket) } else { - const auto hasEdit = m_Edl.InEdit(DVD_TIME_TO_MSEC(m_CurrentAudio.dts + m_offset_pts)); + const auto hasEdit = m_Edl.InEdit( + std::chrono::milliseconds(DVD_TIME_TO_MSEC(m_CurrentAudio.dts + m_offset_pts))); if (hasEdit && hasEdit.value()->action == EDL::Action::MUTE) drop = true; } @@ -2391,7 +2397,8 @@ bool CVideoPlayer::CheckSceneSkip(const CCurrentStream& current) if(current.inited == false) return false; - const auto hasEdit = m_Edl.InEdit(DVD_TIME_TO_MSEC(current.dts + m_offset_pts)); + const auto hasEdit = + m_Edl.InEdit(std::chrono::milliseconds(std::lround(current.dts + m_offset_pts))); return hasEdit && hasEdit.value()->action == EDL::Action::CUT; } @@ -2411,9 +2418,9 @@ void CVideoPlayer::CheckAutoSceneSkip() m_CurrentVideo.inited == false) return; - const int64_t clock = GetTime(); + const std::chrono::milliseconds clock{GetTime()}; - const double correctClock = m_Edl.GetTimeAfterRestoringCuts(clock); + const std::chrono::milliseconds correctClock = m_Edl.GetTimeAfterRestoringCuts(clock); const auto hasEdit = m_Edl.InEdit(correctClock); if (!hasEdit) { @@ -2429,19 +2436,19 @@ void CVideoPlayer::CheckAutoSceneSkip() const auto& edit = hasEdit.value(); if (edit->action == EDL::Action::CUT) { - if ((m_playSpeed > 0 && correctClock < (edit->start + 1000)) || - (m_playSpeed < 0 && correctClock < (edit->end - 1000))) + if ((m_playSpeed > 0 && correctClock < (edit->start + 1s)) || + (m_playSpeed < 0 && correctClock < (edit->end - 1s))) { CLog::Log(LOGDEBUG, "{} - Clock in EDL cut [{} - {}]: {}. Automatically skipping over.", __FUNCTION__, CEdl::MillisecondsToTimeString(edit->start), CEdl::MillisecondsToTimeString(edit->end), CEdl::MillisecondsToTimeString(clock)); // Seeking either goes to the start or the end of the cut depending on the play direction. - int seek = m_playSpeed >= 0 ? edit->end : edit->start; + std::chrono::milliseconds seek = m_playSpeed >= 0 ? edit->end : edit->start; if (m_Edl.GetLastEditTime() != seek) { CDVDMsgPlayerSeek::CMode mode; - mode.time = seek; + mode.time = seek.count(); mode.backward = true; mode.accurate = true; mode.restore = false; @@ -2457,14 +2464,15 @@ void CVideoPlayer::CheckAutoSceneSkip() else if (edit->action == EDL::Action::COMM_BREAK) { // marker for commbreak may be inaccurate. allow user to skip into break from the back - if (m_playSpeed >= 0 && m_Edl.GetLastEditTime() != edit->start && clock < edit->end - 1000) + if (m_playSpeed >= 0 && m_Edl.GetLastEditTime() != edit->start && clock < edit->end - 1s) { const std::shared_ptr advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); if (advancedSettings && advancedSettings->m_EdlDisplayCommbreakNotifications) { - const std::string timeString = - StringUtils::SecondsToTimeString((edit->end - edit->start) / 1000, TIME_FORMAT_MM_SS); + const std::string timeString = StringUtils::SecondsToTimeString( + std::chrono::duration_cast(edit->end - edit->start).count(), + TIME_FORMAT_MM_SS); CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25011), timeString); } @@ -2480,7 +2488,7 @@ void CVideoPlayer::CheckAutoSceneSkip() CEdl::MillisecondsToTimeString(edit->end), CEdl::MillisecondsToTimeString(clock)); CDVDMsgPlayerSeek::CMode mode; - mode.time = edit->end; + mode.time = edit->end.count(); mode.backward = true; mode.accurate = true; mode.restore = false; @@ -2700,7 +2708,10 @@ void CVideoPlayer::HandleMessages() if (msg.GetRelative()) time = (m_clock.GetClock() + m_State.time_offset) / 1000l + time; - time = msg.GetRestore() ? m_Edl.GetTimeAfterRestoringCuts(time) : time; + time = msg.GetRestore() + ? m_Edl.GetTimeAfterRestoringCuts(std::chrono::milliseconds(std::lround(time))) + .count() + : time; // if input stream doesn't support ISeekTime, convert back to pts //! @todo @@ -3268,19 +3279,19 @@ bool CVideoPlayer::SeekScene(Direction seekDirection) * There is a 5 second grace period applied when seeking for scenes backwards. If there is no * grace period applied it is impossible to go backwards past a scene marker. */ - int64_t clock = GetTime(); - if (seekDirection == Direction::BACKWARD && clock > 5 * 1000) // 5 seconds - clock -= 5 * 1000; + auto clock = std::chrono::milliseconds(GetTime()); + if (seekDirection == Direction::BACKWARD && clock > 5s) // 5 seconds + clock -= 5s; - const std::optional sceneMarker = - m_Edl.GetNextSceneMarker(seekDirection, static_cast(clock)); + const std::optional sceneMarker = + m_Edl.GetNextSceneMarker(seekDirection, clock); if (sceneMarker) { /* * Seeking is flushed and inaccurate, just like Seek() */ CDVDMsgPlayerSeek::CMode mode; - mode.time = sceneMarker.value(); + mode.time = sceneMarker.value().count(); mode.backward = seekDirection == Direction::BACKWARD; mode.accurate = false; mode.restore = false; @@ -4940,8 +4951,9 @@ void CVideoPlayer::UpdatePlayState(double timeout) if (m_Edl.HasCuts()) { - state.time = static_cast(m_Edl.GetTimeWithoutCuts(state.time)); - state.timeMax = state.timeMax - static_cast(m_Edl.GetTotalCutTime()); + state.time = static_cast( + m_Edl.GetTimeWithoutCuts(std::chrono::milliseconds(std::lround(state.time))).count()); + state.timeMax = state.timeMax - static_cast(m_Edl.GetTotalCutTime().count()); } if (m_caching > CACHESTATE_DONE && m_caching < CACHESTATE_PLAY) diff --git a/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp b/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp index a8072ca868a76..4d36958f87b4b 100644 --- a/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp +++ b/xbmc/cores/VideoPlayer/test/edl/TestEdl.cpp @@ -13,10 +13,12 @@ #include "settings/SettingsComponent.h" #include "test/TestUtils.h" +#include #include #include +using namespace std::chrono_literals; using namespace EDL; @@ -49,16 +51,16 @@ TEST_F(TestEdl, TestParsingMplayerTimeBasedEDL) // file has only 1 cut starting at 5.3 secs and ending at 7.1 secs // so total cut time should be 1.8 seconds (1800 msec) EXPECT_EQ(edl.HasCuts(), true); - EXPECT_EQ(edl.GetTotalCutTime(), 1.8 * 1000); + EXPECT_EQ(edl.GetTotalCutTime(), 1800ms); EXPECT_EQ(edl.GetCutMarkers().size(), 1); - EXPECT_EQ(edl.GetCutMarkers().at(0), 5.3 * 1000); // 5.3 secs + EXPECT_EQ(edl.GetCutMarkers().at(0), 5300ms); // When removing or restoring cuts, EDL adds (or removes) 1 msec to jump over the start or end boundary of the edit EXPECT_EQ(edl.GetTimeWithoutCuts(edl.GetRawEditList().at(0).start), - edl.GetRawEditList().at(0).start + 1); + edl.GetRawEditList().at(0).start + 1ms); EXPECT_EQ(edl.GetTimeAfterRestoringCuts(edl.GetRawEditList().at(0).start), edl.GetRawEditList().at(0).start); - EXPECT_EQ(edl.GetTimeAfterRestoringCuts(edl.GetRawEditList().at(0).start + 2), - edl.GetRawEditList().at(0).end + 2); + EXPECT_EQ(edl.GetTimeAfterRestoringCuts(edl.GetRawEditList().at(0).start + 2ms), + edl.GetRawEditList().at(0).end + 2ms); // the first edit (note editlist does not contain cuts) is a mute section starting at 15 seconds // of the real file this should correspond to 13.2 secs on the kodi VideoPlayer timeline (start - cuttime) @@ -75,32 +77,36 @@ TEST_F(TestEdl, TestParsingMplayerTimeBasedEDL) // scene markers // one of the scenemarkers (the first) have start and end times defined, kodi should assume the marker at the END position (255.3 secs) - EXPECT_EQ(edl.GetSceneMarkers().at(0), edl.GetTimeWithoutCuts(255.3 * 1000)); + EXPECT_EQ(edl.GetSceneMarkers().at(0), + edl.GetTimeWithoutCuts(duration_cast(255.3s))); // one of them only has start defined, at 720.1 secs - EXPECT_EQ(edl.GetSceneMarkers().at(1), edl.GetTimeWithoutCuts(720.1 * 1000)); + EXPECT_EQ(edl.GetSceneMarkers().at(1), + edl.GetTimeWithoutCuts(duration_cast(720.1s))); // commbreaks // the second edit on the file is a commbreak const auto commbreak = edl.GetEditList().at(1); EXPECT_EQ(commbreak.action, Action::COMM_BREAK); // We should have a scenemarker at the commbreak start and another on commbreak end - std::optional time = edl.GetNextSceneMarker(Direction::FORWARD, commbreak.start - 1); + std::optional time = + edl.GetNextSceneMarker(Direction::FORWARD, commbreak.start - 1ms); // lets cycle to the next scenemarker if starting from 1 msec before the start (or end) of the commbreak EXPECT_NE(time, std::nullopt); EXPECT_EQ(edl.GetTimeWithoutCuts(time.value()), commbreak.start); - time = edl.GetNextSceneMarker(Direction::FORWARD, commbreak.end - 1); + time = edl.GetNextSceneMarker(Direction::FORWARD, commbreak.end - 1ms); EXPECT_NE(time, std::nullopt); EXPECT_EQ(edl.GetTimeWithoutCuts(time.value()), commbreak.end); // same if we cycle backwards - time = edl.GetNextSceneMarker(Direction::BACKWARD, commbreak.start + 1); + time = edl.GetNextSceneMarker(Direction::BACKWARD, commbreak.start + 1ms); EXPECT_NE(time, std::nullopt); EXPECT_EQ(edl.GetTimeWithoutCuts(time.value()), commbreak.start); - time = edl.GetNextSceneMarker(Direction::BACKWARD, commbreak.end + 1); + time = edl.GetNextSceneMarker(Direction::BACKWARD, commbreak.end + 1ms); EXPECT_NE(time, std::nullopt); EXPECT_EQ(edl.GetTimeWithoutCuts(time.value()), commbreak.end); // We should be in an edit if we are in the middle of a commbreak... // lets check and confirm the edits match (after restoring cuts) - const int middleOfCommbreak = commbreak.start + (commbreak.end - commbreak.start) / 2; + const std::chrono::milliseconds middleOfCommbreak = + commbreak.start + (commbreak.end - commbreak.start) / 2; const auto hasEdit = edl.InEdit(edl.GetTimeWithoutCuts(middleOfCommbreak)); EXPECT_NE(hasEdit, std::nullopt); const auto& edit = hasEdit.value(); @@ -126,9 +132,9 @@ TEST_F(TestEdl, TestParsingMplayerTimeBasedInterleavedCutsEDL) EXPECT_EQ(edl.HasEdits(), true); EXPECT_EQ(edl.GetCutMarkers().size(), 2); // lets check the total cut time matches the sum of the two cut durations defined in the file - EXPECT_EQ(edl.GetTotalCutTime(), ((7.1 - 5.3) + (19 - 18)) * 1000); + EXPECT_EQ(edl.GetTotalCutTime(), 2800ms); // (7.1s - 5.3s) + (19s - 18s) // the first edit is after the first cut, so lets check the start time was adjusted exactly by the cut duration - EXPECT_EQ(edl.GetEditList().at(0).start, edl.GetRawEditList().at(1).start - ((7.1 - 5.3) * 1000)); + EXPECT_EQ(edl.GetEditList().at(0).start, edl.GetRawEditList().at(1).start - (7.1s - 5.3s)); EXPECT_EQ(edl.GetEditList().at(0).start, edl.GetTimeWithoutCuts(edl.GetRawEditList().at(1).start)); EXPECT_EQ(edl.GetEditList().at(1).start, @@ -157,9 +163,13 @@ TEST_F(TestEdl, TestParsingMplayerFrameBasedEDL) EXPECT_EQ(edl.GetEditList().size(), 2); // check edit times are correctly calculated provided the fps EXPECT_EQ(edl.GetEditList().at(0).start, - static_cast((360 / fps) * 1000) - edl.GetTotalCutTime()); + std::chrono::duration_cast( + std::chrono::duration>{360 / fps}) - + edl.GetTotalCutTime()); EXPECT_EQ(edl.GetSceneMarkers().at(0), - static_cast((6127 / fps) * 1000) - edl.GetTotalCutTime()); + std::chrono::duration_cast( + std::chrono::duration>{6127 / fps}) - + edl.GetTotalCutTime()); } TEST_F(TestEdl, TestParsingMplayerTimeBasedMixedEDL) @@ -182,7 +192,7 @@ TEST_F(TestEdl, TestParsingMplayerTimeBasedMixedEDL) // check we have correctly parsed the scene with 12:00.1 start point for (const auto& scene : edl.GetSceneMarkers()) { - if (scene == (12 * 60 + 0.1) * 1000 - edl.GetTotalCutTime()) + if (scene == (12 * 60s + 0.1s) - edl.GetTotalCutTime()) { sceneFound = true; break; @@ -190,7 +200,7 @@ TEST_F(TestEdl, TestParsingMplayerTimeBasedMixedEDL) } EXPECT_EQ(sceneFound, true); // check that the first ordered edit starts at 15 secs - EXPECT_EQ(edl.GetEditList().front().start, (15 * 1000) - edl.GetTotalCutTime()); + EXPECT_EQ(edl.GetEditList().front().start, 15s - edl.GetTotalCutTime()); } TEST_F(TestEdl, TestParsingVideoRedoEDL) @@ -213,7 +223,7 @@ TEST_F(TestEdl, TestParsingVideoRedoEDL) EXPECT_EQ(edl.GetCutMarkers().size(), 3); // in videoredo time processing is ms * 10000 // first cut in the file is at 4235230000 - let's confirm this corresponds to second 423.523 - EXPECT_EQ(edl.GetCutMarkers().front(), 423.523 * 1000); + EXPECT_EQ(edl.GetCutMarkers().front(), 423.523s); } TEST_F(TestEdl, TestSnapStreamEDL) @@ -233,8 +243,8 @@ TEST_F(TestEdl, TestSnapStreamEDL) EXPECT_EQ(edl.GetSceneMarkers().size(), 3 * 2); // start and end of each commbreak // snapstream beyond tv uses ms * 10000 // check if first commbreak (4235230000 - 5936600000) is 423.523 sec - 593.660 sec - EXPECT_EQ(edl.GetEditList().front().start, std::lround(423.523 * 1000)); - EXPECT_EQ(edl.GetEditList().front().end, std::lround(593.660 * 1000)); + EXPECT_EQ(edl.GetEditList().front().start, 423.523s); + EXPECT_EQ(edl.GetEditList().front().end, 593.660s); } TEST_F(TestEdl, TestComSkipVersion1EDL) @@ -257,8 +267,12 @@ TEST_F(TestEdl, TestComSkipVersion1EDL) EXPECT_EQ(edl.GetCutMarkers().empty(), true); EXPECT_EQ(edl.GetEditList().size(), 3); EXPECT_EQ(edl.GetSceneMarkers().size(), 3 * 2); // start and end of each commbreak - EXPECT_EQ(edl.GetEditList().front().start, std::lround(12693 / fps * 1000)); - EXPECT_EQ(edl.GetEditList().front().end, std::lround(17792 / fps * 1000)); + EXPECT_EQ(edl.GetEditList().front().start, + std::chrono::duration_cast( + std::chrono::duration>{12693 / fps})); + EXPECT_EQ(edl.GetEditList().front().end, + std::chrono::duration_cast( + std::chrono::duration>{17792 / fps})); } TEST_F(TestEdl, TestComSkipVersion2EDL) @@ -274,23 +288,27 @@ TEST_F(TestEdl, TestComSkipVersion2EDL) EXPECT_EQ(found, true); // fps is obtained from the file as it always takes precedence (note we supplied 0 above), // the EDL file has the value of 2500 for fps. kodi converts this to 25 fps by dividing by a factor of 100 - const float fpsInEdlFile = 2500 / 100; + const double fpsInEdlFile = 2500 / 100; // this format only supports commbreak types EXPECT_EQ(edl.HasEdits(), true); EXPECT_EQ(edl.GetCutMarkers().empty(), true); EXPECT_EQ(edl.GetEditList().size(), 3); EXPECT_EQ(edl.GetSceneMarkers().size(), 3 * 2); // start and end of each commbreak - EXPECT_EQ(edl.GetEditList().front().start, std::lround(12693 / fpsInEdlFile * 1000)); - EXPECT_EQ(edl.GetEditList().front().end, std::lround(17792 / fpsInEdlFile * 1000)); + EXPECT_EQ(edl.GetEditList().front().start, + std::chrono::duration_cast( + std::chrono::duration>{12693 / fpsInEdlFile})); + EXPECT_EQ(edl.GetEditList().front().end, + std::chrono::duration_cast( + std::chrono::duration>{17792 / fpsInEdlFile})); } TEST_F(TestEdl, TestRuntimeSetEDL) { // this is a simple test for SetLastEditTime, SetLastEditActionType and corresponding getters CEdl edl; - edl.SetLastEditTime(1000); + edl.SetLastEditTime(1000ms); edl.SetLastEditActionType(Action::COMM_BREAK); - EXPECT_EQ(edl.GetLastEditTime(), 1000); + EXPECT_EQ(edl.GetLastEditTime(), 1000ms); EXPECT_EQ(edl.GetLastEditActionType(), Action::COMM_BREAK); } @@ -325,17 +343,17 @@ TEST_F(TestEdl, TestCommBreakAdvancedSettings) EXPECT_EQ(found, true); // confirm the start and end times of all the commbreaks match EXPECT_EQ(edl.GetEditList().size(), 5); - EXPECT_EQ(edl.GetEditList().at(0).start, 10 * 1000); - EXPECT_EQ(edl.GetEditList().at(0).end, 22 * 1000); - EXPECT_EQ(edl.GetEditList().at(1).start, 30 * 1000); - EXPECT_EQ(edl.GetEditList().at(1).end, 32 * 1000); - EXPECT_EQ(edl.GetEditList().at(2).start, 37 * 1000); - EXPECT_EQ(edl.GetEditList().at(2).end, 50 * 1000); - EXPECT_EQ(edl.GetEditList().at(3).start, 52 * 1000); - EXPECT_EQ(edl.GetEditList().at(3).end, 60 * 1000); - EXPECT_EQ(edl.GetEditList().at(4).start, 62 * 1000); - EXPECT_EQ(edl.GetEditList().at(4).end, std::lround(65.1 * 1000)); - // now lets change autowait and autowind and check the edits are correcly adjusted + EXPECT_EQ(edl.GetEditList().at(0).start, 10s); + EXPECT_EQ(edl.GetEditList().at(0).end, 22s); + EXPECT_EQ(edl.GetEditList().at(1).start, 30s); + EXPECT_EQ(edl.GetEditList().at(1).end, 32s); + EXPECT_EQ(edl.GetEditList().at(2).start, 37s); + EXPECT_EQ(edl.GetEditList().at(2).end, 50s); + EXPECT_EQ(edl.GetEditList().at(3).start, 52s); + EXPECT_EQ(edl.GetEditList().at(3).end, 60s); + EXPECT_EQ(edl.GetEditList().at(4).start, 62s); + EXPECT_EQ(edl.GetEditList().at(4).end, 65100ms); + // now lets change autowait and autowind and check the edits are correctly adjusted edl.Clear(); advancedSettings->m_iEdlCommBreakAutowait = 3; // secs advancedSettings->m_iEdlCommBreakAutowind = 3; // secs @@ -345,23 +363,23 @@ TEST_F(TestEdl, TestCommBreakAdvancedSettings) EXPECT_EQ(edl.GetEditList().size(), 5); // the second edit has a duration smaller than the autowait // this moves the start time to the end of the edit - EXPECT_EQ(edl.GetEditList().at(1).start, 32 * 1000); + EXPECT_EQ(edl.GetEditList().at(1).start, 32s); EXPECT_EQ(edl.GetEditList().at(1).end, edl.GetEditList().at(1).start); // the others should be adjusted + 3 secs at the start and -3 secs at the end // due to the provided values for autowait and autowind. - EXPECT_EQ(edl.GetEditList().at(0).start, (10 + 3) * 1000); - EXPECT_EQ(edl.GetEditList().at(0).end, (22 - 3) * 1000); - EXPECT_EQ(edl.GetEditList().at(2).start, (37 + 3) * 1000); - EXPECT_EQ(edl.GetEditList().at(2).end, (50 - 3) * 1000); - EXPECT_EQ(edl.GetEditList().at(3).start, (52 + 3) * 1000); - EXPECT_EQ(edl.GetEditList().at(3).end, (60 - 3) * 1000); + EXPECT_EQ(edl.GetEditList().at(0).start, 10s + 3s); + EXPECT_EQ(edl.GetEditList().at(0).end, 22s - 3s); + EXPECT_EQ(edl.GetEditList().at(2).start, 37s + 3s); + EXPECT_EQ(edl.GetEditList().at(2).end, 50s - 3s); + EXPECT_EQ(edl.GetEditList().at(3).start, 52s + 3s); + EXPECT_EQ(edl.GetEditList().at(3).end, 60s - 3s); // since we adjust the start to second 65 and the autowind is 3 seconds kodi should // shift the end time not by 3 seconds but by the "excess" time (in this case 0.1 sec) // this means start and end will be exactly the same. The commbreak would be removed if // mergeshortcommbreaks was active and advancedsetting m_iEdlMinCommBreakLength // was set to a reasonable threshold. - EXPECT_EQ(edl.GetEditList().at(4).start, (62 + 3) * 1000); - EXPECT_EQ(edl.GetEditList().at(4).end, (65.1 - 0.1) * 1000); + EXPECT_EQ(edl.GetEditList().at(4).start, 62s + 3s); + EXPECT_EQ(edl.GetEditList().at(4).end, 65.1s - 0.1s); EXPECT_EQ(edl.GetEditList().at(4).start, edl.GetEditList().at(4).end); } @@ -425,8 +443,8 @@ TEST_F(TestEdl, TestMergeSmallCommbreaks) // kodi should merge all commbreaks into a single one starting at the first point (0) // and ending at the last edit time EXPECT_EQ(edl.GetEditList().size(), 1); - EXPECT_EQ(edl.GetEditList().at(0).start, 0); - EXPECT_EQ(edl.GetEditList().at(0).end, std::lround(65.1 * 1000)); + EXPECT_EQ(edl.GetEditList().at(0).start, 0ms); + EXPECT_EQ(edl.GetEditList().at(0).end, 65100ms); } TEST_F(TestEdl, TestMergeSmallCommbreaksAdvanced) @@ -458,8 +476,7 @@ TEST_F(TestEdl, TestMergeSmallCommbreaksAdvanced) // kodi should merge all commbreaks into two EXPECT_EQ(edl.GetEditList().size(), 2); // second edit of the original file + third one - EXPECT_EQ(edl.GetEditList().at(0).end - edl.GetEditList().at(0).start, (32 - 10) * 1000); + EXPECT_EQ(edl.GetEditList().at(0).end - edl.GetEditList().at(0).start, 32s - 10s); // 4th, 5th and 6th commbreaks joined - EXPECT_EQ(edl.GetEditList().at(1).end - edl.GetEditList().at(1).start, - std::lround((65.1 - 37) * 1000)); + EXPECT_EQ(edl.GetEditList().at(1).end - edl.GetEditList().at(1).start, 65100ms - 37s); } diff --git a/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp b/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp index 62e620dc41e0d..790264c21fde2 100644 --- a/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp @@ -32,6 +32,7 @@ #include "utils/log.h" #include +#include #include #include @@ -672,8 +673,8 @@ std::vector> CPlayerGUIInfo::GetEditList(const CDataCach const std::vector& edits = data.GetEditList(); for (const auto& edit : edits) { - float editStart = edit.start * 100.0f / duration; - float editEnd = edit.end * 100.0f / duration; + float editStart = edit.start.count() * 100.0f / duration; + float editEnd = edit.end.count() * 100.0f / duration; ranges.emplace_back(editStart, editEnd); } return ranges; @@ -684,11 +685,11 @@ std::vector> CPlayerGUIInfo::GetCuts(const CDataCacheCor { std::vector> ranges; - const std::vector& cuts = data.GetCuts(); + const std::vector& cuts = data.GetCuts(); float lastMarker = 0.0f; for (const auto& cut : cuts) { - float marker = cut * 100.0f / duration; + float marker = cut.count() * 100.0f / duration; if (marker != 0) ranges.emplace_back(lastMarker, marker); @@ -702,11 +703,11 @@ std::vector> CPlayerGUIInfo::GetSceneMarkers(const CData { std::vector> ranges; - const std::vector& scenes = data.GetSceneMarkers(); + const std::vector& scenes = data.GetSceneMarkers(); float lastMarker = 0.0f; for (const auto& scene : scenes) { - float marker = scene * 100.0f / duration; + float marker = scene.count() * 100.0f / duration; if (marker != 0) ranges.emplace_back(lastMarker, marker); diff --git a/xbmc/pvr/PVREdl.cpp b/xbmc/pvr/PVREdl.cpp index 72a3ee46fd18b..aa47cbed27f6f 100644 --- a/xbmc/pvr/PVREdl.cpp +++ b/xbmc/pvr/PVREdl.cpp @@ -15,6 +15,8 @@ #include "pvr/recordings/PVRRecording.h" #include "utils/log.h" +#include + namespace PVR { @@ -38,8 +40,8 @@ std::vector CPVREdl::GetEdits(const CFileItem& item) for (const auto& entry : edl) { EDL::Edit edit; - edit.start = entry.start; - edit.end = entry.end; + edit.start = std::chrono::milliseconds(entry.start); + edit.end = std::chrono::milliseconds(entry.end); switch (entry.type) { From 75d2d7cc739761079706ab020757d452e6c8d5f8 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Mon, 8 Jul 2024 11:20:18 +0100 Subject: [PATCH 259/651] [GUIWindowSlideShow] Close dialog if a video starts playing --- xbmc/pictures/GUIWindowSlideShow.cpp | 18 ++++++++++++++++++ xbmc/pictures/GUIWindowSlideShow.h | 11 ++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/xbmc/pictures/GUIWindowSlideShow.cpp b/xbmc/pictures/GUIWindowSlideShow.cpp index 6dc76a5c9a014..fbe77a374fdc9 100644 --- a/xbmc/pictures/GUIWindowSlideShow.cpp +++ b/xbmc/pictures/GUIWindowSlideShow.cpp @@ -152,12 +152,30 @@ CGUIWindowSlideShow::CGUIWindowSlideShow(void) m_loadType = KEEP_IN_MEMORY; m_bLoadNextPic = false; CServiceBroker::GetSlideShowDelegator().SetDelegate(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); Reset(); } CGUIWindowSlideShow::~CGUIWindowSlideShow() { CServiceBroker::GetSlideShowDelegator().ResetDelegate(); + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); +} + +void CGUIWindowSlideShow::Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) +{ + if (flag & ANNOUNCEMENT::Player) + { + if (message == "OnPlay" || message == "OnResume") + { + if (data.isMember("player") && data["player"].isMember("playerid") && + data["player"]["playerid"] == static_cast(PLAYLIST::Id::TYPE_VIDEO)) + Close(); + } + } } void CGUIWindowSlideShow::AnnouncePlayerPlay(const CFileItemPtr& item) diff --git a/xbmc/pictures/GUIWindowSlideShow.h b/xbmc/pictures/GUIWindowSlideShow.h index 0033a39667e7e..5c6e82268a137 100644 --- a/xbmc/pictures/GUIWindowSlideShow.h +++ b/xbmc/pictures/GUIWindowSlideShow.h @@ -10,6 +10,7 @@ #include "SlideShowPicture.h" #include "guilib/GUIDialog.h" +#include "interfaces/IAnnouncer.h" #include "interfaces/ISlideShowDelegate.h" #include "threads/Event.h" #include "threads/Thread.h" @@ -48,7 +49,9 @@ class CBackgroundPicLoader : public CThread CGUIWindowSlideShow* m_pCallback = nullptr; }; -class CGUIWindowSlideShow : public CGUIDialog, public ISlideShowDelegate +class CGUIWindowSlideShow : public CGUIDialog, + public ISlideShowDelegate, + public ANNOUNCEMENT::IAnnouncer { public: CGUIWindowSlideShow(void); @@ -87,6 +90,12 @@ class CGUIWindowSlideShow : public CGUIDialog, public ISlideShowDelegate void Shuffle() override; int GetDirection() const override { return m_iDirection; } + // implementation of IAnnouncer + void Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) override; + bool OnMessage(CGUIMessage& message) override; EVENT_RESULT OnMouseEvent(const CPoint& point, const KODI::MOUSE::CMouseEvent& event) override; bool OnAction(const CAction& action) override; From 55a165148c647fc595f993c03013706e1f96813a Mon Sep 17 00:00:00 2001 From: sarbes Date: Mon, 8 Jul 2024 18:00:39 +0200 Subject: [PATCH 260/651] Estuary: Improve background textures --- .../extras/backgrounds/pattern0.jpg | Bin 3046 -> 0 bytes .../extras/backgrounds/pattern1.jpg | Bin 93811 -> 0 bytes .../extras/backgrounds/pattern1.png | Bin 0 -> 26325 bytes .../extras/backgrounds/pattern2.jpg | Bin 120664 -> 0 bytes .../extras/backgrounds/pattern2.png | Bin 0 -> 37924 bytes .../extras/backgrounds/pattern3.jpg | Bin 156457 -> 0 bytes .../extras/backgrounds/pattern3.png | Bin 0 -> 86557 bytes .../extras/backgrounds/pattern4.jpg | Bin 96323 -> 0 bytes .../extras/backgrounds/pattern4.png | Bin 0 -> 22602 bytes .../extras/backgrounds/pattern5.jpg | Bin 218734 -> 0 bytes .../extras/backgrounds/pattern5.png | Bin 0 -> 88539 bytes .../extras/backgrounds/pattern6.jpg | Bin 53634 -> 0 bytes .../extras/backgrounds/pattern6.png | Bin 0 -> 79518 bytes .../extras/backgrounds/pattern7.jpg | Bin 157156 -> 0 bytes .../extras/backgrounds/pattern7.png | Bin 0 -> 19791 bytes addons/skin.estuary/xml/Includes.xml | 3 ++- 16 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 addons/skin.estuary/extras/backgrounds/pattern0.jpg delete mode 100644 addons/skin.estuary/extras/backgrounds/pattern1.jpg create mode 100644 addons/skin.estuary/extras/backgrounds/pattern1.png delete mode 100644 addons/skin.estuary/extras/backgrounds/pattern2.jpg create mode 100644 addons/skin.estuary/extras/backgrounds/pattern2.png delete mode 100644 addons/skin.estuary/extras/backgrounds/pattern3.jpg create mode 100644 addons/skin.estuary/extras/backgrounds/pattern3.png delete mode 100644 addons/skin.estuary/extras/backgrounds/pattern4.jpg create mode 100644 addons/skin.estuary/extras/backgrounds/pattern4.png delete mode 100644 addons/skin.estuary/extras/backgrounds/pattern5.jpg create mode 100644 addons/skin.estuary/extras/backgrounds/pattern5.png delete mode 100644 addons/skin.estuary/extras/backgrounds/pattern6.jpg create mode 100644 addons/skin.estuary/extras/backgrounds/pattern6.png delete mode 100644 addons/skin.estuary/extras/backgrounds/pattern7.jpg create mode 100644 addons/skin.estuary/extras/backgrounds/pattern7.png diff --git a/addons/skin.estuary/extras/backgrounds/pattern0.jpg b/addons/skin.estuary/extras/backgrounds/pattern0.jpg deleted file mode 100644 index e5ae3ffe4cd26bbe7f88b82a917cbaeee07f9c4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3046 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%0Dj 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 literal 0 HcmV?d00001 diff --git a/addons/skin.estuary/extras/backgrounds/pattern2.jpg b/addons/skin.estuary/extras/backgrounds/pattern2.jpg deleted file mode 100644 index 93f0451dc08385f444f24387d3e039cdd3db92d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120664 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 z 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 z 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 V`5NJMOl|F7NLmkIGRgh@@_(iptnvT= diff --git a/addons/skin.estuary/extras/backgrounds/pattern2.png b/addons/skin.estuary/extras/backgrounds/pattern2.png new file mode 100644 index 0000000000000000000000000000000000000000..c6af819c11b0db6f49e1a50d680b199fa1552eef GIT binary patch literal 37924 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%0AIm&ViPKIu6oL8re7#=lD!_>11r36q%c504 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 literal 0 HcmV?d00001 diff --git a/addons/skin.estuary/extras/backgrounds/pattern3.jpg b/addons/skin.estuary/extras/backgrounds/pattern3.jpg deleted file mode 100644 index 7c4fc2374a8f884c2d43e46f48bc90fa83321e41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156457 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/ytxY)FJ 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 M07*qoM6N<$f_V8*)c^nh literal 0 HcmV?d00001 diff --git a/addons/skin.estuary/extras/backgrounds/pattern4.jpg b/addons/skin.estuary/extras/backgrounds/pattern4.jpg deleted file mode 100644 index db93147380bb150a4f0d2d69abf2a091b7923b5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96323 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

Ya8@|B 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

- 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 z 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 KO2^>;2L1;-v!av$ diff --git a/addons/skin.estuary/extras/backgrounds/pattern4.png b/addons/skin.estuary/extras/backgrounds/pattern4.png new file mode 100644 index 0000000000000000000000000000000000000000..3d2737144c6e903e232d3bd6b7b2056477628ade GIT binary patch literal 22602 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

xGQHB=sTygQG9GaFgd`jHsKq%4JmWFB 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 jb{oKwXuF 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 diff --git a/addons/skin.estuary/extras/backgrounds/pattern5.png b/addons/skin.estuary/extras/backgrounds/pattern5.png new file mode 100644 index 0000000000000000000000000000000000000000..487e59790425dcf1cffc700b33411ee948137525 GIT binary patch literal 88539 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

b}D}sY3JEZ~bD;!5xJVtO>OwJtF)+u4{ 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�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 KMNUMnLSTYx#jA_} literal 0 HcmV?d00001 diff --git a/addons/skin.estuary/extras/backgrounds/pattern6.jpg b/addons/skin.estuary/extras/backgrounds/pattern6.jpg deleted file mode 100644 index 2beb9170ef6746c8e56c735ee4cce726ac17e4c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53634 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 literal 0 HcmV?d00001 diff --git a/addons/skin.estuary/extras/backgrounds/pattern7.jpg b/addons/skin.estuary/extras/backgrounds/pattern7.jpg deleted file mode 100644 index 66c2b59331b30ff1065421059389ecd681559655..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157156 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˰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 z⪻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 Z>fI_L)MaH7kjK2RtD*nn@WE3bf2+U# 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 I2g?Wk53Hz_vj6}9 diff --git a/addons/skin.estuary/extras/backgrounds/pattern7.png b/addons/skin.estuary/extras/backgrounds/pattern7.png new file mode 100644 index 0000000000000000000000000000000000000000..6204b3a8f35084e7a9057f6b0dfd4e43c9eb843c GIT binary patch literal 19791 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 literal 0 HcmV?d00001 diff --git a/addons/skin.estuary/xml/Includes.xml b/addons/skin.estuary/xml/Includes.xml index f43390f640524..e410c699c44c8 100644 --- a/addons/skin.estuary/xml/Includes.xml +++ b/addons/skin.estuary/xml/Includes.xml @@ -1242,7 +1242,8 @@ DepthBackground FullScreenDimensions scale - $INFO[Skin.String(background_overlay),special://skin/extras/backgrounds/pattern,.jpg] + $INFO[Skin.String(background_overlay),special://skin/extras/backgrounds/pattern,.png] + !String.IsEqual(Skin.String(background_overlay),0) From 2d20cbd1828adb67c1a2bc263aeeafd2d8b834ed Mon Sep 17 00:00:00 2001 From: CastagnaIT Date: Tue, 9 Jul 2024 09:49:46 +0200 Subject: [PATCH 261/651] [addons][filesystem][docs] Add verifypeer to ADDON_CURL_OPTION_OPTION --- xbmc/addons/kodi-dev-kit/include/kodi/c-api/filesystem.h | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/filesystem.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/filesystem.h index aedcf6434e378..41810ca93c616 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/filesystem.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/filesystem.h @@ -128,6 +128,7 @@ extern "C" /// | `user-agent` | Set the "user-agent" header /// | `seekable` | Set the stream seekable. 1: enable, 0: disable /// | `sslcipherlist` | Set list of accepted SSL ciphers. + /// | `verifypeer` | Set to false if curl must skip checking the authenticity of the SSL CA certificate. /// ADDON_CURL_OPTION_PROTOCOL, From 3f5848989c460f36445033306e0a24cedc3ce4bc Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 8 Jul 2024 20:30:58 +1000 Subject: [PATCH 262/651] [cmake] create function to add dependency to a target A target may not always use target_link_libraries to create dependency chains. export-files is an example, and we want to make sure it has a dependency on all libs to be acquired/built before trying to execute the export-files script --- CMakeLists.txt | 2 ++ cmake/scripts/common/Macros.cmake | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f6ba1e9097482..0ef3151592bf1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -509,6 +509,8 @@ add_custom_target(gen_skin_pack DEPENDS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${ add_custom_target(generate-packaging ALL DEPENDS TexturePacker::TexturePacker::Executable export-files gen_skin_pack gen_system_addons) +core_target_add_dependencies(export-files) + # Add to lib${APP_NAME_LC} solely for Win UWP. msix building doesnt seem to pick up the # generated buildtree if we do it later. Other platforms dont care when this happens. add_dependencies(lib${APP_NAME_LC} generate-packaging) diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index e3ae5ed9b17cf..987301cb22dae 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -797,3 +797,21 @@ function(core_target_link_libraries core_lib) endif() endforeach() endfunction() + +# Iterate over optional/required dep lists and create dependency +# to the target supplied as first argument +function(core_target_add_dependencies core_target) + foreach(_depspec ${required_deps}) + split_dependency_specification(${_depspec} dep version) + if(TARGET ${APP_NAME_LC}::${dep}) + add_dependencies(${core_target} ${APP_NAME_LC}::${dep}) + endif() + endforeach() + + foreach(_depspec ${optional_deps}) + split_dependency_specification(${_depspec} dep version) + if(TARGET ${APP_NAME_LC}::${dep}) + add_dependencies(${core_target} ${APP_NAME_LC}::${dep}) + endif() + endforeach() +endfunction() From 3311a4874c08cadf80ef476c8e8324750e62b756 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 8 Jul 2024 20:33:00 +1000 Subject: [PATCH 263/651] [cmake][modules] FindLibDvd add explicit dependency to libdvdnav target --- cmake/modules/FindLibDvd.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/modules/FindLibDvd.cmake b/cmake/modules/FindLibDvd.cmake index 25277b587867d..48947165d6711 100644 --- a/cmake/modules/FindLibDvd.cmake +++ b/cmake/modules/FindLibDvd.cmake @@ -20,3 +20,4 @@ endif() add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE IMPORTED) set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES INTERFACE_LINK_LIBRARIES "LibDvdNav::LibDvdNav") +add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} LibDvdNav::LibDvdNav) From cd09120d2801f1f5bb5d67d7874503a35a636fbb Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 9 Jul 2024 16:43:40 +1000 Subject: [PATCH 264/651] [cmake][modules] Rework libdvd* modules Streamline, remove some redundancies, actually be a shared library target --- cmake/modules/FindLibDvd.cmake | 41 ++++++++------- cmake/modules/FindLibDvdCSS.cmake | 45 +++++++---------- cmake/modules/FindLibDvdNav.cmake | 49 +++++++----------- cmake/modules/FindLibDvdRead.cmake | 50 ++++++++----------- .../target/libdvdnav/LIBDVDNAV-VERSION | 2 +- 5 files changed, 79 insertions(+), 108 deletions(-) diff --git a/cmake/modules/FindLibDvd.cmake b/cmake/modules/FindLibDvd.cmake index 48947165d6711..dce52d9bcac5b 100644 --- a/cmake/modules/FindLibDvd.cmake +++ b/cmake/modules/FindLibDvd.cmake @@ -1,23 +1,26 @@ +#.rst: +# FindLibDvd +# ---------- +# +# This will define the following target: +# +# ${APP_NAME_LC}::Dvd - Wrapper target to generate/build libdvdnav shared library -# Check for existing LIBDVDREAD. -# Suppress mismatch warning, see https://cmake.org/cmake/help/latest/module/FindPackageHandleStandardArgs.html -set(FPHSA_NAME_MISMATCHED 1) -find_package(LibDvdNav MODULE REQUIRED) -unset(FPHSA_NAME_MISMATCHED) +if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + find_package(LibDvdNav MODULE REQUIRED) -set(_dvdlibs LibDvdNav::LibDvdNav - $<$:LibDvdCSS::LibDvdCSS>>) + set(_dvdlibs LibDvdNav::LibDvdNav + $<$:LibDvdCSS::LibDvdCSS>>) -if(NOT CORE_SYSTEM_NAME MATCHES windows) - # link a shared dvdnav library that includes the whole archives of dvdread and dvdcss as well - # the quotes around _dvdlibs are on purpose, since we want to pass a list to the function that will be unpacked automatically - core_link_library(LibDvdNav::LibDvdNav system/players/VideoPlayer/libdvdnav libdvdnav archives "${_dvdlibs}") -else() - set(LIBDVD_TARGET_DIR .) - copy_file_to_buildtree(${DEPENDS_PATH}/bin/libdvdnav.dll DIRECTORY ${LIBDVD_TARGET_DIR}) -endif() + if(CORE_SYSTEM_NAME MATCHES windows) + set(LIBDVD_TARGET_DIR .) + copy_file_to_buildtree($ DIRECTORY ${LIBDVD_TARGET_DIR}) + else() + # link a shared dvdnav library that includes the whole archives of dvdread and dvdcss as well + # the quotes around _dvdlibs are on purpose, since we want to pass a list to the function that will be unpacked automatically + core_link_library(LibDvdNav::LibDvdNav system/players/VideoPlayer/libdvdnav libdvdnav archives "${_dvdlibs}") + endif() -add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE IMPORTED) -set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES - INTERFACE_LINK_LIBRARIES "LibDvdNav::LibDvdNav") -add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} LibDvdNav::LibDvdNav) + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE IMPORTED) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} LibDvdNav::LibDvdNav) +endif() diff --git a/cmake/modules/FindLibDvdCSS.cmake b/cmake/modules/FindLibDvdCSS.cmake index ea8d92c407435..deed2de5ac376 100644 --- a/cmake/modules/FindLibDvdCSS.cmake +++ b/cmake/modules/FindLibDvdCSS.cmake @@ -7,7 +7,7 @@ # # LibDvdCSS::LibDvdCSS - The LibDvdCSS library -if(ENABLE_DVDCSS) +if(NOT TARGET LibDvdCSS::LibDvdCSS) include(cmake/scripts/common/ModuleHelpers.cmake) set(MODULE_LC libdvdcss) @@ -34,12 +34,10 @@ if(ENABLE_DVDCSS) if(CORE_SYSTEM_NAME STREQUAL android) if(ARCH STREQUAL arm) set(HOST_ARCH arm-linux-androideabi) - elseif(ARCH STREQUAL aarch64) - set(HOST_ARCH aarch64-linux-android) elseif(ARCH STREQUAL i486-linux) set(HOST_ARCH i686-linux-android) - elseif(ARCH STREQUAL x86_64) - set(HOST_ARCH x86_64-linux-android) + elseif() + set(HOST_ARCH ${ARCH}-linux-android) endif() elseif(CORE_SYSTEM_NAME STREQUAL windowsstore) set(LIBDVD_ADDITIONAL_ARGS "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}" "-DCMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION}") @@ -83,28 +81,21 @@ if(ENABLE_DVDCSS) BUILD_DEP_TARGET() -endif() - -include(SelectLibraryConfigurations) -select_library_configurations(LibDvdCSS) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibDvdCSS - REQUIRED_VARS LIBDVDCSS_LIBRARY LIBDVDCSS_INCLUDE_DIR - VERSION_VAR LIBDVDCSS_VERSION) - -if(LIBDVDCSS_FOUND) - add_library(LibDvdCSS::LibDvdCSS UNKNOWN IMPORTED) - set_target_properties(LibDvdCSS::LibDvdCSS PROPERTIES - IMPORTED_LOCATION "${LIBDVDCSS_LIBRARY}" - INTERFACE_COMPILE_DEFINITIONS "HAVE_DVDCSS_DVDCSS_H" - INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDCSS_INCLUDE_DIR}") - - if(TARGET libdvdcss) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LibDvdCSS + REQUIRED_VARS LIBDVDCSS_LIBRARY LIBDVDCSS_INCLUDE_DIR + VERSION_VAR LIBDVDCSS_VERSION) + + if(LibDvdCSS_FOUND) + add_library(LibDvdCSS::LibDvdCSS STATIC IMPORTED) + set_target_properties(LibDvdCSS::LibDvdCSS PROPERTIES + IMPORTED_LOCATION "${LIBDVDCSS_LIBRARY}" + INTERFACE_COMPILE_DEFINITIONS "HAVE_DVDCSS_DVDCSS_H" + INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDCSS_INCLUDE_DIR}") add_dependencies(LibDvdCSS::LibDvdCSS libdvdcss) - endif() -else() - if(LibDvdCSS_FIND_REQUIRED) - message(FATAL_ERROR "Libdvdcss not found. Possibly remove ENABLE_DVDCSS.") + else() + if(LibDvdCSS_FIND_REQUIRED) + message(FATAL_ERROR "Libdvdcss not found. Possibly remove ENABLE_DVDCSS.") + endif() endif() endif() diff --git a/cmake/modules/FindLibDvdNav.cmake b/cmake/modules/FindLibDvdNav.cmake index fe319be03e400..503f559e103ea 100644 --- a/cmake/modules/FindLibDvdNav.cmake +++ b/cmake/modules/FindLibDvdNav.cmake @@ -9,11 +9,7 @@ if(NOT TARGET LibDvdNav::LibDvdNav) - # Check for existing LibDvdRead. - # Suppress mismatch warning, see https://cmake.org/cmake/help/latest/module/FindPackageHandleStandardArgs.html - set(FPHSA_NAME_MISMATCHED 1) find_package(LibDvdRead MODULE REQUIRED) - unset(FPHSA_NAME_MISMATCHED) include(cmake/scripts/common/ModuleHelpers.cmake) @@ -41,12 +37,10 @@ if(NOT TARGET LibDvdNav::LibDvdNav) if(CORE_SYSTEM_NAME STREQUAL android) if(ARCH STREQUAL arm) set(HOST_ARCH arm-linux-androideabi) - elseif(ARCH STREQUAL aarch64) - set(HOST_ARCH aarch64-linux-android) elseif(ARCH STREQUAL i486-linux) set(HOST_ARCH i686-linux-android) - elseif(ARCH STREQUAL x86_64) - set(HOST_ARCH x86_64-linux-android) + elseif() + set(HOST_ARCH ${ARCH}-linux-android) endif() elseif(CORE_SYSTEM_NAME STREQUAL windowsstore) set(LIBDVD_ADDITIONAL_ARGS "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}" "-DCMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION}") @@ -105,33 +99,24 @@ if(NOT TARGET LibDvdNav::LibDvdNav) BUILD_DEP_TARGET() - if(TARGET LibDvdRead::LibDvdRead) - add_dependencies(libdvdnav LibDvdRead::LibDvdRead) - endif() -endif() + add_dependencies(libdvdnav LibDvdRead::LibDvdRead) -include(SelectLibraryConfigurations) -select_library_configurations(LibDvdNav) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LibDvdNav + REQUIRED_VARS LIBDVDNAV_LIBRARY LIBDVDNAV_INCLUDE_DIR + VERSION_VAR LIBDVDNAV_VERSION) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibDvdNav - REQUIRED_VARS LIBDVDNAV_LIBRARY LIBDVDNAV_INCLUDE_DIR - VERSION_VAR LIBDVDNAV_VERSION) + if(LibDvdNav_FOUND) + add_library(LibDvdNav::LibDvdNav UNKNOWN IMPORTED) + set_target_properties(LibDvdNav::LibDvdNav PROPERTIES + IMPORTED_LOCATION "${LIBDVDNAV_LIBRARY}" + INTERFACE_LINK_LIBRARIES LibDvdRead::LibDvdRead + INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDNAV_INCLUDE_DIR}") -if(LIBDVDNAV_FOUND) - add_library(LibDvdNav::LibDvdNav UNKNOWN IMPORTED) - set_target_properties(LibDvdNav::LibDvdNav PROPERTIES - IMPORTED_LOCATION "${LIBDVDNAV_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDNAV_INCLUDE_DIR}") - - if(TARGET libdvdnav) add_dependencies(LibDvdNav::LibDvdNav libdvdnav) - endif() - if(TARGET LibDvdRead::LibDvdRead) - target_link_libraries(LibDvdNav::LibDvdNav INTERFACE LibDvdRead::LibDvdRead) - endif() -else() - if(LibDvdNav_FIND_REQUIRED) - message(FATAL_ERROR "Libdvdnav not found") + else() + if(LibDvdNav_FIND_REQUIRED) + message(FATAL_ERROR "Libdvdnav not found") + endif() endif() endif() diff --git a/cmake/modules/FindLibDvdRead.cmake b/cmake/modules/FindLibDvdRead.cmake index 9f590343af5ab..4dead2d137545 100644 --- a/cmake/modules/FindLibDvdRead.cmake +++ b/cmake/modules/FindLibDvdRead.cmake @@ -10,11 +10,7 @@ if(NOT TARGET LibDvdRead::LibDvdRead) if(ENABLE_DVDCSS) - # Check for existing LIBDVDCSS. - # Suppress mismatch warning, see https://cmake.org/cmake/help/latest/module/FindPackageHandleStandardArgs.html - set(FPHSA_NAME_MISMATCHED 1) find_package(LibDvdCSS MODULE REQUIRED) - unset(FPHSA_NAME_MISMATCHED) endif() include(cmake/scripts/common/ModuleHelpers.cmake) @@ -43,12 +39,10 @@ if(NOT TARGET LibDvdRead::LibDvdRead) if(CORE_SYSTEM_NAME STREQUAL android) if(ARCH STREQUAL arm) set(HOST_ARCH arm-linux-androideabi) - elseif(ARCH STREQUAL aarch64) - set(HOST_ARCH aarch64-linux-android) elseif(ARCH STREQUAL i486-linux) set(HOST_ARCH i686-linux-android) - elseif(ARCH STREQUAL x86_64) - set(HOST_ARCH x86_64-linux-android) + else() + set(HOST_ARCH ${ARCH}-linux-android) endif() elseif(CORE_SYSTEM_NAME STREQUAL windowsstore) set(LIBDVD_ADDITIONAL_ARGS "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}" "-DCMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION}") @@ -71,7 +65,7 @@ if(NOT TARGET LibDvdRead::LibDvdRead) if(TARGET LibDvdCSS::LibDvdCSS) string(APPEND LIBDVDREAD_CFLAGS " -I$ $<$:-D$>") - string(APPEND with-css "--with-libdvdcss") + set(with-css "--with-libdvdcss") endif() find_program(AUTORECONF autoreconf REQUIRED) @@ -105,30 +99,28 @@ if(NOT TARGET LibDvdRead::LibDvdRead) if(TARGET LibDvdCSS::LibDvdCSS) add_dependencies(libdvdread LibDvdCSS::LibDvdCSS) endif() -endif() -include(SelectLibraryConfigurations) -select_library_configurations(LibDvdRead) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LibDvdRead + REQUIRED_VARS LIBDVDREAD_LIBRARY LIBDVDREAD_INCLUDE_DIR + VERSION_VAR LIBDVDREAD_VERSION) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibDvdRead - REQUIRED_VARS LIBDVDREAD_LIBRARY LIBDVDREAD_INCLUDE_DIR - VERSION_VAR LIBDVDREAD_VERSION) + if(LibDvdRead_FOUND) + add_library(LibDvdRead::LibDvdRead STATIC IMPORTED) + set_target_properties(LibDvdRead::LibDvdRead PROPERTIES + IMPORTED_LOCATION "${LIBDVDREAD_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDREAD_INCLUDE_DIR}") -if(LIBDVDREAD_FOUND) - add_library(LibDvdRead::LibDvdRead UNKNOWN IMPORTED) - set_target_properties(LibDvdRead::LibDvdRead PROPERTIES - IMPORTED_LOCATION "${LIBDVDREAD_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDREAD_INCLUDE_DIR}") + if(TARGET LibDvdCSS::LibDvdCSS) + set_target_properties(LibDvdRead::LibDvdRead PROPERTIES + INTERFACE_LINK_LIBRARIES LibDvdCSS::LibDvdCSS) + add_dependencies(LibDvdRead::LibDvdRead LibDvdCSS::LibDvdCSS) + endif() - if(TARGET libdvdread) add_dependencies(LibDvdRead::LibDvdRead libdvdread) - endif() - if(TARGET LibDvdCSS::LibDvdCSS) - target_link_libraries(LibDvdRead::LibDvdRead INTERFACE LibDvdCSS::LibDvdCSS) - endif() -else() - if(LibDvdRead_FIND_REQUIRED) - message(FATAL_ERROR "Libdvdread not found") + else() + if(LibDvdRead_FIND_REQUIRED) + message(FATAL_ERROR "Libdvdread not found") + endif() endif() endif() diff --git a/tools/depends/target/libdvdnav/LIBDVDNAV-VERSION b/tools/depends/target/libdvdnav/LIBDVDNAV-VERSION index eff7e7812bc0e..1f7ebd66c7232 100644 --- a/tools/depends/target/libdvdnav/LIBDVDNAV-VERSION +++ b/tools/depends/target/libdvdnav/LIBDVDNAV-VERSION @@ -5,4 +5,4 @@ FULL_URL=$(BASE_URL)/archive/$(VERSION).tar.gz ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz SHA512=51e6fc033121241354a5f0b3fc9a430577ae3ff6bb7f31445aa548ef4893037fb80eea3b2c6774c81e9ebaf9c45e9b490c98c2c65eb38f9f7daba84b236f7e1d BYPRODUCT=libdvdnav.a -BYPRODUCT_WIN=libdvdnav.lib +BYPRODUCT_WIN=libdvdnav.dll From 29f33e3d7d85364ce86d252790ed0fc4b4006e9f Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 9 Jul 2024 20:42:16 +1000 Subject: [PATCH 265/651] [cmake] Fix LibDVD* for all platforms Oops, broke this pretty bad. Fix macros using incorrect arguments. Fix target usage of additional libs. Dont use generator expression in a variable --- cmake/modules/FindLibDvd.cmake | 13 ++++++++----- cmake/scripts/darwin_embedded/Macros.cmake | 2 +- cmake/scripts/freebsd/Macros.cmake | 2 +- cmake/scripts/linux/Macros.cmake | 2 +- cmake/scripts/osx/Macros.cmake | 3 +-- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cmake/modules/FindLibDvd.cmake b/cmake/modules/FindLibDvd.cmake index dce52d9bcac5b..ac0467c5f5936 100644 --- a/cmake/modules/FindLibDvd.cmake +++ b/cmake/modules/FindLibDvd.cmake @@ -9,16 +9,19 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(LibDvdNav MODULE REQUIRED) - set(_dvdlibs LibDvdNav::LibDvdNav - $<$:LibDvdCSS::LibDvdCSS>>) - if(CORE_SYSTEM_NAME MATCHES windows) set(LIBDVD_TARGET_DIR .) - copy_file_to_buildtree($ DIRECTORY ${LIBDVD_TARGET_DIR}) + copy_file_to_buildtree(${DEPENDS_PATH}/bin/libdvdnav.dll DIRECTORY ${LIBDVD_TARGET_DIR}) else() + set(_dvdlibs LibDvdRead::LibDvdRead) + + if(TARGET LibDvdCSS::LibDvdCSS) + list(APPEND _dvdlibs LibDvdCSS::LibDvdCSS) + endif() + # link a shared dvdnav library that includes the whole archives of dvdread and dvdcss as well # the quotes around _dvdlibs are on purpose, since we want to pass a list to the function that will be unpacked automatically - core_link_library(LibDvdNav::LibDvdNav system/players/VideoPlayer/libdvdnav libdvdnav archives "${_dvdlibs}") + core_link_library(LibDvdNav::LibDvdNav system/players/VideoPlayer/libdvdnav archives "${_dvdlibs}") endif() add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} INTERFACE IMPORTED) diff --git a/cmake/scripts/darwin_embedded/Macros.cmake b/cmake/scripts/darwin_embedded/Macros.cmake index f101044b11082..1aee7a293961e 100644 --- a/cmake/scripts/darwin_embedded/Macros.cmake +++ b/cmake/scripts/darwin_embedded/Macros.cmake @@ -39,7 +39,7 @@ function(core_link_library lib wraplib) elseif(check_arg STREQUAL archives) foreach(_data_arg ${data_arg}) if(TARGET ${_data_arg}) - list(APPEND extra_libs $) + list(APPEND extra_libs $) else() list(APPEND extra_libs ${_data_arg}) endif() diff --git a/cmake/scripts/freebsd/Macros.cmake b/cmake/scripts/freebsd/Macros.cmake index f1877cf1e4603..efd0f3eb257d1 100644 --- a/cmake/scripts/freebsd/Macros.cmake +++ b/cmake/scripts/freebsd/Macros.cmake @@ -30,7 +30,7 @@ function(core_link_library lib wraplib) elseif(check_arg STREQUAL archives) foreach(_data_arg ${data_arg}) if(TARGET ${_data_arg}) - list(APPEND extra_libs $) + list(APPEND extra_libs $) else() list(APPEND extra_libs ${_data_arg}) endif() diff --git a/cmake/scripts/linux/Macros.cmake b/cmake/scripts/linux/Macros.cmake index 5ffb8171ebc7b..bb6a9a9f32beb 100644 --- a/cmake/scripts/linux/Macros.cmake +++ b/cmake/scripts/linux/Macros.cmake @@ -30,7 +30,7 @@ function(core_link_library lib wraplib) elseif(check_arg STREQUAL archives) foreach(_data_arg ${data_arg}) if(TARGET ${_data_arg}) - list(APPEND extra_libs $) + list(APPEND extra_libs $) else() list(APPEND extra_libs ${_data_arg}) endif() diff --git a/cmake/scripts/osx/Macros.cmake b/cmake/scripts/osx/Macros.cmake index 6d41981db453d..10a9eac96edd1 100644 --- a/cmake/scripts/osx/Macros.cmake +++ b/cmake/scripts/osx/Macros.cmake @@ -9,7 +9,6 @@ function(core_link_library lib wraplib) set(link_lib $) set(check_arg ${ARGV2}) set(data_arg ${ARGV3}) - else() set(target ${ARGV2}) set(link_lib ${lib}) @@ -26,7 +25,7 @@ function(core_link_library lib wraplib) elseif(check_arg STREQUAL archives) foreach(_data_arg ${data_arg}) if(TARGET ${_data_arg}) - list(APPEND extra_libs $) + list(APPEND extra_libs $) else() list(APPEND extra_libs ${_data_arg}) endif() From a5e486efe2ad4d541bc82453ffd3448226e61002 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Tue, 9 Jul 2024 17:35:20 +0200 Subject: [PATCH 266/651] [Android] Replace ALooper_pollAll with ALooper_pollOnce --- xbmc/platform/android/activity/EventLoop.cpp | 33 +++++++++----------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/xbmc/platform/android/activity/EventLoop.cpp b/xbmc/platform/android/activity/EventLoop.cpp index ac5fc615655c2..c8d9989ccc489 100644 --- a/xbmc/platform/android/activity/EventLoop.cpp +++ b/xbmc/platform/android/activity/EventLoop.cpp @@ -23,33 +23,30 @@ CEventLoop::CEventLoop(android_app* application) m_application->onInputEvent = inputCallback; } -void CEventLoop::run(IActivityHandler &activityHandler, IInputHandler &inputHandler) +void CEventLoop::run(IActivityHandler& activityHandler, IInputHandler& inputHandler) { - int ident; - int events; - struct android_poll_source* source; - m_activityHandler = &activityHandler; m_inputHandler = &inputHandler; CXBMCApp::android_printf("CEventLoop: starting event loop"); - while (true) + + while (!m_application->destroyRequested) { - // We will block forever waiting for events. - while ((ident = ALooper_pollAll(-1, NULL, &events, (void**)&source)) >= 0) + android_poll_source* source = nullptr; + int result = ALooper_pollOnce(-1, nullptr, nullptr, reinterpret_cast(&source)); + + if (result == ALOOPER_POLL_ERROR) { - // Process this event. - if (source != NULL) - source->process(m_application, source); - - // Check if we are exiting. - if (m_application->destroyRequested) - { - CXBMCApp::android_printf("CEventLoop: we are being destroyed"); - return; - } + CXBMCApp::android_printf("CEventLoop: ALooper_pollOnce returned an error"); + break; } + + // Process this event. + if (source != nullptr) + source->process(m_application, source); } + + CXBMCApp::android_printf("CEventLoop: we are being destroyed"); } void CEventLoop::processActivity(int32_t command) From e95f207ecf1de5eed44744b6d84691c00b6f1e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Sun, 7 Jul 2024 18:34:11 +0200 Subject: [PATCH 267/651] Map: Make access log(n) if keys can be sorted --- xbmc/utils/Map.h | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/xbmc/utils/Map.h b/xbmc/utils/Map.h index 17af54548f6d0..f4a1c872c49b0 100644 --- a/xbmc/utils/Map.h +++ b/xbmc/utils/Map.h @@ -26,7 +26,8 @@ * This class is useful for mapping enum values to strings that can be * compile time checked. This also helps with heap usage. * - * Lookups have linear complexity, so should not be used for "big" maps. + * Lookups have log(n) complexity if Key is comparable using std::less<>, + * otherwise it's linear. */ template class CMap @@ -55,6 +56,12 @@ class CMap // ++begin; // } + + if constexpr (requires(Key k) { std::less<>{}(k, k); }) + { + std::sort(m_map.begin(), m_map.end(), + [](const auto& a, const auto& b) { return std::less<>{}(a.first, b.first); }); + } } ~CMap() = default; @@ -74,8 +81,21 @@ class CMap constexpr auto find(const Key& key) const { - return std::find_if(m_map.cbegin(), m_map.cend(), - [&key](const auto& pair) { return pair.first == key; }); + if constexpr (requires(Key k) { std::less<>{}(k, k); }) + { + const auto iter = + std::lower_bound(m_map.cbegin(), m_map.cend(), key, + [](const auto& a, const auto& b) { return std::less<>{}(a.first, b); }); + if (iter != m_map.end() && !(key < iter->first)) + return iter; + else + return m_map.end(); + } + else + { + return std::find_if(m_map.cbegin(), m_map.cend(), + [&key](const auto& pair) { return pair.first == key; }); + } } constexpr size_t size() const { return Size; } From 6fb722cf5b61ad366c6a7eb0918fdec9a5099423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Sun, 7 Jul 2024 21:34:19 +0200 Subject: [PATCH 268/651] TestMap: Add unit test for CMap --- xbmc/utils/test/CMakeLists.txt | 1 + xbmc/utils/test/TestMap.cpp | 65 ++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 xbmc/utils/test/TestMap.cpp diff --git a/xbmc/utils/test/CMakeLists.txt b/xbmc/utils/test/CMakeLists.txt index 99bd4faaf736d..82da6783a62a7 100644 --- a/xbmc/utils/test/CMakeLists.txt +++ b/xbmc/utils/test/CMakeLists.txt @@ -28,6 +28,7 @@ set(SOURCES TestAlarmClock.cpp TestLangCodeExpander.cpp TestLocale.cpp Testlog.cpp + TestMap.cpp TestMathUtils.cpp TestMime.cpp TestPOUtils.cpp diff --git a/xbmc/utils/test/TestMap.cpp b/xbmc/utils/test/TestMap.cpp new file mode 100644 index 0000000000000..514c5a7c17951 --- /dev/null +++ b/xbmc/utils/test/TestMap.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 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 "utils/Map.h" + +#include + +#include + +// CMap with sortable keys. Keys are sorted at compile time and find uses binary search +TEST(TestMap, SortableKey) +{ + constexpr auto map = make_map({{3, "three"}, {1, "one"}, {2, "two"}}); + EXPECT_EQ(map.find(1)->second, "one"); + EXPECT_EQ(map.find(2)->second, "two"); + EXPECT_EQ(map.find(3)->second, "three"); + EXPECT_EQ(map.find(0), map.cend()); + EXPECT_EQ(map.find(4), map.cend()); + // Ensure content is sorted + EXPECT_TRUE( + std::is_sorted(map.cbegin(), map.cend(), [](auto& a, auto& b) { return a.first < b.first; })); +} + +// CMap with non sortable keys. Performs linear search in find +TEST(TestMap, NonSortableKey) +{ + struct Dummy + { + int i; + bool operator==(const Dummy& other) const { return i == other.i; } + }; + + constexpr auto map = make_map( + {{Dummy{3}, "three"}, {Dummy{1}, "one"}, {Dummy{2}, "two"}}); + EXPECT_EQ(map.find(Dummy{1})->second, "one"); + EXPECT_EQ(map.find(Dummy{2})->second, "two"); + EXPECT_EQ(map.find(Dummy{3})->second, "three"); + EXPECT_EQ(map.find(Dummy{0}), map.cend()); + EXPECT_EQ(map.find(Dummy{4}), map.cend()); +} + +// CMap compile time tests (not really a unit test...) +TEST(TestMap, EnumConstexpr) +{ + enum class ENUM + { + ONE, + TWO, + THREE, + ENUM_MAX, + }; + + constexpr auto map = make_map( + {{ENUM::ONE, "ONE"}, {ENUM::TWO, "TWO"}, {ENUM::THREE, "THREE"}}); + static_assert(map.find(ENUM::ONE)->second == "ONE"); + static_assert(map.find(ENUM::TWO)->second == "TWO"); + static_assert(map.find(ENUM::THREE)->second == "THREE"); + static_assert(map.find(ENUM::ENUM_MAX) == map.cend()); + static_assert(map.size() == size_t(ENUM::ENUM_MAX)); +} From 1be7403820c92d355d3115fc177e732b6e3b9cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Sun, 7 Jul 2024 21:42:20 +0200 Subject: [PATCH 269/651] Map: Simplify constructor --- xbmc/utils/Map.h | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/xbmc/utils/Map.h b/xbmc/utils/Map.h index f4a1c872c49b0..21c4011621a50 100644 --- a/xbmc/utils/Map.h +++ b/xbmc/utils/Map.h @@ -36,26 +36,7 @@ class CMap template constexpr CMap(Iterable begin, Iterable end) { - size_t index = 0; - while (begin != end) - { - // c++17 doesn't have constexpr assignment operator for std::pair - auto& first = m_map[index].first; - auto& second = m_map[index].second; - ++index; - - first = std::move(begin->first); - second = std::move(begin->second); - ++begin; - - //! @todo: c++20 can use constexpr assignment operator instead - // auto& p = data[index]; - // ++index; - - // p = std::move(*begin); - // ++begin; - // - } + std::move(begin, end, m_map.begin()); if constexpr (requires(Key k) { std::less<>{}(k, k); }) { From 6137da592ce203e736249d40174cbc3b7aa183c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Sun, 7 Jul 2024 22:05:45 +0200 Subject: [PATCH 270/651] Map: Make CMap constructor consteval --- xbmc/utils/Map.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/utils/Map.h b/xbmc/utils/Map.h index 21c4011621a50..4e14432e05752 100644 --- a/xbmc/utils/Map.h +++ b/xbmc/utils/Map.h @@ -34,7 +34,7 @@ class CMap { public: template - constexpr CMap(Iterable begin, Iterable end) + consteval CMap(Iterable begin, Iterable end) { std::move(begin, end, m_map.begin()); @@ -97,7 +97,7 @@ class CMap * */ template -constexpr auto make_map(std::pair(&&m)[Size]) -> CMap +consteval auto make_map(std::pair (&&m)[Size]) -> CMap { return CMap(std::begin(m), std::end(m)); } From cc1869c6414a0a18077601f3a237ec759b1b1cad Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Mon, 8 Jul 2024 11:34:59 +0100 Subject: [PATCH 271/651] [depends] Bump exiv2 to v0.28.3 --- cmake/modules/FindExiv2.cmake | 8 +- ...XIV2_ENABLE_FILESYSTEM_ACCESS-option.patch | 564 ------------------ ...stfix.patch => 0001-WIN-lib-postfix.patch} | 0 ...HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch | 169 ------ .../0003-UWP-Disable-getLoadedLibraries.patch | 45 -- .../target/exiv2/0005-GCC13-cstdint.patch | 10 - tools/depends/target/exiv2/EXIV2-VERSION | 4 +- tools/depends/target/exiv2/Makefile | 12 +- 8 files changed, 6 insertions(+), 806 deletions(-) delete mode 100644 tools/depends/target/exiv2/0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch rename tools/depends/target/exiv2/{0004-WIN-lib-postfix.patch => 0001-WIN-lib-postfix.patch} (100%) delete mode 100644 tools/depends/target/exiv2/0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch delete mode 100644 tools/depends/target/exiv2/0003-UWP-Disable-getLoadedLibraries.patch delete mode 100644 tools/depends/target/exiv2/0005-GCC13-cstdint.patch diff --git a/cmake/modules/FindExiv2.cmake b/cmake/modules/FindExiv2.cmake index 0496a11f0f69a..11418dad9040a 100644 --- a/cmake/modules/FindExiv2.cmake +++ b/cmake/modules/FindExiv2.cmake @@ -12,12 +12,8 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) macro(buildexiv2) find_package(Iconv REQUIRED) - # Note: Please drop once a release based on master is made. First 2 are already upstream. - set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch" - "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch" - "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0003-UWP-Disable-getLoadedLibraries.patch" - "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0004-WIN-lib-postfix.patch" - "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0005-GCC13-cstdint.patch") + # Patch pending review upstream (https://github.com/Exiv2/exiv2/pull/3004) + set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/0001-WIN-lib-postfix.patch") generate_patchcommand("${patches}") diff --git a/tools/depends/target/exiv2/0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch b/tools/depends/target/exiv2/0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch deleted file mode 100644 index 85983690f1c06..0000000000000 --- a/tools/depends/target/exiv2/0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch +++ /dev/null @@ -1,564 +0,0 @@ -From 64acd1a9b4a83ae9fb093660245bb1206cf3817b Mon Sep 17 00:00:00 2001 -From: Miguel Borges de Freitas <92enen@gmail.com> -Date: Fri, 17 Nov 2023 17:08:29 +0000 -Subject: [PATCH 1/2] Add EXIV2_ENABLE_FILESYSTEM_ACCESS option - ---- - CMakeLists.txt | 3 ++- - README.md | 1 + - cmake/config.h.cmake | 3 +++ - cmake/findDependencies.cmake | 4 +++- - cmake/generateConfigFile.cmake | 9 +++++---- - cmake/printSummary.cmake | 1 + - include/exiv2/basicio.hpp | 4 +++- - include/exiv2/exif.hpp | 6 ++++++ - include/exiv2/preview.hpp | 2 ++ - meson.build | 1 + - src/basicio.cpp | 32 +++++++++++++++++--------------- - src/exif.cpp | 6 ++++++ - src/futils.cpp | 10 ++++++++++ - src/image.cpp | 14 +++++++++++++- - src/makernote_int.cpp | 8 +++++++- - src/preview.cpp | 2 ++ - src/tiffcomposite_int.cpp | 2 +- - 17 files changed, 83 insertions(+), 25 deletions(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index e9f14f722..36266d41a 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -36,7 +36,7 @@ option( EXIV2_ENABLE_BMFF "Build with BMFF support" - option( EXIV2_ENABLE_BROTLI "Use Brotli for JPEG XL compressed boxes (BMFF)" ON ) - option( EXIV2_ENABLE_VIDEO "Build with video support" ON ) - option( EXIV2_ENABLE_INIH "Use inih library" ON ) -- -+option( EXIV2_ENABLE_FILESYSTEM_ACCESS "Build with filesystem access" ON ) - option( EXIV2_BUILD_SAMPLES "Build sample applications" OFF ) - option( EXIV2_BUILD_EXIV2_COMMAND "Build exiv2 command-line executable" ON ) - option( EXIV2_BUILD_UNIT_TESTS "Build unit tests" OFF ) -@@ -106,6 +106,7 @@ if(EXIV2_BUILD_EXIV2_COMMAND) - - if( EXIV2_BUILD_SAMPLES ) - add_subdirectory( samples ) -+ set(EXIV2_ENABLE_FILESYSTEM_ACCESS ON) - get_directory_property(SAMPLES DIRECTORY samples DEFINITION APPLICATIONS) - - if (Python3_Interpreter_FOUND) -diff --git a/README.md b/README.md -index 63c9a1c4f..331065363 100644 ---- a/README.md -+++ b/README.md -@@ -254,6 +254,7 @@ option( EXIV2_ENABLE_PNG "Build with png support (requires libz)" - ... - option( EXIV2_ENABLE_BMFF "Build with BMFF support (brotli recommended)" ON ) - option( EXIV2_ENABLE_BROTLI "Use Brotli for JPEG XL compressed boxes (BMFF)" ON ) -+option( EXIV2_ENABLE_FILESYSTEM_ACCESS "Build with filesystem access" ON ) - 577 rmills@rmillsmm:~/gnu/github/exiv2/exiv2 $ - ``` - -diff --git a/cmake/config.h.cmake b/cmake/config.h.cmake -index a817d2d1b..f24cd4a63 100644 ---- a/cmake/config.h.cmake -+++ b/cmake/config.h.cmake -@@ -6,6 +6,9 @@ - // Define to 1 if you want to use libcurl in httpIO. - #cmakedefine EXV_USE_CURL - -+// Define to 1 if you want to enable filesystem access -+#cmakedefine EXV_ENABLE_FILESYSTEM -+ - // Define if you require webready support. - #cmakedefine EXV_ENABLE_WEBREADY - -diff --git a/cmake/findDependencies.cmake b/cmake/findDependencies.cmake -index 5ee481d1c..5159cb0b7 100644 ---- a/cmake/findDependencies.cmake -+++ b/cmake/findDependencies.cmake -@@ -35,7 +35,9 @@ if (NOT Python3_Interpreter_FOUND) - message(WARNING "Python3 was not found. Python tests under the 'tests' folder will not be executed") - endif() - --find_package(Filesystem COMPONENTS Experimental Final REQUIRED) -+if(EXIV2_ENABLE_FILESYSTEM_ACCESS) -+ find_package(Filesystem COMPONENTS Experimental Final REQUIRED) -+endif() - - # don't use Frameworks on the Mac (#966) - if (APPLE) -diff --git a/cmake/generateConfigFile.cmake b/cmake/generateConfigFile.cmake -index 49c9a10f6..20e331c04 100644 ---- a/cmake/generateConfigFile.cmake -+++ b/cmake/generateConfigFile.cmake -@@ -6,10 +6,11 @@ include(CheckCXXSymbolExists) - if (${EXIV2_ENABLE_WEBREADY}) - set(EXV_USE_CURL ${EXIV2_ENABLE_CURL}) - endif() --set(EXV_ENABLE_BMFF ${EXIV2_ENABLE_BMFF}) --set(EXV_ENABLE_WEBREADY ${EXIV2_ENABLE_WEBREADY}) --set(EXV_HAVE_LENSDATA ${EXIV2_ENABLE_LENSDATA}) --set(EXV_ENABLE_INIH ${EXIV2_ENABLE_INIH}) -+set(EXV_ENABLE_BMFF ${EXIV2_ENABLE_BMFF}) -+set(EXV_ENABLE_WEBREADY ${EXIV2_ENABLE_WEBREADY}) -+set(EXV_HAVE_LENSDATA ${EXIV2_ENABLE_LENSDATA}) -+set(EXV_ENABLE_INIH ${EXIV2_ENABLE_INIH}) -+set(EXV_ENABLE_FILESYSTEM ${EXIV2_ENABLE_FILESYSTEM_ACCESS}) - - set(EXV_PACKAGE_NAME ${PROJECT_NAME}) - set(EXV_PACKAGE_VERSION ${PROJECT_VERSION}) -diff --git a/cmake/printSummary.cmake b/cmake/printSummary.cmake -index 854323ba0..06b2745a2 100644 ---- a/cmake/printSummary.cmake -+++ b/cmake/printSummary.cmake -@@ -69,4 +69,5 @@ OptionOutput( "Building unit tests: " EXIV2_BUILD_UNIT_TESTS - OptionOutput( "Building fuzz tests: " EXIV2_BUILD_FUZZ_TESTS ) - OptionOutput( "Building doc: " EXIV2_BUILD_DOC ) - OptionOutput( "Building with coverage flags: " BUILD_WITH_COVERAGE ) -+OptionOutput( "Building with filesystem access " EXIV2_ENABLE_FILESYSTEM_ACCESS ) - OptionOutput( "Using ccache: " BUILD_WITH_CCACHE ) -diff --git a/include/exiv2/basicio.hpp b/include/exiv2/basicio.hpp -index 179220464..71155136d 100644 ---- a/include/exiv2/basicio.hpp -+++ b/include/exiv2/basicio.hpp -@@ -276,6 +276,7 @@ class EXIV2API IoCloser { - IoCloser& operator=(const IoCloser&) = delete; - }; // class IoCloser - -+#ifdef EXV_ENABLE_FILESYSTEM - /*! - @brief Provides binary file IO by implementing the BasicIo - interface. -@@ -479,6 +480,7 @@ class EXIV2API FileIo : public BasicIo { - std::unique_ptr p_; - - }; // class FileIo -+#endif - - /*! - @brief Provides binary IO on blocks of memory by implementing the BasicIo -@@ -686,7 +688,7 @@ class EXIV2API XPathIo : public MemIo { - */ - void ReadDataUri(const std::string& path); - }; // class XPathIo --#else -+#elif defined(EXV_ENABLE_FILESYSTEM) - class EXIV2API XPathIo : public FileIo { - public: - /*! -diff --git a/include/exiv2/exif.hpp b/include/exiv2/exif.hpp -index 8b1d3d6a4..1228b5dd4 100644 ---- a/include/exiv2/exif.hpp -+++ b/include/exiv2/exif.hpp -@@ -229,6 +229,7 @@ class EXIV2API ExifThumbC { - data buffer and %DataBuf ensures that it will be deleted. - */ - [[nodiscard]] DataBuf copy() const; -+#ifdef EXV_ENABLE_FILESYSTEM - /*! - @brief Write the thumbnail image to a file. - -@@ -240,6 +241,7 @@ class EXIV2API ExifThumbC { - @return The number of bytes written. - */ - [[nodiscard]] size_t writeFile(const std::string& path) const; -+#endif - /*! - @brief Return the MIME type of the thumbnail, either \c "image/tiff" - or \c "image/jpeg". -@@ -279,6 +281,7 @@ class EXIV2API ExifThumb : public ExifThumbC { - - //! @name Manipulators - //@{ -+#ifdef EXV_ENABLE_FILESYSTEM - /*! - @brief Set the Exif thumbnail to the JPEG image \em path. Set - XResolution, YResolution and ResolutionUnit to \em xres, -@@ -297,6 +300,7 @@ class EXIV2API ExifThumb : public ExifThumbC { - application that comes with OS X for one.) - David Harvey. - */ - void setJpegThumbnail(const std::string& path, URational xres, URational yres, uint16_t unit); -+#endif - /*! - @brief Set the Exif thumbnail to the JPEG image pointed to by \em buf, - and size \em size. Set XResolution, YResolution and -@@ -315,6 +319,7 @@ class EXIV2API ExifThumb : public ExifThumbC { - application that comes with OS X for one.) - David Harvey. - */ - void setJpegThumbnail(const byte* buf, size_t size, URational xres, URational yres, uint16_t unit); -+#ifdef EXV_ENABLE_FILESYSTEM - /*! - @brief Set the Exif thumbnail to the JPEG image \em path. - -@@ -329,6 +334,7 @@ class EXIV2API ExifThumb : public ExifThumbC { - @note Additional existing Exif thumbnail tags are not modified. - */ - void setJpegThumbnail(const std::string& path); -+#endif - /*! - @brief Set the Exif thumbnail to the JPEG image pointed to by \em buf, - and size \em size. -diff --git a/include/exiv2/preview.hpp b/include/exiv2/preview.hpp -index 295c35b5c..db069ec8c 100644 ---- a/include/exiv2/preview.hpp -+++ b/include/exiv2/preview.hpp -@@ -68,6 +68,7 @@ class EXIV2API PreviewImage { - @brief Return the size of the preview image in bytes. - */ - [[nodiscard]] uint32_t size() const; -+#ifdef EXV_ENABLE_FILESYSTEM - /*! - @brief Write the thumbnail image to a file. - -@@ -79,6 +80,7 @@ class EXIV2API PreviewImage { - @return The number of bytes written. - */ - [[nodiscard]] size_t writeFile(const std::string& path) const; -+#endif - /*! - @brief Return the MIME type of the preview image, usually either - \c "image/tiff" or \c "image/jpeg". -diff --git a/meson.build b/meson.build -index 5c205d845..1b141d5cf 100644 ---- a/meson.build -+++ b/meson.build -@@ -116,6 +116,7 @@ cdata.set('EXV_HAVE_LIBZ', zlib_dep.found()) - cdata.set('EXV_USE_CURL', curl_dep.found()) - cdata.set('EXV_ENABLE_NLS', intl_dep.found()) - cdata.set('EXV_ENABLE_WEBREADY', curl_dep.found()) -+cdata.set('EXV_ENABLE_FILESYSTEM', true) - - cfile = configure_file( - input: 'cmake/config.h.cmake', -diff --git a/src/basicio.cpp b/src/basicio.cpp -index 1986622b3..36509b16b 100644 ---- a/src/basicio.cpp -+++ b/src/basicio.cpp -@@ -36,6 +36,7 @@ - #include - #endif - -+#ifdef EXV_ENABLE_FILESYSTEM - #ifdef _WIN32 - using mode_t = unsigned short; - #include -@@ -49,19 +50,7 @@ namespace fs = std::filesystem; - #include - namespace fs = std::experimental::filesystem; - #endif -- --// ***************************************************************************** --// class member definitions --namespace { --/// @brief replace each substring of the subject that matches the given search string with the given replacement. --void ReplaceStringInPlace(std::string& subject, std::string_view search, std::string_view replace) { -- auto pos = subject.find(search); -- while (pos != std::string::npos) { -- subject.replace(pos, search.length(), replace); -- pos += subject.find(search, pos + replace.length()); -- } --} --} // namespace -+#endif - - namespace Exiv2 { - void BasicIo::readOrThrow(byte* buf, size_t rcount, ErrorCode err) { -@@ -75,6 +64,7 @@ void BasicIo::seekOrThrow(int64_t offset, Position pos, ErrorCode err) { - Internal::enforce(r == 0, err); - } - -+#ifdef EXV_ENABLE_FILESYSTEM - //! Internal Pimpl structure of class FileIo. - class FileIo::Impl { - public: -@@ -570,6 +560,7 @@ const std::string& FileIo::path() const noexcept { - - void FileIo::populateFakeData() { - } -+#endif - - //! Internal Pimpl structure of class MemIo. - class MemIo::Impl final { -@@ -918,7 +909,7 @@ void XPathIo::ReadDataUri(const std::string& path) { - delete[] decodeData; - } - --#else -+#elif defined(EXV_ENABLE_FILESYSTEM) - XPathIo::XPathIo(const std::string& orgPath) : FileIo(XPathIo::writeDataToFile(orgPath)), tempFilePath_(path()) { - } - -@@ -933,6 +924,16 @@ void XPathIo::transfer(BasicIo& src) { - if (isTemp_) { - // replace temp path to gent path. - auto currentPath = path(); -+ -+ // replace each substring of the subject that matches the given search string with the given replacement. -+ auto ReplaceStringInPlace = [](std::string& subject, std::string_view search, std::string_view replace) { -+ auto pos = subject.find(search); -+ while (pos != std::string::npos) { -+ subject.replace(pos, search.length(), replace); -+ pos += subject.find(search, pos + replace.length()); -+ } -+ }; -+ - ReplaceStringInPlace(currentPath, XPathIo::TEMP_FILE_EXT, XPathIo::GEN_FILE_EXT); - setPath(currentPath); - -@@ -1726,7 +1727,7 @@ CurlIo::CurlIo(const std::string& url, size_t blockSize) { - - // ************************************************************************* - // free functions -- -+#ifdef EXV_ENABLE_FILESYSTEM - DataBuf readFile(const std::string& path) { - FileIo file(path); - if (file.open("rb") != 0) { -@@ -1750,6 +1751,7 @@ size_t writeFile(const DataBuf& buf, const std::string& path) { - } - return file.write(buf.c_data(), buf.size()); - } -+#endif - - #ifdef EXV_USE_CURL - size_t curlWriter(char* data, size_t size, size_t nmemb, std::string* writerData) { -diff --git a/src/exif.cpp b/src/exif.cpp -index 4f61b074c..9c300e531 100644 ---- a/src/exif.cpp -+++ b/src/exif.cpp -@@ -375,6 +375,7 @@ DataBuf ExifThumbC::copy() const { - return thumbnail->copy(exifData_); - } - -+#ifdef EXV_ENABLE_FILESYSTEM - size_t ExifThumbC::writeFile(const std::string& path) const { - auto thumbnail = Thumbnail::create(exifData_); - if (!thumbnail) -@@ -387,6 +388,7 @@ size_t ExifThumbC::writeFile(const std::string& path) const { - - return Exiv2::writeFile(buf, name); - } -+#endif - - const char* ExifThumbC::mimeType() const { - auto thumbnail = Thumbnail::create(exifData_); -@@ -405,10 +407,12 @@ const char* ExifThumbC::extension() const { - ExifThumb::ExifThumb(ExifData& exifData) : ExifThumbC(exifData), exifData_(exifData) { - } - -+#ifdef EXV_ENABLE_FILESYSTEM - void ExifThumb::setJpegThumbnail(const std::string& path, URational xres, URational yres, uint16_t unit) { - DataBuf thumb = readFile(path); // may throw - setJpegThumbnail(thumb.c_data(), thumb.size(), xres, yres, unit); - } -+#endif - - void ExifThumb::setJpegThumbnail(const byte* buf, size_t size, URational xres, URational yres, uint16_t unit) { - setJpegThumbnail(buf, size); -@@ -417,10 +421,12 @@ void ExifThumb::setJpegThumbnail(const byte* buf, size_t size, URational xres, U - exifData_["Exif.Thumbnail.ResolutionUnit"] = unit; - } - -+#ifdef EXV_ENABLE_FILESYSTEM - void ExifThumb::setJpegThumbnail(const std::string& path) { - DataBuf thumb = readFile(path); // may throw - setJpegThumbnail(thumb.c_data(), thumb.size()); - } -+#endif - - void ExifThumb::setJpegThumbnail(const byte* buf, size_t size) { - exifData_["Exif.Thumbnail.Compression"] = static_cast(6); -diff --git a/src/futils.cpp b/src/futils.cpp -index c746838d5..0fd5024d4 100644 ---- a/src/futils.cpp -+++ b/src/futils.cpp -@@ -15,6 +15,7 @@ - #include - #include - -+#ifdef EXV_ENABLE_FILESYSTEM - #if __has_include() - #include - namespace fs = std::filesystem; -@@ -22,6 +23,7 @@ namespace fs = std::filesystem; - #include - namespace fs = std::experimental::filesystem; - #endif -+#endif - - #if defined(_WIN32) - // clang-format off -@@ -230,7 +232,11 @@ bool fileExists(const std::string& path) { - if (fileProtocol(path) != pFile) { - return true; - } -+#ifdef EXV_ENABLE_FILESYSTEM - return fs::exists(path); -+#else -+ return false; -+#endif - } - - std::string strError() { -@@ -340,6 +346,7 @@ Uri Uri::Parse(const std::string& uri) { - } - - std::string getProcessPath() { -+#ifdef EXV_ENABLE_FILESYSTEM - #if defined(__FreeBSD__) - std::string ret("unknown"); - unsigned int n; -@@ -381,5 +388,8 @@ std::string getProcessPath() { - return "unknown"; - } - #endif -+#else -+ return "unknown"; -+#endif - } - } // namespace Exiv2 -diff --git a/src/image.cpp b/src/image.cpp -index 05d8345f9..9bbe1f272 100644 ---- a/src/image.cpp -+++ b/src/image.cpp -@@ -116,11 +116,13 @@ constexpr Registry registry[] = { - #endif // EXV_ENABLE_BMFF - }; - -+#ifdef EXV_ENABLE_FILESYSTEM - std::string pathOfFileUrl(const std::string& url) { - std::string path = url.substr(7); - size_t found = path.find('/'); - return (found == std::string::npos) ? path : path.substr(found); - } -+#endif - - } // namespace - -@@ -748,9 +750,13 @@ bool ImageFactory::checkType(ImageType type, BasicIo& io, bool advance) { - return false; - } - --ImageType ImageFactory::getType(const std::string& path) { -+ImageType ImageFactory::getType([[maybe_unused]] const std::string& path) { -+#ifdef EXV_ENABLE_FILESYSTEM - FileIo fileIo(path); - return getType(fileIo); -+#else -+ return ImageType::none; -+#endif - } - - ImageType ImageFactory::getType(const byte* data, size_t size) { -@@ -781,12 +787,16 @@ BasicIo::UniquePtr ImageFactory::createIo(const std::string& path, bool useCurl) - - if (fProt == pHttp) - return std::make_unique(path); // may throw -+#ifdef EXV_ENABLE_FILESYSTEM - if (fProt == pFileUri) - return std::make_unique(pathOfFileUrl(path)); - if (fProt == pStdin || fProt == pDataUri) - return std::make_unique(path); // may throw - - return std::make_unique(path); -+#else -+ return nullptr; -+#endif - - (void)(useCurl); - } // ImageFactory::createIo -@@ -817,6 +827,7 @@ Image::UniquePtr ImageFactory::open(BasicIo::UniquePtr io) { - return nullptr; - } - -+#ifdef EXV_ENABLE_FILESYSTEM - Image::UniquePtr ImageFactory::create(ImageType type, const std::string& path) { - auto fileIo = std::make_unique(path); - // Create or overwrite the file, then close it -@@ -831,6 +842,7 @@ Image::UniquePtr ImageFactory::create(ImageType type, const std::string& path) { - throw Error(ErrorCode::kerUnsupportedImageType, static_cast(type)); - return image; - } -+#endif - - Image::UniquePtr ImageFactory::create(ImageType type) { - auto image = create(type, std::make_unique()); -diff --git a/src/makernote_int.cpp b/src/makernote_int.cpp -index 0d2e0443f..0579d2596 100644 ---- a/src/makernote_int.cpp -+++ b/src/makernote_int.cpp -@@ -18,6 +18,7 @@ - #include - #include - -+#ifdef EXV_ENABLE_FILESYSTEM - #if __has_include() - #include - namespace fs = std::filesystem; -@@ -25,6 +26,7 @@ namespace fs = std::filesystem; - #include - namespace fs = std::experimental::filesystem; - #endif -+#endif - - #if !defined(_WIN32) - #include -@@ -62,6 +64,7 @@ namespace Exiv2::Internal { - // If not found in cwd, we return the default path - // which is the user profile path on win and the home dir on linux - std::string getExiv2ConfigPath() { -+#ifdef EXV_ENABLE_FILESYSTEM - #ifdef _WIN32 - std::string inifile("exiv2.ini"); - #else -@@ -83,13 +86,16 @@ std::string getExiv2ConfigPath() { - currentPath = std::string(pw ? pw->pw_dir : ""); - #endif - return (currentPath / inifile).string(); -+#else -+ return ""; -+#endif - } - - std::string readExiv2Config([[maybe_unused]] const std::string& section, [[maybe_unused]] const std::string& value, - const std::string& def) { - std::string result = def; - --#ifdef EXV_ENABLE_INIH -+#if defined(EXV_ENABLE_INIH) && defined(EXV_ENABLE_FILESYSTEM) - INIReader reader(Exiv2::Internal::getExiv2ConfigPath()); - if (reader.ParseError() == 0) { - result = reader.Get(section, value, def); -diff --git a/src/preview.cpp b/src/preview.cpp -index 18fe6122b..993c3b749 100644 ---- a/src/preview.cpp -+++ b/src/preview.cpp -@@ -966,12 +966,14 @@ PreviewImage& PreviewImage::operator=(const PreviewImage& rhs) { - return *this; - } - -+#ifdef EXV_ENABLE_FILESYSTEM - size_t PreviewImage::writeFile(const std::string& path) const { - std::string name = path + extension(); - // Todo: Creating a DataBuf here unnecessarily copies the memory - DataBuf buf(pData(), size()); - return Exiv2::writeFile(buf, name); - } -+#endif - - DataBuf PreviewImage::copy() const { - return {pData(), size()}; -diff --git a/src/tiffcomposite_int.cpp b/src/tiffcomposite_int.cpp -index 33967b427..95ce450c7 100644 ---- a/src/tiffcomposite_int.cpp -+++ b/src/tiffcomposite_int.cpp -@@ -1095,7 +1095,7 @@ size_t TiffBinaryArray::doWrite(IoWrapper& ioWrapper, ByteOrder byteOrder, size_ - } - DataBuf buf = cryptFct(tag(), mio.mmap(), mio.size(), pRoot_); - if (!buf.empty()) { -- mio.seek(0, Exiv2::FileIo::beg); -+ mio.seek(0, Exiv2::BasicIo::beg); - mio.write(buf.c_data(), buf.size()); - } - } --- -2.39.3 (Apple Git-146) - diff --git a/tools/depends/target/exiv2/0004-WIN-lib-postfix.patch b/tools/depends/target/exiv2/0001-WIN-lib-postfix.patch similarity index 100% rename from tools/depends/target/exiv2/0004-WIN-lib-postfix.patch rename to tools/depends/target/exiv2/0001-WIN-lib-postfix.patch diff --git a/tools/depends/target/exiv2/0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch b/tools/depends/target/exiv2/0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch deleted file mode 100644 index 46cbb7079fa6c..0000000000000 --- a/tools/depends/target/exiv2/0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch +++ /dev/null @@ -1,169 +0,0 @@ -From 0a626d3e54d5db1193e42b505db5cac9e65967af Mon Sep 17 00:00:00 2001 -From: Miguel Borges de Freitas <92enen@gmail.com> -Date: Fri, 24 Nov 2023 11:56:11 +0000 -Subject: [PATCH 2/2] Set conditional HTTP depending on EXIV2_ENABLE_WEBREADY - ---- - include/exiv2/exiv2.hpp | 2 ++ - include/meson.build | 5 ++++- - meson.build | 8 +++++++- - meson_options.txt | 5 +++++ - src/CMakeLists.txt | 6 ++++-- - src/basicio.cpp | 2 ++ - src/image.cpp | 4 +++- - 7 files changed, 27 insertions(+), 5 deletions(-) - -diff --git a/include/exiv2/exiv2.hpp b/include/exiv2/exiv2.hpp -index bae5614d2..35bdb1ab9 100644 ---- a/include/exiv2/exiv2.hpp -+++ b/include/exiv2/exiv2.hpp -@@ -19,7 +19,9 @@ - #include "exiv2/exif.hpp" - #include "exiv2/futils.hpp" - #include "exiv2/gifimage.hpp" -+#ifdef EXV_ENABLE_WEBREADY - #include "exiv2/http.hpp" -+#endif - #include "exiv2/image.hpp" - #include "exiv2/iptc.hpp" - #include "exiv2/jp2image.hpp" -diff --git a/include/meson.build b/include/meson.build -index 7bbb3800f..abcb4a4d6 100644 ---- a/include/meson.build -+++ b/include/meson.build -@@ -14,7 +14,6 @@ headers = files( - 'exiv2/exiv2.hpp', - 'exiv2/futils.hpp', - 'exiv2/gifimage.hpp', -- 'exiv2/http.hpp', - 'exiv2/image.hpp', - 'exiv2/image_types.hpp', - 'exiv2/iptc.hpp', -@@ -47,6 +46,10 @@ if get_option('video') - headers += files('exiv2/asfvideo.hpp', 'exiv2/matroskavideo.hpp', 'exiv2/quicktimevideo.hpp', 'exiv2/riffvideo.hpp') - endif - -+if get_option('webready') -+ headers += files('exiv2/http.hpp') -+endif -+ - if zlib_dep.found() - headers += files('exiv2/pngimage.hpp') - endif -diff --git a/meson.build b/meson.build -index 1b141d5cf..b69fada34 100644 ---- a/meson.build -+++ b/meson.build -@@ -67,7 +67,12 @@ if brotli_dep.found() - deps += brotli_dep - endif - --curl_dep = dependency('libcurl', disabler: true, required: get_option('curl')) -+if get_option('webready') -+ curl_dep = dependency('libcurl', disabler: true, required: get_option('curl')) -+else -+ curl_dep = dependency('', disabler: true, required: false) -+endif -+ - if curl_dep.found() - deps += curl_dep - endif -@@ -113,6 +118,7 @@ cdata.set('EXV_HAVE_XMP_TOOLKIT', expat_dep.found()) - cdata.set('EXV_HAVE_BROTLI', brotli_dep.found()) - cdata.set('EXV_HAVE_ICONV', iconv_dep.found()) - cdata.set('EXV_HAVE_LIBZ', zlib_dep.found()) -+cdata.set('EXV_ENABLE_WEBREADY', get_option('webready')) - cdata.set('EXV_USE_CURL', curl_dep.found()) - cdata.set('EXV_ENABLE_NLS', intl_dep.found()) - cdata.set('EXV_ENABLE_WEBREADY', curl_dep.found()) -diff --git a/meson_options.txt b/meson_options.txt -index f80430a70..ca03706ea 100644 ---- a/meson_options.txt -+++ b/meson_options.txt -@@ -44,3 +44,8 @@ option('xmp', type : 'feature', - option('unitTests', type : 'feature', - description : 'Build and run unit tests', - ) -+ -+option('webready', type : 'boolean', -+ value: true, -+ description : 'Build with support for webready', -+) -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index f1b27b861..348a0bfb1 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -48,7 +48,6 @@ set(PUBLIC_HEADERS - ../include/exiv2/exiv2.hpp - ../include/exiv2/futils.hpp - ../include/exiv2/gifimage.hpp -- ../include/exiv2/http.hpp - ../include/exiv2/image.hpp - ../include/exiv2/image_types.hpp - ../include/exiv2/iptc.hpp -@@ -92,7 +91,6 @@ add_library( exiv2lib - futils.cpp - fff.h - gifimage.cpp -- http.cpp - image.cpp - iptc.cpp - jp2image.cpp -@@ -129,6 +127,10 @@ generate_export_header(exiv2lib - - # Conditional addition of sources to library targets - # --------------------------------------------------------- -+if(EXIV2_ENABLE_WEBREADY) -+ set(PUBLIC_HEADERS ${PUBLIC_HEADERS} ../include/exiv2/http.hpp) -+ target_sources(exiv2lib PRIVATE http.cpp ../include/exiv2/http.hpp) -+endif() - - if( EXIV2_ENABLE_PNG ) - set(PUBLIC_HEADERS ${PUBLIC_HEADERS} ../include/exiv2/pngimage.hpp) -diff --git a/src/basicio.cpp b/src/basicio.cpp -index 36509b16b..5f3a9abb3 100644 ---- a/src/basicio.cpp -+++ b/src/basicio.cpp -@@ -1386,6 +1386,7 @@ void RemoteIo::populateFakeData() { - } - } - -+#ifdef EXV_ENABLE_WEBREADY - //! Internal Pimpl structure of class HttpIo. - class HttpIo::HttpImpl : public Impl { - public: -@@ -1525,6 +1526,7 @@ void HttpIo::HttpImpl::writeRemote(const byte* data, size_t size, size_t from, s - HttpIo::HttpIo(const std::string& url, size_t blockSize) { - p_ = std::make_unique(url, blockSize); - } -+#endif - - #ifdef EXV_USE_CURL - //! Internal Pimpl structure of class RemoteIo. -diff --git a/src/image.cpp b/src/image.cpp -index 9bbe1f272..e65ec7464 100644 ---- a/src/image.cpp -+++ b/src/image.cpp -@@ -777,7 +777,7 @@ ImageType ImageFactory::getType(BasicIo& io) { - } - - BasicIo::UniquePtr ImageFactory::createIo(const std::string& path, bool useCurl) { -- Protocol fProt = fileProtocol(path); -+ [[maybe_unused]] Protocol fProt = fileProtocol(path); - - #ifdef EXV_USE_CURL - if (useCurl && (fProt == pHttp || fProt == pHttps || fProt == pFtp)) { -@@ -785,8 +785,10 @@ BasicIo::UniquePtr ImageFactory::createIo(const std::string& path, bool useCurl) - } - #endif - -+#ifdef EXV_ENABLE_WEBREADY - if (fProt == pHttp) - return std::make_unique(path); // may throw -+#endif - #ifdef EXV_ENABLE_FILESYSTEM - if (fProt == pFileUri) - return std::make_unique(pathOfFileUrl(path)); --- -2.39.3 (Apple Git-146) - diff --git a/tools/depends/target/exiv2/0003-UWP-Disable-getLoadedLibraries.patch b/tools/depends/target/exiv2/0003-UWP-Disable-getLoadedLibraries.patch deleted file mode 100644 index 41047f7056cc6..0000000000000 --- a/tools/depends/target/exiv2/0003-UWP-Disable-getLoadedLibraries.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 8affb24ed2c704462cf49f8069954acdfe1f8f79 Mon Sep 17 00:00:00 2001 -From: Miguel Borges de Freitas <92enen@gmail.com> -Date: Mon, 17 Jun 2024 13:29:29 +0100 -Subject: [PATCH] [UWP] Disable getLoadedLibraries - ---- - src/version.cpp | 21 ++++++++++++--------- - 1 file changed, 12 insertions(+), 9 deletions(-) - -diff --git a/src/version.cpp b/src/version.cpp -index 7e8c0b98a..8dcc11ece 100644 ---- a/src/version.cpp -+++ b/src/version.cpp -@@ -122,16 +122,19 @@ static std::vector getLoadedLibraries() { - std::string path; - - #if defined(_WIN32) || defined(__CYGWIN__) -- // enumerate loaded libraries and determine path to executable -- HMODULE handles[200]; -- DWORD cbNeeded; -- if (EnumProcessModules(GetCurrentProcess(), handles, static_cast(std::size(handles)), &cbNeeded)) { -- char szFilename[_MAX_PATH]; -- for (DWORD h = 0; h < cbNeeded / sizeof(handles[0]); h++) { -- GetModuleFileNameA(handles[h], szFilename, static_cast(std::size(szFilename))); -- pushPath(szFilename, libs, paths); -- } -+ #include -+ #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_APP) -+ // enumerate loaded libraries and determine path to executable -+ HMODULE handles[200]; -+ DWORD cbNeeded; -+ if (EnumProcessModules(GetCurrentProcess(), handles, static_cast(std::size(handles)), &cbNeeded)) { -+ char szFilename[_MAX_PATH]; -+ for (DWORD h = 0; h < cbNeeded / sizeof(handles[0]); h++) { -+ GetModuleFileNameA(handles[h], szFilename, static_cast(std::size(szFilename))); -+ pushPath(szFilename, libs, paths); -+ } - } -+ #endif - #elif defined(__APPLE__) - // man 3 dyld - uint32_t count = _dyld_image_count(); --- -2.39.3 (Apple Git-146) - diff --git a/tools/depends/target/exiv2/0005-GCC13-cstdint.patch b/tools/depends/target/exiv2/0005-GCC13-cstdint.patch deleted file mode 100644 index 72746e0605904..0000000000000 --- a/tools/depends/target/exiv2/0005-GCC13-cstdint.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/src/futils.cpp -+++ b/src/futils.cpp -@@ -11,6 +11,7 @@ - // + standard includes - #include - #include -+#include - #include - #include - #include diff --git a/tools/depends/target/exiv2/EXIV2-VERSION b/tools/depends/target/exiv2/EXIV2-VERSION index c07af28a90f08..3f4bcd18ce755 100644 --- a/tools/depends/target/exiv2/EXIV2-VERSION +++ b/tools/depends/target/exiv2/EXIV2-VERSION @@ -1,6 +1,6 @@ LIBNAME=exiv2 -VERSION=0.28.2 +VERSION=0.28.3 ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz -SHA512=197cc607c0271b5731714713283756250031cef81ba7ed5d9c3e222b4c2397966cc2bbdbceaae706598329dde6f8a9729597d0ae4c36ac264c76546942e4e37b +SHA512=c8338a118feefa104d73932890c732247c884ab9ce1d170c43a22ab5884517a0e2a7fd1febde7705b8290fbbbc29e64738610404816e4db2b56a70fc444ca049 BYPRODUCT=libexiv2.a BYPRODUCT_WIN=exiv2.lib diff --git a/tools/depends/target/exiv2/Makefile b/tools/depends/target/exiv2/Makefile index b289e02eb3608..d0a9595462098 100644 --- a/tools/depends/target/exiv2/Makefile +++ b/tools/depends/target/exiv2/Makefile @@ -1,10 +1,6 @@ include ../../Makefile.include EXIV2-VERSION ../../download-files.include DEPS = ../../Makefile.include Makefile EXIV2-VERSION ../../download-files.include \ - 0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch \ - 0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch \ - 0003-UWP-Disable-getLoadedLibraries.patch \ - 0004-WIN-lib-postfix.patch \ - 0005-GCC13-cstdint.patch + 0001-WIN-lib-postfix.patch # configuration settings CMAKE_OPTIONS=-DCMAKE_INSTALL_PREFIX=$(PREFIX) \ @@ -29,11 +25,7 @@ all: .installed-$(PLATFORM) $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) rm -rf $(PLATFORM); mkdir -p $(PLATFORM)/build cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) - cd $(PLATFORM); patch -p1 -i ../0001-Add-EXIV2_ENABLE_FILESYSTEM_ACCESS-option.patch - cd $(PLATFORM); patch -p1 -i ../0002-Set-conditional-HTTP-depending-on-EXIV2_ENABLE_WEBRE.patch - cd $(PLATFORM); patch -p1 -i ../0003-UWP-Disable-getLoadedLibraries.patch - cd $(PLATFORM); patch -p1 -i ../0004-WIN-lib-postfix.patch - cd $(PLATFORM); patch -p1 -i ../0005-GCC13-cstdint.patch + cd $(PLATFORM); patch -p1 -i ../0001-WIN-lib-postfix.patch cd $(PLATFORM)/build; $(CMAKE) $(CMAKE_OPTIONS) .. $(LIBDYLIB): $(PLATFORM) From 8425bac2038c6404059e1d8cb99e65707ab0999e Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 29 Jun 2024 00:23:07 +0200 Subject: [PATCH 272/651] [interfaces] CPVROperations: Improve const-correctness. Modernize C++. clang-format. --- xbmc/interfaces/json-rpc/PVROperations.cpp | 284 ++++++++++++++------- 1 file changed, 186 insertions(+), 98 deletions(-) diff --git a/xbmc/interfaces/json-rpc/PVROperations.cpp b/xbmc/interfaces/json-rpc/PVROperations.cpp index 597d8b850deb3..3665b3773cebc 100644 --- a/xbmc/interfaces/json-rpc/PVROperations.cpp +++ b/xbmc/interfaces/json-rpc/PVROperations.cpp @@ -34,18 +34,22 @@ using namespace JSONRPC; using namespace PVR; using namespace KODI::MESSAGING; -JSONRPC_STATUS CPVROperations::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::GetProperties(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - CVariant properties = CVariant(CVariant::VariantTypeObject); - for (unsigned int index = 0; index < parameterObject["properties"].size(); index++) + CVariant properties{CVariant::VariantTypeObject}; + for (unsigned int index = 0; index < parameterObject["properties"].size(); ++index) { - std::string propertyName = parameterObject["properties"][index].asString(); + const std::string propertyName{parameterObject["properties"][index].asString()}; CVariant property; - JSONRPC_STATUS ret; - if ((ret = GetPropertyValue(propertyName, property)) != OK) + const JSONRPC_STATUS ret{GetPropertyValue(propertyName, property)}; + if (ret != OK) return ret; properties[propertyName] = property; @@ -56,47 +60,58 @@ JSONRPC_STATUS CPVROperations::GetProperties(const std::string &method, ITranspo return OK; } -JSONRPC_STATUS CPVROperations::GetChannelGroups(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::GetChannelGroups(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - std::shared_ptr channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + const std::shared_ptr channelGroupContainer{ + CServiceBroker::GetPVRManager().ChannelGroups()}; if (!channelGroupContainer) return FailedToExecute; const std::shared_ptr channelGroups{ - channelGroupContainer->Get(parameterObject["channeltype"].asString().compare("radio") == 0)}; + channelGroupContainer->Get(parameterObject["channeltype"].asString() == "radio")}; if (!channelGroups) return FailedToExecute; - int start, end; + int start{0}; + int end{0}; - std::vector> groupList = channelGroups->GetMembers(true); - HandleLimits(parameterObject, result, groupList.size(), start, end); - for (int index = start; index < end; index++) + std::vector> groupList{channelGroups->GetMembers(true)}; + HandleLimits(parameterObject, result, static_cast(groupList.size()), start, end); + for (int index = start; index < end; ++index) FillChannelGroupDetails(groupList.at(index), parameterObject, result["channelgroups"], true); return OK; } -JSONRPC_STATUS CPVROperations::GetChannelGroupDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::GetChannelGroupDetails(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - std::shared_ptr channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + const std::shared_ptr channelGroupContainer{ + CServiceBroker::GetPVRManager().ChannelGroups()}; if (!channelGroupContainer) return FailedToExecute; - std::shared_ptr channelGroup; - CVariant id = parameterObject["channelgroupid"]; + std::shared_ptr channelGroup; + const CVariant id{parameterObject["channelgroupid"]}; if (id.isInteger()) - channelGroup = channelGroupContainer->GetByIdFromAll((int)id.asInteger()); + channelGroup = channelGroupContainer->GetByIdFromAll(static_cast(id.asInteger())); else if (id.isString()) channelGroup = channelGroupContainer->GetGroupAll(id.asString() == "allradio"); - if (channelGroup == NULL) + if (!channelGroup) return InvalidParams; FillChannelGroupDetails(channelGroup, parameterObject, result["channelgroupdetails"], false); @@ -104,23 +119,28 @@ JSONRPC_STATUS CPVROperations::GetChannelGroupDetails(const std::string &method, return OK; } -JSONRPC_STATUS CPVROperations::GetChannels(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::GetChannels(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - std::shared_ptr channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + const std::shared_ptr channelGroupContainer{ + CServiceBroker::GetPVRManager().ChannelGroups()}; if (!channelGroupContainer) return FailedToExecute; - std::shared_ptr channelGroup; - CVariant id = parameterObject["channelgroupid"]; + std::shared_ptr channelGroup; + const CVariant id{parameterObject["channelgroupid"]}; if (id.isInteger()) - channelGroup = channelGroupContainer->GetByIdFromAll((int)id.asInteger()); + channelGroup = channelGroupContainer->GetByIdFromAll(static_cast(id.asInteger())); else if (id.isString()) channelGroup = channelGroupContainer->GetGroupAll(id.asString() == "allradio"); - if (channelGroup == NULL) + if (!channelGroup) return InvalidParams; CFileItemList channels; @@ -135,22 +155,27 @@ JSONRPC_STATUS CPVROperations::GetChannels(const std::string &method, ITransport return OK; } -JSONRPC_STATUS CPVROperations::GetChannelDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::GetChannelDetails(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - std::shared_ptr channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + const std::shared_ptr channelGroupContainer{ + CServiceBroker::GetPVRManager().ChannelGroups()}; if (!channelGroupContainer) return FailedToExecute; - std::shared_ptr channel = channelGroupContainer->GetChannelById( - static_cast(parameterObject["channelid"].asInteger())); - if (channel == NULL) + const std::shared_ptr channel{channelGroupContainer->GetChannelById( + static_cast(parameterObject["channelid"].asInteger()))}; + if (!channel) return InvalidParams; - const std::shared_ptr groupMember = - CServiceBroker::GetPVRManager().Get().GetChannelGroupMember(channel); + const std::shared_ptr groupMember{ + CServiceBroker::GetPVRManager().Get().GetChannelGroupMember(channel)}; if (!groupMember) return InvalidParams; @@ -169,54 +194,68 @@ JSONRPC_STATUS CPVROperations::GetClients(const std::string& method, if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - int start, end; - + int start{0}; + int end{0}; auto clientInfos = CServiceBroker::GetPVRManager().Clients()->GetEnabledClientInfos(); - HandleLimits(parameterObject, result, clientInfos.size(), start, end); - for (int index = start; index < end; index++) + HandleLimits(parameterObject, result, static_cast(clientInfos.size()), start, end); + + for (int index = start; index < end; ++index) + { result["clients"].append(clientInfos[index]); + } return OK; } -JSONRPC_STATUS CPVROperations::GetBroadcasts(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::GetBroadcasts(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - std::shared_ptr channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + const std::shared_ptr channelGroupContainer{ + CServiceBroker::GetPVRManager().ChannelGroups()}; if (!channelGroupContainer) return FailedToExecute; - std::shared_ptr channel = channelGroupContainer->GetChannelById((int)parameterObject["channelid"].asInteger()); - if (channel == NULL) + const std::shared_ptr channel{channelGroupContainer->GetChannelById( + static_cast(parameterObject["channelid"].asInteger()))}; + if (!channel) return InvalidParams; - std::shared_ptr channelEpg = channel->GetEPG(); + const std::shared_ptr channelEpg{channel->GetEPG()}; if (!channelEpg) return InternalError; CFileItemList programFull; - const std::vector> tags = channelEpg->GetTags(); + const std::vector> tags{channelEpg->GetTags()}; for (const auto& tag : tags) { programFull.Add(std::make_shared(tag)); } - HandleFileItemList("broadcastid", false, "broadcasts", programFull, parameterObject, result, programFull.Size(), true); + HandleFileItemList("broadcastid", false, "broadcasts", programFull, parameterObject, result, + programFull.Size(), true); return OK; } -JSONRPC_STATUS CPVROperations::GetBroadcastDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::GetBroadcastDetails(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - const std::shared_ptr epgTag = + const std::shared_ptr epgTag{ CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( - parameterObject["broadcastid"].asInteger()); + static_cast(parameterObject["broadcastid"].asInteger()))}; if (!epgTag) return InvalidParams; @@ -236,9 +275,9 @@ JSONRPC_STATUS CPVROperations::GetBroadcastIsPlayable(const std::string& method, if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - const std::shared_ptr epgTag = + const std::shared_ptr epgTag{ CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( - parameterObject["broadcastid"].asInteger()); + static_cast(parameterObject["broadcastid"].asInteger()))}; if (!epgTag) return InvalidParams; @@ -248,13 +287,17 @@ JSONRPC_STATUS CPVROperations::GetBroadcastIsPlayable(const std::string& method, return OK; } -JSONRPC_STATUS CPVROperations::Record(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::Record(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; std::shared_ptr pChannel; - CVariant channel = parameterObject["channel"]; + const CVariant channel{parameterObject["channel"]}; if (channel.isString() && channel.asString() == "current") { pChannel = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel(); @@ -263,37 +306,42 @@ JSONRPC_STATUS CPVROperations::Record(const std::string &method, ITransportLayer } else if (channel.isInteger()) { - std::shared_ptr channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + const std::shared_ptr channelGroupContainer{ + CServiceBroker::GetPVRManager().ChannelGroups()}; if (!channelGroupContainer) return FailedToExecute; - pChannel = channelGroupContainer->GetChannelById((int)channel.asInteger()); + pChannel = channelGroupContainer->GetChannelById(static_cast(channel.asInteger())); } else return InvalidParams; - if (pChannel == NULL) + if (!pChannel) return InvalidParams; else if (!pChannel->CanRecord()) return FailedToExecute; - CVariant record = parameterObject["record"]; - bool bIsRecording = CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*pChannel); + const CVariant record{parameterObject["record"]}; + const bool isRecording{CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*pChannel)}; bool toggle = true; - if (record.isBoolean() && record.asBoolean() == bIsRecording) + if (record.isBoolean() && record.asBoolean() == isRecording) toggle = false; if (toggle) { if (!CServiceBroker::GetPVRManager().Get().SetRecordingOnChannel( - pChannel, !bIsRecording)) + pChannel, !isRecording)) return FailedToExecute; } return ACK; } -JSONRPC_STATUS CPVROperations::Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::Scan(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; @@ -301,7 +349,7 @@ JSONRPC_STATUS CPVROperations::Scan(const std::string &method, ITransportLayer * if (parameterObject.isMember("clientid")) { if (CServiceBroker::GetPVRManager().Get().StartChannelScan( - parameterObject["clientid"].asInteger())) + static_cast(parameterObject["clientid"].asInteger()))) return ACK; } else @@ -313,9 +361,9 @@ JSONRPC_STATUS CPVROperations::Scan(const std::string &method, ITransportLayer * return FailedToExecute; } -JSONRPC_STATUS CPVROperations::GetPropertyValue(const std::string &property, CVariant &result) +JSONRPC_STATUS CPVROperations::GetPropertyValue(const std::string& property, CVariant& result) { - bool started = CServiceBroker::GetPVRManager().IsStarted(); + const bool started{CServiceBroker::GetPVRManager().IsStarted()}; if (property == "available") result = started; @@ -345,10 +393,10 @@ void CPVROperations::FillChannelGroupDetails( CVariant& result, bool append /* = false */) { - if (channelGroup == NULL) + if (!channelGroup) return; - CVariant object(CVariant::VariantTypeObject); + CVariant object{CVariant::VariantTypeObject}; object["channelgroupid"] = channelGroup->GroupID(); object["channeltype"] = channelGroup->IsRadio() ? "radio" : "tv"; object["label"] = channelGroup->GroupName(); @@ -358,30 +406,35 @@ void CPVROperations::FillChannelGroupDetails( else { CFileItemList channels; - const auto groupMembers = channelGroup->GetMembers(CPVRChannelGroup::Include::ONLY_VISIBLE); + const auto groupMembers{channelGroup->GetMembers(CPVRChannelGroup::Include::ONLY_VISIBLE)}; for (const auto& groupMember : groupMembers) { channels.Add(std::make_shared(groupMember)); } object["channels"] = CVariant(CVariant::VariantTypeArray); - HandleFileItemList("channelid", false, "channels", channels, parameterObject["channels"], object, false); + HandleFileItemList("channelid", false, "channels", channels, parameterObject["channels"], + object, false); result = object; } } -JSONRPC_STATUS CPVROperations::GetTimers(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::GetTimers(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - std::shared_ptr timers = CServiceBroker::GetPVRManager().Timers(); + const std::shared_ptr timers{CServiceBroker::GetPVRManager().Timers()}; if (!timers) return FailedToExecute; CFileItemList timerList; - const std::vector> tags = timers->GetAll(); + const std::vector> tags{timers->GetAll()}; for (const auto& timer : tags) { timerList.Add(std::make_shared(timer)); @@ -392,16 +445,21 @@ JSONRPC_STATUS CPVROperations::GetTimers(const std::string &method, ITransportLa return OK; } -JSONRPC_STATUS CPVROperations::GetTimerDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::GetTimerDetails(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - std::shared_ptr timers = CServiceBroker::GetPVRManager().Timers(); + const std::shared_ptr timers{CServiceBroker::GetPVRManager().Timers()}; if (!timers) return FailedToExecute; - std::shared_ptr timer = timers->GetById((int)parameterObject["timerid"].asInteger()); + const std::shared_ptr timer{ + timers->GetById(static_cast(parameterObject["timerid"].asInteger()))}; if (!timer) return InvalidParams; @@ -411,14 +469,18 @@ JSONRPC_STATUS CPVROperations::GetTimerDetails(const std::string &method, ITrans return OK; } -JSONRPC_STATUS CPVROperations::AddTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::AddTimer(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - const std::shared_ptr epgTag = + const std::shared_ptr epgTag{ CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( - parameterObject["broadcastid"].asInteger()); + static_cast(parameterObject["broadcastid"].asInteger()))}; if (!epgTag) return InvalidParams; @@ -426,9 +488,9 @@ JSONRPC_STATUS CPVROperations::AddTimer(const std::string &method, ITransportLay if (CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag)) return InvalidParams; - const std::shared_ptr newTimer = + const std::shared_ptr newTimer{ CPVRTimerInfoTag::CreateFromEpg(epgTag, parameterObject["timerrule"].asBoolean(false), - parameterObject["reminder"].asBoolean(false)); + parameterObject["reminder"].asBoolean(false))}; if (newTimer) { if (CServiceBroker::GetPVRManager().Get().AddTimer(newTimer)) @@ -437,17 +499,21 @@ JSONRPC_STATUS CPVROperations::AddTimer(const std::string &method, ITransportLay return FailedToExecute; } - -JSONRPC_STATUS CPVROperations::DeleteTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::DeleteTimer(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - std::shared_ptr timers = CServiceBroker::GetPVRManager().Timers(); + const std::shared_ptr timers{CServiceBroker::GetPVRManager().Timers()}; if (!timers) return FailedToExecute; - std::shared_ptr timer = timers->GetById(parameterObject["timerid"].asInteger()); + const std::shared_ptr timer{ + timers->GetById(static_cast(parameterObject["timerid"].asInteger()))}; if (!timer) return InvalidParams; @@ -457,28 +523,37 @@ JSONRPC_STATUS CPVROperations::DeleteTimer(const std::string &method, ITransport return FailedToExecute; } -JSONRPC_STATUS CPVROperations::ToggleTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::ToggleTimer(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - const std::shared_ptr epgTag = + const std::shared_ptr epgTag{ CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( - parameterObject["broadcastid"].asInteger()); + static_cast(parameterObject["broadcastid"].asInteger()))}; if (!epgTag) return InvalidParams; - bool timerrule = parameterObject["timerrule"].asBoolean(false); + const std::shared_ptr timers{CServiceBroker::GetPVRManager().Timers()}; + if (!timers) + return FailedToExecute; + + const bool timerrule{parameterObject["timerrule"].asBoolean(false)}; bool sentOkay = false; - std::shared_ptr timer = CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag); + std::shared_ptr timer{timers->GetTimerForEpgTag(epgTag)}; if (timer) { if (timerrule) - timer = CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer); + timer = timers->GetTimerRule(timer); if (timer) - sentOkay = (CServiceBroker::GetPVRManager().Timers()->DeleteTimer(timer, timer->IsRecording(), false) == TimerOperationResult::OK); + sentOkay = + (timers->DeleteTimer(timer, timer->IsRecording(), false) == TimerOperationResult::OK); } else { @@ -495,41 +570,54 @@ JSONRPC_STATUS CPVROperations::ToggleTimer(const std::string &method, ITransport return FailedToExecute; } -JSONRPC_STATUS CPVROperations::GetRecordings(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::GetRecordings(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - std::shared_ptr recordings = CServiceBroker::GetPVRManager().Recordings(); + const std::shared_ptr recordings{ + CServiceBroker::GetPVRManager().Recordings()}; if (!recordings) return FailedToExecute; CFileItemList recordingsList; - const std::vector> recs = recordings->GetAll(); + const std::vector> recs{recordings->GetAll()}; for (const auto& recording : recs) { recordingsList.Add(std::make_shared(recording)); } - HandleFileItemList("recordingid", true, "recordings", recordingsList, parameterObject, result, true); + HandleFileItemList("recordingid", true, "recordings", recordingsList, parameterObject, result, + true); return OK; } -JSONRPC_STATUS CPVROperations::GetRecordingDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +JSONRPC_STATUS CPVROperations::GetRecordingDetails(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) { if (!CServiceBroker::GetPVRManager().IsStarted()) return FailedToExecute; - std::shared_ptr recordings = CServiceBroker::GetPVRManager().Recordings(); + const std::shared_ptr recordings{ + CServiceBroker::GetPVRManager().Recordings()}; if (!recordings) return FailedToExecute; - const std::shared_ptr recording = recordings->GetById(static_cast(parameterObject["recordingid"].asInteger())); + const std::shared_ptr recording{ + recordings->GetById(static_cast(parameterObject["recordingid"].asInteger()))}; if (!recording) return InvalidParams; - HandleFileItem("recordingid", true, "recordingdetails", std::make_shared(recording), parameterObject, parameterObject["properties"], result, false); + HandleFileItem("recordingid", true, "recordingdetails", std::make_shared(recording), + parameterObject, parameterObject["properties"], result, false); return OK; } @@ -538,12 +626,12 @@ std::shared_ptr CPVROperations::GetRecordingFileItem(int recordingId) { if (CServiceBroker::GetPVRManager().IsStarted()) { - const std::shared_ptr recordings = - CServiceBroker::GetPVRManager().Recordings(); + const std::shared_ptr recordings{ + CServiceBroker::GetPVRManager().Recordings()}; if (recordings) { - const std::shared_ptr recording = recordings->GetById(recordingId); + const std::shared_ptr recording{recordings->GetById(recordingId)}; if (recording) return std::make_shared(recording); } From cd0997fac09fdff35c75359aa30c3f5a69215c2a Mon Sep 17 00:00:00 2001 From: CastagnaIT Date: Thu, 11 Jul 2024 10:05:03 +0200 Subject: [PATCH 273/651] [FileItem] Fix mimetype content lookup --- xbmc/FileItem.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 5065b51c461ad..2776627018714 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -1587,7 +1587,10 @@ void CFileItem::UpdateInfo(const CFileItem &item, bool replaceLabels /*=true*/) if (!item.GetArt().empty()) SetArt(item.GetArt()); AppendProperties(item); - UpdateMimeType(); + + SetContentLookup(item.m_doContentLookup); + SetMimeType(item.m_mimetype); + UpdateMimeType(m_doContentLookup); } void CFileItem::MergeInfo(const CFileItem& item) @@ -1657,7 +1660,10 @@ void CFileItem::MergeInfo(const CFileItem& item) SetArt(item.GetArt()); } AppendProperties(item); - UpdateMimeType(); + + SetContentLookup(item.m_doContentLookup); + SetMimeType(item.m_mimetype); + UpdateMimeType(m_doContentLookup); } void CFileItem::SetFromVideoInfoTag(const CVideoInfoTag &video) From 1f6d5190a3c333f0597cffe5ec1464e9a39e1f9e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 11 Jul 2024 18:58:29 +1000 Subject: [PATCH 274/651] [cmake] core_optional_dep will propgate *_FOUND variables for build tool searches We generally dont use targets for build tools, so we need to still propagate the *_FOUND variables through for our system to use. --- cmake/scripts/common/Macros.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index e3ae5ed9b17cf..65691615bef82 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -438,6 +438,10 @@ function(core_optional_dep) if (NOT ${depspec} IN_LIST optional_deps) set(optional_deps ${optional_deps} ${depspec} PARENT_SCOPE) endif() + else() + # Propagate _FOUND variable for build tool optional deps. We dont use targets + # for build tools in general, and still rely on variables + set(${depup}_FOUND ${${depup}_FOUND} PARENT_SCOPE) endif() elseif(_required) From 242f5044592111428cea75a07e4a9dc068d2b4fd Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 11 Jul 2024 18:59:59 +1000 Subject: [PATCH 275/651] [cmake][linux] Missed a target conversion for GLU --- cmake/scripts/linux/Install.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index 5b4216badb30c..ab489e4791b0f 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -275,7 +275,7 @@ if(ENABLE_EVENTCLIENTS) DESTINATION ${bindir} COMPONENT kodi-eventclients-ps3) - if(TARGET ${APP_NAME_LC}::Bluetooth AND ${APP_NAME_LC}::CWiid AND GLU_FOUND) + if(TARGET ${APP_NAME_LC}::Bluetooth AND TARGET ${APP_NAME_LC}::CWiid AND TARGET ${APP_NAME_LC}::GLU) # Install kodi-eventclients-wiiremote install(PROGRAMS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/WiiRemote/${APP_NAME_LC}-wiiremote DESTINATION ${bindir} From 7562fcd6d7e14150b5a3580defceb40b51713a3b Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Thu, 11 Jul 2024 22:14:43 +0100 Subject: [PATCH 276/651] [Texture] Fix NPOT condition --- xbmc/guilib/TextureBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/guilib/TextureBase.cpp b/xbmc/guilib/TextureBase.cpp index 63c510ff94922..a8ce9a91e18f0 100644 --- a/xbmc/guilib/TextureBase.cpp +++ b/xbmc/guilib/TextureBase.cpp @@ -32,7 +32,7 @@ void CTextureBase::Allocate(uint32_t width, uint32_t height, XB_FMT format) m_textureWidth = m_imageWidth; m_textureHeight = m_imageHeight; - if (!CServiceBroker::GetRenderSystem()->SupportsNPOT((m_textureFormat & KD_TEX_FMT_TYPE_MASK) != + if (!CServiceBroker::GetRenderSystem()->SupportsNPOT((m_textureFormat & KD_TEX_FMT_TYPE_MASK) == KD_TEX_FMT_S3TC)) { m_textureWidth = PadPow2(m_textureWidth); From ade0eb6ded058dbdd10b1b5bcf8dfd94fb4e913f Mon Sep 17 00:00:00 2001 From: Martin Vallevand Date: Fri, 5 Jul 2024 17:10:01 -0400 Subject: [PATCH 277/651] JSONRPC Changes for PVR Series/Episodes Expose episodename and episodepart to List.Item.All In PVR.Fields.Broadcast duplicate seasonnum as season and episodenum as episode making them available to List.Item.All calls. Flag seasonnum and episodenum as deprecated for future removeall --- xbmc/interfaces/json-rpc/schema/types.json | 16 ++++++++++++++-- xbmc/interfaces/json-rpc/schema/version.txt | 2 +- xbmc/pvr/epg/EpgInfoTag.cpp | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/xbmc/interfaces/json-rpc/schema/types.json b/xbmc/interfaces/json-rpc/schema/types.json index 4c67f7c35e9a2..d0f38892db02c 100644 --- a/xbmc/interfaces/json-rpc/schema/types.json +++ b/xbmc/interfaces/json-rpc/schema/types.json @@ -2576,6 +2576,7 @@ "progress", "progresspercentage", "genre", + "episode", "episodename", "episodenum", "episodepart", @@ -2599,6 +2600,7 @@ "isplayable", "clientid", "hasreminder", + "season", "seasonnum" ] } @@ -2640,9 +2642,13 @@ "episodename": { "type": "string" }, - "episodenum": { + "episode": { "type": "integer" }, + "episodenum": { + "type": "integer", + "description": "Deprecated - Use episode" + }, "episodepart": { "type": "integer" }, @@ -2707,8 +2713,12 @@ "hasreminder": { "type": "boolean" }, - "seasonnum": { + "season": { "type": "integer" + }, + "seasonnum": { + "type": "integer", + "description": "Deprecated - Use season" } } }, @@ -3881,6 +3891,8 @@ "firstaired", "season", "episode", + "episodepart", + "episodename", "showtitle", "thumbnail", "file", diff --git a/xbmc/interfaces/json-rpc/schema/version.txt b/xbmc/interfaces/json-rpc/schema/version.txt index 9f814eb2ecdc4..4727db30924e8 100644 --- a/xbmc/interfaces/json-rpc/schema/version.txt +++ b/xbmc/interfaces/json-rpc/schema/version.txt @@ -1 +1 @@ -JSONRPC_VERSION 13.5.0 +JSONRPC_VERSION 13.6.0 diff --git a/xbmc/pvr/epg/EpgInfoTag.cpp b/xbmc/pvr/epg/EpgInfoTag.cpp index a21894b64c7f4..0b9cef0d01d77 100644 --- a/xbmc/pvr/epg/EpgInfoTag.cpp +++ b/xbmc/pvr/epg/EpgInfoTag.cpp @@ -188,8 +188,10 @@ void CPVREpgInfoTag::Serialize(CVariant& value) const value["progress"] = Progress(); value["progresspercentage"] = ProgressPercentage(); value["episodename"] = m_strEpisodeName; + value["episode"] = m_iEpisodeNumber; value["episodenum"] = m_iEpisodeNumber; value["episodepart"] = m_iEpisodePart; + value["season"] = m_iSeriesNumber; value["seasonnum"] = m_iSeriesNumber; value["isactive"] = IsActive(); value["wasactive"] = WasActive(); From 0fc2eda21d2009a7ec732f32adbc926fc8efbb87 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 11 Jul 2024 16:09:03 +0200 Subject: [PATCH 278/651] [video] Do not show video info dialog if item has an empty video info tag. --- xbmc/video/windows/GUIWindowVideoBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index 95cb103f10b54..e93b2b02aa1ff 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -416,7 +416,7 @@ bool CGUIWindowVideoBase::ShowInfo(const CFileItemPtr& item2, const ScraperPtr& } m_database.Close(); } - else if(item->HasVideoInfoTag()) + else if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->IsEmpty()) { bHasInfo = true; movieDetails = *item->GetVideoInfoTag(); From 75fb024b9b4481aeeaa563e4b4068cec4ecede17 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 11 Jul 2024 19:18:36 +0200 Subject: [PATCH 279/651] [filesystem] CVideoDatabaseDirectory::GetDirectory: Do not create empty video info tags (which CFileItem::GetVideoInfoTag does if called from non-const methods). Only if the item already has a video info tag, it can have a path which we then set as item's dyn path. --- xbmc/filesystem/VideoDatabaseDirectory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/filesystem/VideoDatabaseDirectory.cpp b/xbmc/filesystem/VideoDatabaseDirectory.cpp index 4242b095ce44f..0afa2986ddc2b 100644 --- a/xbmc/filesystem/VideoDatabaseDirectory.cpp +++ b/xbmc/filesystem/VideoDatabaseDirectory.cpp @@ -106,7 +106,7 @@ bool CVideoDatabaseDirectory::GetDirectory(const CURL& url, CFileItemList &items if (!strImage.empty() && CServiceBroker::GetGUI()->GetTextureManager().HasTexture(strImage)) item->SetArt("icon", strImage); } - if (item->GetVideoInfoTag()) + if (item->HasVideoInfoTag()) { item->SetDynPath(item->GetVideoInfoTag()->GetPath()); } From 07f51558d8a03d997d1f6df46938c97094f1393f Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 5 Jun 2024 23:27:43 +0200 Subject: [PATCH 280/651] [PVR] Groups: Automatically create a channel group for every client, containing all channels provided by the client. Example: 2 clients enabled, create two groups 'Client 1 [All channels]' containing all channels from client 1 and 'Client 2 [All channels]' containing all channels from client 2. --- .../resources/strings.po | 8 +- xbmc/pvr/channels/CMakeLists.txt | 2 + xbmc/pvr/channels/PVRChannelGroup.h | 16 +++- .../pvr/channels/PVRChannelGroupAllChannels.h | 2 +- ...PVRChannelGroupAllChannelsSingleClient.cpp | 95 +++++++++++++++++++ .../PVRChannelGroupAllChannelsSingleClient.h | 95 +++++++++++++++++++ xbmc/pvr/channels/PVRChannelGroupFactory.cpp | 31 +++++- xbmc/pvr/channels/PVRChannelGroupFactory.h | 11 +++ xbmc/pvr/channels/PVRChannelGroups.cpp | 37 +++++++- xbmc/pvr/channels/PVRChannelGroups.h | 2 + 10 files changed, 286 insertions(+), 13 deletions(-) create mode 100644 xbmc/pvr/channels/PVRChannelGroupAllChannelsSingleClient.cpp create mode 100644 xbmc/pvr/channels/PVRChannelGroupAllChannelsSingleClient.h diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 53523fc9e6359..ab0a84adac9a3 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -3996,7 +3996,13 @@ msgctxt "#858" msgid "User" msgstr "" -#empty strings from id 859 to 996 +#. Label postfix for channel groups containing content from all channels (e.g. "PVR Client 1 [All channels]") +#: xbmc/pvr/channels/PVRChannelGroupAllChannelsSingleClient.cpp +msgctxt "#859" +msgid "{0:s} [All channels]" +msgstr "" + +#empty strings from id 860 to 996 #: xbmc/windows/GUIMediaWindow.cpp msgctxt "#997" diff --git a/xbmc/pvr/channels/CMakeLists.txt b/xbmc/pvr/channels/CMakeLists.txt index c1eb19f4df0b5..7094e029e6e15 100644 --- a/xbmc/pvr/channels/CMakeLists.txt +++ b/xbmc/pvr/channels/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES PVRChannel.cpp PVRChannelGroup.cpp PVRChannelGroupAllChannels.cpp PVRChannelGroupFactory.cpp + PVRChannelGroupAllChannelsSingleClient.cpp PVRChannelGroupFromClient.cpp PVRChannelGroupMember.cpp PVRChannelGroupSettings.cpp @@ -15,6 +16,7 @@ set(HEADERS PVRChannel.h PVRChannelGroup.h PVRChannelGroupAllChannels.h PVRChannelGroupFactory.h + PVRChannelGroupAllChannelsSingleClient.h PVRChannelGroupFromClient.h PVRChannelGroupFromUser.h PVRChannelGroupMember.h diff --git a/xbmc/pvr/channels/PVRChannelGroup.h b/xbmc/pvr/channels/PVRChannelGroup.h index 4582a273a3c46..af521abf98c26 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.h +++ b/xbmc/pvr/channels/PVRChannelGroup.h @@ -25,8 +25,9 @@ struct PVR_CHANNEL_GROUP; namespace PVR { static constexpr int PVR_GROUP_TYPE_CLIENT = 0; -static constexpr int PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS = 1; +static constexpr int PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_ALL_CLIENTS = 1; static constexpr int PVR_GROUP_TYPE_USER = 2; +static constexpr int PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_SINGLE_CLIENT = 3; static constexpr int PVR_GROUP_CLIENT_ID_UNKNOWN = -2; static constexpr int PVR_GROUP_CLIENT_ID_LOCAL = -1; @@ -526,6 +527,19 @@ class CPVRChannelGroup : public IChannelGroupSettingsCallback virtual bool ShouldBeIgnored( const std::vector>& allChannelGroups) const; + /*! + * @brief Update all group members. + * @param allChannelsGroup The all channels group. + * @param allChannelGroups All available channel groups. + * @return True on success, false otherwise. + */ + virtual bool UpdateGroupMembers( + const std::shared_ptr& allChannelsGroup, + const std::vector>& allChannelGroups) + { + return true; + } + protected: /*! * @brief Remove deleted group members from this group. diff --git a/xbmc/pvr/channels/PVRChannelGroupAllChannels.h b/xbmc/pvr/channels/PVRChannelGroupAllChannels.h index a51aadae761d6..53e166c8303cb 100644 --- a/xbmc/pvr/channels/PVRChannelGroupAllChannels.h +++ b/xbmc/pvr/channels/PVRChannelGroupAllChannels.h @@ -62,7 +62,7 @@ class CPVRChannelGroupAllChannels : public CPVRChannelGroup /*! * @brief Return the type of this group. */ - int GroupType() const override { return PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS; } + int GroupType() const override { return PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_ALL_CLIENTS; } /*! * @brief Check whether this group could be deleted by the user. diff --git a/xbmc/pvr/channels/PVRChannelGroupAllChannelsSingleClient.cpp b/xbmc/pvr/channels/PVRChannelGroupAllChannelsSingleClient.cpp new file mode 100644 index 0000000000000..40b1649ecc696 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupAllChannelsSingleClient.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024 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 "PVRChannelGroupAllChannelsSingleClient.h" + +#include "ServiceBroker.h" +#include "guilib/LocalizeStrings.h" +#include "pvr/PVRManager.h" +#include "pvr/addons/PVRClient.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/channels/PVRChannelsPath.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include +#include + +using namespace PVR; + +std::vector> CPVRChannelGroupAllChannelsSingleClient:: + CreateMissingGroups(const std::shared_ptr& allChannelsGroup, + const std::vector>& allChannelGroups) +{ + const std::vector> allGroupMembers{ + allChannelsGroup->GetMembers()}; + + // Create a unique list of active client ids from current members of the all channels list. + std::unordered_set clientIds; + for (const auto& member : allGroupMembers) + { + clientIds.insert(member->ChannelClientID()); + } + + // If only one client is active, global all channels group would be identical to the all + // channels group for the client. No need to create it. + if (clientIds.size() <= 1) + return {}; + + std::vector> addedGroups; + + for (int clientId : clientIds) + { + const std::shared_ptr client{ + CServiceBroker().GetPVRManager().GetClient(clientId)}; + if (!client) + { + CLog::LogFC(LOGERROR, LOGPVR, "Failed to get client instance for client id {}", clientId); + continue; + } + + // Create a group containg all channels for this client, if not yet existing. + const auto it = std::find_if(allChannelGroups.cbegin(), allChannelGroups.cend(), + [&client](const auto& group) + { + return (group->GroupType() == + PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_SINGLE_CLIENT) && + (group->GetClientID() == client->GetID()); + }); + if (it == allChannelGroups.cend()) + { + const std::string name{ + StringUtils::Format(g_localizeStrings.Get(859), client->GetFullClientName())}; + const CPVRChannelsPath path{allChannelsGroup->IsRadio(), name, client->GetID()}; + addedGroups.emplace_back( + std::make_shared(path, allChannelsGroup)); + } + } + + return addedGroups; +} + +bool CPVRChannelGroupAllChannelsSingleClient::UpdateGroupMembers( + const std::shared_ptr& allChannelsGroup, + const std::vector>& allChannelGroups) +{ + std::vector> groupMembers; + + // Collect and populate matching members. + const auto allChannelsGroupMembers{allChannelsGroup->GetMembers()}; + for (const auto& member : allChannelsGroupMembers) + { + if (member->ChannelClientID() != GetClientID()) + continue; + + groupMembers.emplace_back(std::make_shared( + GroupID(), GroupName(), GetClientID(), member->Channel())); + } + + return UpdateGroupEntries(groupMembers); +} diff --git a/xbmc/pvr/channels/PVRChannelGroupAllChannelsSingleClient.h b/xbmc/pvr/channels/PVRChannelGroupAllChannelsSingleClient.h new file mode 100644 index 0000000000000..9e096c9a27c20 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupAllChannelsSingleClient.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024 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 "pvr/channels/PVRChannelGroup.h" + +namespace PVR +{ + +class CPVRChannelGroupAllChannelsSingleClient : public CPVRChannelGroup +{ +public: + /*! + * @brief Create a new channel group instance. + * @param path The channel group path. + * @param allChannelsGroup The channel group containing all TV or radio channels. + */ + CPVRChannelGroupAllChannelsSingleClient( + const CPVRChannelsPath& path, const std::shared_ptr& allChannelsGroup) + : CPVRChannelGroup(path, allChannelsGroup) + { + } + + /*! + * @brief Get the group's origin. + * @return The origin. + */ + Origin GetOrigin() const override { return Origin::SYSTEM; } + + /*! + * @brief Return the type of this group. + */ + int GroupType() const override { return PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_SINGLE_CLIENT; } + + /*! + * @brief Check whether this group could be deleted by the user. + * @return True if the group could be deleted, false otherwise. + */ + bool SupportsDelete() const override { return false; } + + /*! + * @brief Check whether members could be added to this group by the user. + * @return True if members could be added, false otherwise. + */ + bool SupportsMemberAdd() const override { return false; } + + /*! + * @brief Check whether members could be removed from this group by the user. + * @return True if members could be removed, false otherwise. + */ + bool SupportsMemberRemove() const override { return false; } + + /*! + * @brief Check whether this group is owner of the channel instances it contains. + * @return True if owner, false otherwise. + */ + bool IsChannelsOwner() const override { return false; } + + /*! + * @brief Update data with channel group members from the given clients, sync with local data. + * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. + * @return True on success, false otherwise. + */ + bool UpdateFromClients(const std::vector>& clients) override + { + return true; // Nothing to update from any client for locally managed groups. + } + + /*! + * @brief Create any missing channel groups (e.g. after an update of groups/members/clients). + * @param allChannelsGroup The all channels group. + * @param allChannelGroups All channel groups. + * @return The newly created groups, if any. + */ + static std::vector> CreateMissingGroups( + const std::shared_ptr& allChannelsGroup, + const std::vector>& allChannelGroups); + + /*! + * @brief Update all group members. + * @param allChannelsGroup The all channels group. + * @param allChannelGroups All available channel groups. + * @return True on success, false otherwise. + */ + bool UpdateGroupMembers( + const std::shared_ptr& allChannelsGroup, + const std::vector>& allChannelGroups) override; +}; +} // namespace PVR diff --git a/xbmc/pvr/channels/PVRChannelGroupFactory.cpp b/xbmc/pvr/channels/PVRChannelGroupFactory.cpp index a895b5355449f..0eec8b5e0881b 100644 --- a/xbmc/pvr/channels/PVRChannelGroupFactory.cpp +++ b/xbmc/pvr/channels/PVRChannelGroupFactory.cpp @@ -9,6 +9,7 @@ #include "PVRChannelGroupFactory.h" #include "pvr/channels/PVRChannelGroupAllChannels.h" +#include "pvr/channels/PVRChannelGroupAllChannelsSingleClient.h" #include "pvr/channels/PVRChannelGroupFromClient.h" #include "pvr/channels/PVRChannelGroupFromUser.h" #include "pvr/channels/PVRChannelsPath.h" @@ -45,10 +46,12 @@ std::shared_ptr CPVRChannelGroupFactory::CreateGroup( { switch (groupType) { - case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS: + case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_ALL_CLIENTS: return std::make_shared(groupPath); case PVR_GROUP_TYPE_USER: return std::make_shared(groupPath, allChannels); + case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_SINGLE_CLIENT: + return std::make_shared(groupPath, allChannels); case PVR_GROUP_TYPE_CLIENT: return std::make_shared(groupPath, allChannels); default: @@ -63,13 +66,31 @@ int CPVRChannelGroupFactory::GetGroupTypePriority( { switch (group->GroupType()) { - case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS: + // System groups, created and managed by Kodi + case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_ALL_CLIENTS: return 0; // highest - case PVR_GROUP_TYPE_USER: + case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_SINGLE_CLIENT: return 1; + + // User groups, created and managed by the user + case PVR_GROUP_TYPE_USER: + return 20; + + // Client groups, created and managed by a PVR client add-on case PVR_GROUP_TYPE_CLIENT: - return 2; + return 40; + default: - return 3; + CLog::LogFC(LOGWARNING, LOGPVR, "Using default priority for group '{}' with type {}'.", + group->GroupName(), group->GroupType()); + return 60; } } + +std::vector> CPVRChannelGroupFactory::CreateMissingGroups( + const std::shared_ptr& allChannelsGroup, + const std::vector>& allChannelGroups) +{ + return CPVRChannelGroupAllChannelsSingleClient::CreateMissingGroups(allChannelsGroup, + allChannelGroups); +} diff --git a/xbmc/pvr/channels/PVRChannelGroupFactory.h b/xbmc/pvr/channels/PVRChannelGroupFactory.h index 569eb9b3324c2..8f126b09d35c1 100644 --- a/xbmc/pvr/channels/PVRChannelGroupFactory.h +++ b/xbmc/pvr/channels/PVRChannelGroupFactory.h @@ -9,6 +9,7 @@ #pragma once #include +#include struct PVR_CHANNEL_GROUP; @@ -73,5 +74,15 @@ class CPVRChannelGroupFactory * @return The group's type priority. */ int GetGroupTypePriority(const std::shared_ptr& group) const; + + /*! + * @brief Create any missing channel groups (e.g. after an update of groups/members/clients). + * @param allChannelsGroup The all channels group. + * @param allChannelGroups All channel groups. + * @return The newly created groups, if any. + */ + std::vector> CreateMissingGroups( + const std::shared_ptr& allChannelsGroup, + const std::vector>& allChannelGroups); }; } // namespace PVR diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp index 45008d81ddf3e..97033d73fd88b 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.cpp +++ b/xbmc/pvr/channels/PVRChannelGroups.cpp @@ -359,15 +359,15 @@ bool CPVRChannelGroups::UpdateFromClients(const std::vectorGroupName()); bReturn = false; } - } - if (bReturn && group->IsChannelsOwner() && - CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bPVRChannelIconsAutoScan) - { - CServiceBroker::GetPVRManager().TriggerSearchMissingChannelIcons(group); + if (bReturn && group->IsChannelsOwner() && + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bPVRChannelIconsAutoScan) + CServiceBroker::GetPVRManager().TriggerSearchMissingChannelIcons(group); } } + UpdateSystemChannelGroups(); + if (bChannelsOnly) { // changes in the all channels group may require resorting/renumbering of other groups. @@ -381,6 +381,32 @@ bool CPVRChannelGroups::UpdateFromClients(const std::vector lock(m_critSection); + + // Update existing groups + for (const auto& group : m_groups) + { + group->UpdateGroupMembers(GetGroupAll(), m_groups); + } + + // Determine new groups needed + const std::vector> newGroups{ + GetGroupFactory()->CreateMissingGroups(GetGroupAll(), m_groups)}; + + // Update new groups + for (const auto& group : newGroups) + { + group->UpdateGroupMembers(GetGroupAll(), m_groups); + } + + if (!newGroups.empty()) + m_groups.insert(m_groups.end(), newGroups.cbegin(), newGroups.cend()); + + SortGroups(); +} + bool CPVRChannelGroups::UpdateChannelNumbersFromAllChannelsGroup() { std::unique_lock lock(m_critSection); @@ -616,6 +642,7 @@ void CPVRChannelGroups::GroupStateChanged(const std::shared_ptrPersist(); + UpdateSystemChannelGroups(); CServiceBroker::GetPVRManager().PublishEvent(PVREvent::ChannelGroupsInvalidated); } diff --git a/xbmc/pvr/channels/PVRChannelGroups.h b/xbmc/pvr/channels/PVRChannelGroups.h index 8df9c5c525936..b14a6026114dc 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.h +++ b/xbmc/pvr/channels/PVRChannelGroups.h @@ -294,6 +294,8 @@ class CPVRChannelGroups : public ISettingCallback void GroupStateChanged(const std::shared_ptr& group, GroupState state = GroupState::CHANGED); + void UpdateSystemChannelGroups(); + bool m_bRadio{false}; std::vector> m_groups; mutable CCriticalSection m_critSection; From f4aca04da6ebd004e885bc5084a75ea9f5f52972 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 8 Jun 2024 23:56:58 +0200 Subject: [PATCH 281/651] [PVR] Groups: Automatically create a channel group containing all channels from groups with equal group name. Example: A group with name 'A' is provided by two different client addons, create a group 'A [All channels]' containing all channels from both 'A' groups. --- .../resources/strings.po | 1 + xbmc/pvr/channels/CMakeLists.txt | 2 + xbmc/pvr/channels/PVRChannelGroup.h | 1 + xbmc/pvr/channels/PVRChannelGroupFactory.cpp | 17 +- .../channels/PVRChannelGroupMergedByName.cpp | 164 ++++++++++++++++++ .../channels/PVRChannelGroupMergedByName.h | 103 +++++++++++ 6 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 xbmc/pvr/channels/PVRChannelGroupMergedByName.cpp create mode 100644 xbmc/pvr/channels/PVRChannelGroupMergedByName.h diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index ab0a84adac9a3..bc5d2f735bffe 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -3998,6 +3998,7 @@ msgstr "" #. Label postfix for channel groups containing content from all channels (e.g. "PVR Client 1 [All channels]") #: xbmc/pvr/channels/PVRChannelGroupAllChannelsSingleClient.cpp +#: xbmc/pvr/channels/PVRChannelGroupMergedByName.cpp msgctxt "#859" msgid "{0:s} [All channels]" msgstr "" diff --git a/xbmc/pvr/channels/CMakeLists.txt b/xbmc/pvr/channels/CMakeLists.txt index 7094e029e6e15..54206d27a0365 100644 --- a/xbmc/pvr/channels/CMakeLists.txt +++ b/xbmc/pvr/channels/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES PVRChannel.cpp PVRChannelGroupAllChannelsSingleClient.cpp PVRChannelGroupFromClient.cpp PVRChannelGroupMember.cpp + PVRChannelGroupMergedByName.cpp PVRChannelGroupSettings.cpp PVRChannelGroups.cpp PVRChannelGroupsContainer.cpp @@ -20,6 +21,7 @@ set(HEADERS PVRChannel.h PVRChannelGroupFromClient.h PVRChannelGroupFromUser.h PVRChannelGroupMember.h + PVRChannelGroupMergedByName.h PVRChannelGroupSettings.h PVRChannelGroups.h PVRChannelGroupsContainer.h diff --git a/xbmc/pvr/channels/PVRChannelGroup.h b/xbmc/pvr/channels/PVRChannelGroup.h index af521abf98c26..b4cc6c7a3a181 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.h +++ b/xbmc/pvr/channels/PVRChannelGroup.h @@ -28,6 +28,7 @@ static constexpr int PVR_GROUP_TYPE_CLIENT = 0; static constexpr int PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_ALL_CLIENTS = 1; static constexpr int PVR_GROUP_TYPE_USER = 2; static constexpr int PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_SINGLE_CLIENT = 3; +static constexpr int PVR_GROUP_TYPE_SYSTEM_MERGED_BY_NAME = 4; static constexpr int PVR_GROUP_CLIENT_ID_UNKNOWN = -2; static constexpr int PVR_GROUP_CLIENT_ID_LOCAL = -1; diff --git a/xbmc/pvr/channels/PVRChannelGroupFactory.cpp b/xbmc/pvr/channels/PVRChannelGroupFactory.cpp index 0eec8b5e0881b..86f4f00a70452 100644 --- a/xbmc/pvr/channels/PVRChannelGroupFactory.cpp +++ b/xbmc/pvr/channels/PVRChannelGroupFactory.cpp @@ -12,6 +12,7 @@ #include "pvr/channels/PVRChannelGroupAllChannelsSingleClient.h" #include "pvr/channels/PVRChannelGroupFromClient.h" #include "pvr/channels/PVRChannelGroupFromUser.h" +#include "pvr/channels/PVRChannelGroupMergedByName.h" #include "pvr/channels/PVRChannelsPath.h" #include "utils/log.h" @@ -52,6 +53,8 @@ std::shared_ptr CPVRChannelGroupFactory::CreateGroup( return std::make_shared(groupPath, allChannels); case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_SINGLE_CLIENT: return std::make_shared(groupPath, allChannels); + case PVR_GROUP_TYPE_SYSTEM_MERGED_BY_NAME: + return std::make_shared(groupPath, allChannels); case PVR_GROUP_TYPE_CLIENT: return std::make_shared(groupPath, allChannels); default: @@ -71,6 +74,8 @@ int CPVRChannelGroupFactory::GetGroupTypePriority( return 0; // highest case PVR_GROUP_TYPE_SYSTEM_ALL_CHANNELS_SINGLE_CLIENT: return 1; + case PVR_GROUP_TYPE_SYSTEM_MERGED_BY_NAME: + return 2; // User groups, created and managed by the user case PVR_GROUP_TYPE_USER: @@ -91,6 +96,14 @@ std::vector> CPVRChannelGroupFactory::CreateMi const std::shared_ptr& allChannelsGroup, const std::vector>& allChannelGroups) { - return CPVRChannelGroupAllChannelsSingleClient::CreateMissingGroups(allChannelsGroup, - allChannelGroups); + std::vector> newGroups{ + CPVRChannelGroupAllChannelsSingleClient::CreateMissingGroups(allChannelsGroup, + allChannelGroups)}; + + std::vector> newGroupsTmp{ + CPVRChannelGroupMergedByName::CreateMissingGroups(allChannelsGroup, allChannelGroups)}; + if (!newGroupsTmp.empty()) + newGroups.insert(newGroups.end(), newGroupsTmp.cbegin(), newGroupsTmp.cend()); + + return newGroups; } diff --git a/xbmc/pvr/channels/PVRChannelGroupMergedByName.cpp b/xbmc/pvr/channels/PVRChannelGroupMergedByName.cpp new file mode 100644 index 0000000000000..067cd36b72550 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupMergedByName.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2024 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 "PVRChannelGroupMergedByName.h" + +#include "guilib/LocalizeStrings.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "utils/StringUtils.h" + +#include +#include + +using namespace PVR; + +bool CPVRChannelGroupMergedByName::ShouldBeIgnored( + const std::vector>& allChannelGroups) const +{ + std::unique_lock lock(m_critSection); + + // Ignore the group if it is empty or only members from one group are present. + + if (m_members.empty()) + return true; + + const std::string mergedGroupName{!ClientGroupName().empty() ? ClientGroupName() : GroupName()}; + static constexpr int NO_GROUP_FOUND{-1}; + int matchingGroup{NO_GROUP_FOUND}; + + for (const auto& member : m_members) + { + for (const auto& group : allChannelGroups) + { + if (group->GetOrigin() != Origin::USER && group->GetOrigin() != Origin::CLIENT) + continue; + + if (group->GroupName() != mergedGroupName && group->ClientGroupName() != mergedGroupName) + continue; + + if (group->IsGroupMember(member.second)) + { + if (matchingGroup != NO_GROUP_FOUND && matchingGroup != group->GroupID()) + { + // Found a second group containing a member of this group. This group must not be ignored. + return false; + } + // First match or no new group. Continue with next group. + matchingGroup = group->GroupID(); + } + } + } + return true; +} + +namespace +{ +std::vector GetGroupNames(const std::vector>& groups) +{ + std::unordered_map knownNamesCountMap; + + // Structure group data for easy further processing. + for (const auto& group : groups) + { + const std::string groupName{!group->ClientGroupName().empty() ? group->ClientGroupName() + : group->GroupName()}; + switch (group->GetOrigin()) + { + case CPVRChannelGroup::Origin::SYSTEM: + { + // Ignore system-created groups, except merged by name groups + if (group->GroupType() == PVR_GROUP_TYPE_SYSTEM_MERGED_BY_NAME) + { + const auto it = knownNamesCountMap.find(groupName); + if (it == knownNamesCountMap.end()) + knownNamesCountMap.insert({groupName, 0}); // remember we found a merged group + else + (*it).second = 0; // reset groups counter. we do not need a new merged group + } + break; + } + + case CPVRChannelGroup::Origin::USER: + case CPVRChannelGroup::Origin::CLIENT: + { + const auto it = knownNamesCountMap.find(groupName); + if (it == knownNamesCountMap.end()) + knownNamesCountMap.insert({groupName, 1}); // first occurance + else if ((*it).second > 0) + (*it).second++; // second+ occurance + break; + } + default: + break; + } + } + + std::vector names; + for (const auto& name : knownNamesCountMap) + { + if (name.second > 1) + names.emplace_back(name.first); + } + return names; +} +} // namespace + +std::vector> CPVRChannelGroupMergedByName::CreateMissingGroups( + const std::shared_ptr& allChannelsGroup, + const std::vector>& allChannelGroups) +{ + std::vector> addedGroups; + + const std::vector names{GetGroupNames(allChannelGroups)}; + for (const auto& name : names) + { + const std::string groupName{StringUtils::Format(g_localizeStrings.Get(859), name)}; + const CPVRChannelsPath path{allChannelsGroup->IsRadio(), groupName, PVR_GROUP_CLIENT_ID_LOCAL}; + const std::shared_ptr mergedByNameGroup{ + std::make_shared(path, allChannelsGroup)}; + mergedByNameGroup->SetClientGroupName(name); + addedGroups.emplace_back(mergedByNameGroup); + } + + return addedGroups; +} + +bool CPVRChannelGroupMergedByName::UpdateGroupMembers( + const std::shared_ptr& allChannelsGroup, + const std::vector>& allChannelGroups) +{ + std::vector> groupMembers; + + // Collect and populate matching members. + for (const auto& group : allChannelGroups) + { + const std::string groupName{!group->ClientGroupName().empty() ? group->ClientGroupName() + : group->GroupName()}; + if (groupName != ClientGroupName()) + continue; + + switch (group->GetOrigin()) + { + case CPVRChannelGroup::Origin::USER: + case CPVRChannelGroup::Origin::CLIENT: + { + const auto members{group->GetMembers()}; + for (const auto& member : members) + { + groupMembers.emplace_back(std::make_shared( + GroupID(), GroupName(), GetClientID(), member->Channel())); + } + break; + } + default: + break; + } + } + + return UpdateGroupEntries(groupMembers); +} diff --git a/xbmc/pvr/channels/PVRChannelGroupMergedByName.h b/xbmc/pvr/channels/PVRChannelGroupMergedByName.h new file mode 100644 index 0000000000000..b7f7fbdc58380 --- /dev/null +++ b/xbmc/pvr/channels/PVRChannelGroupMergedByName.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2024 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 "pvr/channels/PVRChannelGroup.h" + +namespace PVR +{ + +class CPVRChannelGroupMergedByName : public CPVRChannelGroup +{ +public: + /*! + * @brief Create a new channel group instance. + * @param path The channel group path. + * @param allChannelsGroup The channel group containing all TV or radio channels. + */ + CPVRChannelGroupMergedByName(const CPVRChannelsPath& path, + const std::shared_ptr& allChannelsGroup) + : CPVRChannelGroup(path, allChannelsGroup) + { + } + + /*! + * @brief Get the group's origin. + * @return The origin. + */ + Origin GetOrigin() const override { return Origin::SYSTEM; } + + /*! + * @brief Return the type of this group. + */ + int GroupType() const override { return PVR_GROUP_TYPE_SYSTEM_MERGED_BY_NAME; } + + /*! + * @brief Check whether this group could be deleted by the user. + * @return True if the group could be deleted, false otherwise. + */ + bool SupportsDelete() const override { return false; } + + /*! + * @brief Check whether members could be added to this group by the user. + * @return True if members could be added, false otherwise. + */ + bool SupportsMemberAdd() const override { return false; } + + /*! + * @brief Check whether members could be removed from this group by the user. + * @return True if members could be removed, false otherwise. + */ + bool SupportsMemberRemove() const override { return false; } + + /*! + * @brief Check whether this group is owner of the channel instances it contains. + * @return True if owner, false otherwise. + */ + bool IsChannelsOwner() const override { return false; } + + /*! + * @brief Update data with channel group members from the given clients, sync with local data. + * @param clients The clients to fetch data from. Leave empty to fetch data from all created clients. + * @return True on success, false otherwise. + */ + bool UpdateFromClients(const std::vector>& clients) override + { + return true; // Nothing to update from any client for locally managed groups. + } + + /*! + * @brief Check whether this group should be ignored, e.g. not presented to the user and API. + * @param allChannelGroups All available channel groups. + * @return True if to be ignored, false otherwise. + */ + bool ShouldBeIgnored( + const std::vector>& allChannelGroups) const override; + + /*! + * @brief Create any missing channel groups (e.g. after an update of groups/members/clients). + * @param allChannelsGroup The all channels group. + * @param allChannelGroups All channel groups. + * @return The newly created groups, if any. + */ + static std::vector> CreateMissingGroups( + const std::shared_ptr& allChannelsGroup, + const std::vector>& allChannelGroups); + + /*! + * @brief Update all group members. + * @param allChannelsGroup The all channels group. + * @param allChannelGroups All available channel groups. + * @return True on success, false otherwise. + */ + bool UpdateGroupMembers( + const std::shared_ptr& allChannelsGroup, + const std::vector>& allChannelGroups) override; +}; +} // namespace PVR From 274e4e14a7c9ff1bd9a0cbea278ad715f9212b28 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:02:49 +0200 Subject: [PATCH 282/651] [FileItem][PVR] Remove duplicate code to calculate the title for an EPG tag, taking settings and parental controls into account. --- xbmc/FileItem.cpp | 26 ++++---------------------- xbmc/pvr/guilib/PVRGUIActionsEPG.cpp | 20 ++++++++++++++++++++ xbmc/pvr/guilib/PVRGUIActionsEPG.h | 15 +++++++++++++++ xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 25 ++++++------------------- 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 2776627018714..574f1ac4b27e7 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -43,6 +43,7 @@ #include "pvr/epg/EpgInfoTag.h" #include "pvr/epg/EpgSearchFilter.h" #include "pvr/guilib/PVRGUIActionsChannels.h" +#include "pvr/guilib/PVRGUIActionsEPG.h" #include "pvr/guilib/PVRGUIActionsUtils.h" #include "pvr/recordings/PVRRecording.h" #include "pvr/timers/PVRTimerInfoTag.h" @@ -124,37 +125,18 @@ CFileItem::CFileItem(const CVideoInfoTag& movie) SetFromVideoInfoTag(movie); } -namespace -{ -std::string GetEpgTagTitle(const std::shared_ptr& epgTag) -{ - if (CServiceBroker::GetPVRManager().IsParentalLocked(epgTag)) - return g_localizeStrings.Get(19266); // Parental locked - else if (epgTag->Title().empty() && - !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( - CSettings::SETTING_EPG_HIDENOINFOAVAILABLE)) - return g_localizeStrings.Get(19055); // no information available - else - return epgTag->Title(); -} -} // unnamed namespace - void CFileItem::FillMusicInfoTag(const std::shared_ptr& tag) { CMusicInfoTag* musictag = GetMusicInfoTag(); // create (!) the music tag. + musictag->SetTitle(CServiceBroker::GetPVRManager().Get().GetTitleForEpgTag(tag)); + if (tag) { - musictag->SetTitle(GetEpgTagTitle(tag)); musictag->SetGenre(tag->Genre()); musictag->SetDuration(tag->GetDuration()); musictag->SetURL(tag->Path()); } - else if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( - CSettings::SETTING_EPG_HIDENOINFOAVAILABLE)) - { - musictag->SetTitle(g_localizeStrings.Get(19055)); // no information available - } musictag->SetLoaded(true); } @@ -167,7 +149,7 @@ CFileItem::CFileItem(const std::shared_ptr& tag) m_epgInfoTag = tag; m_strPath = tag->Path(); m_bCanQueue = false; - SetLabel(GetEpgTagTitle(tag)); + SetLabel(CServiceBroker::GetPVRManager().Get().GetTitleForEpgTag(tag)); m_dateTime = tag->StartAsLocalTime(); if (!tag->IconPath().empty()) diff --git a/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp b/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp index 15d3aedd081ac..8dcf7113eb1e5 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp @@ -21,9 +21,12 @@ #include "pvr/dialogs/GUIDialogPVRChannelGuide.h" #include "pvr/dialogs/GUIDialogPVRGuideInfo.h" #include "pvr/epg/EpgContainer.h" +#include "pvr/epg/EpgInfoTag.h" #include "pvr/epg/EpgSearchFilter.h" #include "pvr/guilib/PVRGUIActionsParentalControl.h" #include "pvr/windows/GUIWindowPVRSearch.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" #include "utils/Variant.h" #include "utils/log.h" @@ -215,3 +218,20 @@ bool CPVRGUIActionsEPG::DeleteSavedSearch(const CFileItem& item) } return false; } + +std::string CPVRGUIActionsEPG::GetTitleForEpgTag(const std::shared_ptr& tag) +{ + if (tag) + { + if (CServiceBroker::GetPVRManager().IsParentalLocked(tag)) + return g_localizeStrings.Get(19266); // Parental locked + else if (!tag->Title().empty()) + return tag->Title(); + } + + if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_EPG_HIDENOINFOAVAILABLE)) + return g_localizeStrings.Get(19055); // no information available + + return {}; +} diff --git a/xbmc/pvr/guilib/PVRGUIActionsEPG.h b/xbmc/pvr/guilib/PVRGUIActionsEPG.h index e0a3edcf8c827..c66740e98503f 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsEPG.h +++ b/xbmc/pvr/guilib/PVRGUIActionsEPG.h @@ -10,10 +10,15 @@ #include "pvr/IPVRComponent.h" +#include +#include + class CFileItem; namespace PVR { +class CPVREpgInfoTag; + class CPVRGUIActionsEPG : public IPVRComponent { public: @@ -70,6 +75,16 @@ class CPVRGUIActionsEPG : public IPVRComponent */ bool DeleteSavedSearch(const CFileItem& item); + /*! + * @brief Get the title for the given EPG tag, taking settings and parental controls into account. + * @param item The EPG tag. + * @return "Parental locked" in case the access to the information is restricted by parental + * controls, "No information avilable" if the real EPG event title is empty and + * SETTING_EPG_HIDENOINFOAVAILABLE is false or tag is nullptr, the real EPG event title as given + * by the EPG data provider otherwise. + */ + std::string GetTitleForEpgTag(const std::shared_ptr& tag); + private: CPVRGUIActionsEPG(const CPVRGUIActionsEPG&) = delete; CPVRGUIActionsEPG const& operator=(CPVRGUIActionsEPG const&) = delete; diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index c259759112970..9df61c25a69e4 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -34,6 +34,7 @@ #include "pvr/epg/EpgInfoTag.h" #include "pvr/epg/EpgSearchFilter.h" #include "pvr/guilib/PVRGUIActionsChannels.h" +#include "pvr/guilib/PVRGUIActionsEPG.h" #include "pvr/providers/PVRProvider.h" #include "pvr/providers/PVRProviders.h" #include "pvr/recordings/PVRRecording.h" @@ -361,23 +362,6 @@ std::string GetAsLocalizedDateTimeString(const CDateTime& datetime) return datetime.IsValid() ? datetime.GetAsLocalizedDateTime(false, false) : ""; } -std::string GetEpgTagTitle(const std::shared_ptr& epgTag) -{ - if (epgTag) - { - if (CServiceBroker::GetPVRManager().IsParentalLocked(epgTag)) - return g_localizeStrings.Get(19266); // Parental locked - else if (!epgTag->Title().empty()) - return epgTag->Title(); - } - - if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( - CSettings::SETTING_EPG_HIDENOINFOAVAILABLE)) - return g_localizeStrings.Get(19055); // no information available - - return {}; -} - } // unnamed namespace bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, @@ -683,7 +667,7 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, case LISTITEM_EPG_EVENT_TITLE: // Note: in difference to LISTITEM_TITLE, LISTITEM_EPG_EVENT_TITLE returns the title // associated with the epg event of a timer, if any, and not the title of the timer. - strValue = GetEpgTagTitle(epgTag); + strValue = CServiceBroker::GetPVRManager().Get().GetTitleForEpgTag(epgTag); return true; } } @@ -1303,8 +1287,11 @@ bool CPVRGUIInfo::GetFallbackLabel(std::string& value, ///////////////////////////////////////////////////////////////////////////////////////////// case VIDEOPLAYER_TITLE: case MUSICPLAYER_TITLE: - value = GetEpgTagTitle(CPVRItem(item).GetEpgInfoTag()); + { + const std::shared_ptr tag{CPVRItem(item).GetEpgInfoTag()}; + value = CServiceBroker::GetPVRManager().Get().GetTitleForEpgTag(tag); return !value.empty(); + } default: break; } From fb529f9b93b9bafd729a19d3f1a1445019447384 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Tue, 25 Jun 2024 07:14:40 +0200 Subject: [PATCH 283/651] changed: move CFileItem::GetLocalFanart to ArtUtils --- xbmc/FileItem.cpp | 78 ------------------ xbmc/FileItem.h | 7 -- xbmc/utils/ArtUtils.cpp | 97 ++++++++++++++++++++++ xbmc/utils/ArtUtils.h | 7 ++ xbmc/utils/test/TestArtUtils.cpp | 107 +++++++++++++++++++++++++ xbmc/video/VideoItemArtworkHandler.cpp | 3 +- 6 files changed, 213 insertions(+), 86 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 2776627018714..21e5dfd9d3177 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -2196,84 +2196,6 @@ std::string CFileItem::GetBaseMoviePath(bool bUseFolderNames) const return strMovieName; } -std::string CFileItem::GetLocalFanart() const -{ - if (VIDEO::IsVideoDb(*this)) - { - if (!HasVideoInfoTag()) - return ""; // nothing can be done - CFileItem dbItem(m_bIsFolder ? GetVideoInfoTag()->m_strPath : GetVideoInfoTag()->m_strFileNameAndPath, m_bIsFolder); - return dbItem.GetLocalFanart(); - } - - std::string strFile2; - std::string strFile = m_strPath; - if (IsStack()) - { - std::string strPath; - URIUtils::GetParentPath(m_strPath,strPath); - CStackDirectory dir; - std::string strPath2; - strPath2 = dir.GetStackedTitlePath(strFile); - strFile = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strPath2)); - CFileItem item(dir.GetFirstStackedFile(m_strPath),false); - std::string strTBNFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), "-fanart")); - strFile2 = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strTBNFile)); - } - if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) - { - std::string strPath = URIUtils::GetDirectory(strFile); - std::string strParent; - URIUtils::GetParentPath(strPath,strParent); - strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(m_strPath)); - } - - // no local fanart available for these - if (NETWORK::IsInternetStream(*this) || URIUtils::IsUPnP(strFile) || - URIUtils::IsBluray(strFile) || IsLiveTV() || IsPlugin() || IsAddonsPath() || IsDVD() || - (URIUtils::IsFTP(strFile) && - !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bFTPThumbs) || - m_strPath.empty()) - return ""; - - std::string strDir = URIUtils::GetDirectory(strFile); - - if (strDir.empty()) - return ""; - - CFileItemList items; - CDirectory::GetDirectory(strDir, items, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO); - if (IsOpticalMediaFile()) - { // grab from the optical media parent folder as well - CFileItemList moreItems; - CDirectory::GetDirectory(GetLocalMetadataPath(), moreItems, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO); - items.Append(moreItems); - } - - std::vector fanarts = { "fanart" }; - - strFile = URIUtils::ReplaceExtension(strFile, "-fanart"); - fanarts.insert(m_bIsFolder ? fanarts.end() : fanarts.begin(), URIUtils::GetFileName(strFile)); - - if (!strFile2.empty()) - fanarts.insert(m_bIsFolder ? fanarts.end() : fanarts.begin(), URIUtils::GetFileName(strFile2)); - - for (std::vector::const_iterator i = fanarts.begin(); i != fanarts.end(); ++i) - { - for (int j = 0; j < items.Size(); j++) - { - std::string strCandidate = URIUtils::GetFileName(items[j]->m_strPath); - URIUtils::RemoveExtension(strCandidate); - std::string strFanart = *i; - URIUtils::RemoveExtension(strFanart); - if (StringUtils::EqualsNoCase(strCandidate, strFanart)) - return items[j]->m_strPath; - } - } - - return ""; -} - std::string CFileItem::GetLocalMetadataPath() const { if (m_bIsFolder && !IsFileFolder()) diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index 140d130f48868..a6ffd48cec6e2 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -388,13 +388,6 @@ class CFileItem : CPictureInfoTag* GetPictureInfoTag(); - /*! - \brief Get the local fanart for this item if it exists - \return path to the local fanart for this item, or empty if none exists - \sa GetFolderThumb, GetTBNFile - */ - std::string GetLocalFanart() const; - /*! \brief Assemble the base filename of local artwork for an item, accounting for archives, stacks and multi-paths, and BDMV/VIDEO_TS folders. diff --git a/xbmc/utils/ArtUtils.cpp b/xbmc/utils/ArtUtils.cpp index c76192956e1a2..a3e98c1c8955c 100644 --- a/xbmc/utils/ArtUtils.cpp +++ b/xbmc/utils/ArtUtils.cpp @@ -9,15 +9,112 @@ #include "ArtUtils.h" #include "FileItem.h" +#include "FileItemList.h" +#include "ServiceBroker.h" +#include "filesystem/Directory.h" #include "filesystem/File.h" #include "filesystem/StackDirectory.h" +#include "network/NetworkFileItemClassify.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/FileExtensionProvider.h" +#include "utils/StringUtils.h" #include "utils/URIUtils.h" +#include "video/VideoFileItemClassify.h" +#include "video/VideoInfoTag.h" using namespace XFILE; namespace KODI::ART { +std::string GetLocalFanart(const CFileItem& item) +{ + if (VIDEO::IsVideoDb(item)) + { + if (!item.HasVideoInfoTag()) + return ""; // nothing can be done + CFileItem dbItem(item.m_bIsFolder ? item.GetVideoInfoTag()->m_strPath + : item.GetVideoInfoTag()->m_strFileNameAndPath, + item.m_bIsFolder); + return GetLocalFanart(dbItem); + } + + std::string strFile2; + std::string strFile = item.GetPath(); + if (item.IsStack()) + { + std::string strPath; + URIUtils::GetParentPath(item.GetPath(), strPath); + CStackDirectory dir; + std::string strPath2; + strPath2 = dir.GetStackedTitlePath(strFile); + strFile = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strPath2)); + CFileItem fan_item(dir.GetFirstStackedFile(item.GetPath()), false); + std::string strTBNFile(URIUtils::ReplaceExtension(GetTBNFile(fan_item), "-fanart")); + strFile2 = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strTBNFile)); + } + + if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) + { + std::string strPath = URIUtils::GetDirectory(strFile); + std::string strParent; + URIUtils::GetParentPath(strPath, strParent); + strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(item.GetPath())); + } + + // no local fanart available for these + if (NETWORK::IsInternetStream(item) || URIUtils::IsUPnP(strFile) || URIUtils::IsBluray(strFile) || + item.IsLiveTV() || item.IsPlugin() || item.IsAddonsPath() || item.IsDVD() || + (URIUtils::IsFTP(strFile) && + !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bFTPThumbs) || + item.GetPath().empty()) + return ""; + + std::string strDir = URIUtils::GetDirectory(strFile); + + if (strDir.empty()) + return ""; + + CFileItemList items; + CDirectory::GetDirectory(strDir, items, + CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), + DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO); + if (item.IsOpticalMediaFile()) + { // grab from the optical media parent folder as well + CFileItemList moreItems; + CDirectory::GetDirectory(item.GetLocalMetadataPath(), moreItems, + CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), + DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO); + items.Append(moreItems); + } + + std::vector fanarts = {"fanart"}; + + strFile = URIUtils::ReplaceExtension(strFile, "-fanart"); + fanarts.insert(item.m_bIsFolder ? fanarts.end() : fanarts.begin(), + URIUtils::GetFileName(strFile)); + + if (!strFile2.empty()) + fanarts.insert(item.m_bIsFolder ? fanarts.end() : fanarts.begin(), + URIUtils::GetFileName(strFile2)); + + for (const auto& fanart : fanarts) + { + for (const auto& item : items) + { + std::string strCandidate = URIUtils::GetFileName(item->GetPath()); + URIUtils::RemoveExtension(strCandidate); + std::string strFanart = fanart; + URIUtils::RemoveExtension(strFanart); + if (StringUtils::EqualsNoCase(strCandidate, strFanart)) + return item->GetPath(); + } + } + + return ""; +} + // Gets the .tbn filename from a file or folder name. // .ext -> .tbn // / -> .tbn diff --git a/xbmc/utils/ArtUtils.h b/xbmc/utils/ArtUtils.h index b2ae688206e4b..564b42b056469 100644 --- a/xbmc/utils/ArtUtils.h +++ b/xbmc/utils/ArtUtils.h @@ -15,6 +15,13 @@ class CFileItem; namespace KODI::ART { +/*! + \brief Get the local fanart for item if it exists + \return path to the local fanart for this item, or empty if none exists + \sa GetFolderThumb, GetTBNFile + */ +std::string GetLocalFanart(const CFileItem& item); + // Gets the .tbn file associated with an item std::string GetTBNFile(const CFileItem& item); diff --git a/xbmc/utils/test/TestArtUtils.cpp b/xbmc/utils/test/TestArtUtils.cpp index 92770435ecdf8..324836b233b09 100644 --- a/xbmc/utils/test/TestArtUtils.cpp +++ b/xbmc/utils/test/TestArtUtils.cpp @@ -7,19 +7,75 @@ */ #include "FileItem.h" +#include "URL.h" +#include "filesystem/Directory.h" #include "platform/Filesystem.h" #include "utils/ArtUtils.h" #include "utils/FileUtils.h" +#include "utils/StringUtils.h" #include "utils/URIUtils.h" +#include "video/VideoInfoTag.h" #include #include +#include #include #include using namespace KODI; +namespace +{ + +std::string unique_path(const std::string& input) +{ + std::random_device rd; + std::mt19937 gen(rd()); + auto randchar = [&gen]() + { + const std::string set = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + std::uniform_int_distribution<> select(0, set.size() - 1); + return set[select(gen)]; + }; + + std::string ret; + ret.reserve(input.size()); + std::transform(input.begin(), input.end(), std::back_inserter(ret), + [&randchar](const char c) { return (c == '%') ? randchar() : c; }); + + return ret; +} + +struct FanartTest +{ + std::string path; + std::string result; +}; + +class GetLocalFanartTest : public testing::WithParamInterface, public testing::Test +{ +}; + +const auto local_fanart_tests = std::array{ + FanartTest{"stack://#DIRECTORY#foo-cd1.avi , #DIRECTORY#foo-cd2.avi", "foo-fanart.jpg"}, + FanartTest{"stack://#DIRECTORY#foo-cd1.avi , #DIRECTORY#foo-cd2.avi", "foo-cd1-fanart.jpg"}, + FanartTest{"zip://#URLENCODED_DIRECTORY#bar.zip/foo.avi", "foo-fanart.jpg"}, + FanartTest{"ftp://some.where/foo.avi", ""}, + FanartTest{"https://some.where/foo.avi", ""}, + FanartTest{"upnp://some.where/123", ""}, + FanartTest{"bluray://1", ""}, + FanartTest{"/home/user/1.pvr", ""}, + FanartTest{"plugin://random.video/1", ""}, + FanartTest{"addons://plugins/video/1", ""}, + FanartTest{"dvd://1", ""}, + FanartTest{"", ""}, + FanartTest{"foo.avi", ""}, + FanartTest{"foo.avi", "foo-fanart.jpg"}, + FanartTest{"videodb://movies/1", ""}, + FanartTest{"videodb://movies/1", "foo-fanart.jpg"}, +}; + struct TbnTest { std::string path; @@ -31,6 +87,57 @@ class GetTbnTest : public testing::WithParamInterface, public testing:: { }; +} // namespace + +TEST_P(GetLocalFanartTest, GetLocalFanart) +{ + std::string path, file_path, uniq; + if (GetParam().result.empty()) + file_path = GetParam().path; + else + { + std::error_code ec; + auto tmpdir = KODI::PLATFORM::FILESYSTEM::temp_directory_path(ec); + ASSERT_TRUE(!ec); + uniq = unique_path("FanartTest%%%%%"); + path = URIUtils::AddFileToFolder(tmpdir, uniq); + URIUtils::AddSlashAtEnd(path); + XFILE::CDirectory::Create(path); + std::ofstream of(URIUtils::AddFileToFolder(path, GetParam().result), std::ios::out); + if (GetParam().path.find("#DIRECTORY#") != std::string::npos) + { + file_path = GetParam().path; + StringUtils::Replace(file_path, "#DIRECTORY#", path); + } + else if (GetParam().path.find("#URLENCODED_DIRECTORY#") != std::string::npos) + { + file_path = GetParam().path; + StringUtils::Replace(file_path, "#URLENCODED_DIRECTORY#", CURL::Encode(path)); + } + else if (GetParam().path.starts_with("videodb://")) + { + file_path = GetParam().path; + } + else + file_path = + URIUtils::AddFileToFolder(URIUtils::AddFileToFolder(tmpdir, uniq), GetParam().path); + } + + CFileItem item(file_path, false); + if (GetParam().path.starts_with("videodb://") && !GetParam().result.empty()) + { + item.GetVideoInfoTag()->m_strFileNameAndPath = URIUtils::AddFileToFolder(path, "foo.avi"); + } + const std::string res = ART::GetLocalFanart(item); + + EXPECT_EQ(URIUtils::GetFileName(res), GetParam().result); + + if (!GetParam().result.empty()) + XFILE::CDirectory::RemoveRecursive(path); +} + +INSTANTIATE_TEST_SUITE_P(TestArtUtils, GetLocalFanartTest, testing::ValuesIn(local_fanart_tests)); + TEST_P(GetTbnTest, TbnTest) { EXPECT_EQ(ART::GetTBNFile(CFileItem(GetParam().path, GetParam().isFolder)), GetParam().result); diff --git a/xbmc/video/VideoItemArtworkHandler.cpp b/xbmc/video/VideoItemArtworkHandler.cpp index 847267c1c9338..aee0401075d21 100644 --- a/xbmc/video/VideoItemArtworkHandler.cpp +++ b/xbmc/video/VideoItemArtworkHandler.cpp @@ -18,6 +18,7 @@ #include "music/MusicDatabase.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" +#include "utils/ArtUtils.h" #include "utils/FileExtensionProvider.h" #include "utils/FileUtils.h" #include "utils/URIUtils.h" @@ -478,7 +479,7 @@ std::vector CVideoItemArtworkFanartHandler::GetRemoteArt() const std::string CVideoItemArtworkFanartHandler::GetLocalArt() const { - return m_item->GetLocalFanart(); + return ART::GetLocalFanart(*m_item); } std::string CVideoItemArtworkFanartHandler::UpdateEmbeddedArt(const std::string& art) From 41ea30f21af80dc1fa4b71e4d8abcc321b4bed4a Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Sun, 30 Jun 2024 18:59:44 +0200 Subject: [PATCH 284/651] drop str prefix from strings --- xbmc/utils/ArtUtils.cpp | 53 ++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/xbmc/utils/ArtUtils.cpp b/xbmc/utils/ArtUtils.cpp index a3e98c1c8955c..80f09c3e8e9f7 100644 --- a/xbmc/utils/ArtUtils.cpp +++ b/xbmc/utils/ArtUtils.cpp @@ -40,44 +40,44 @@ std::string GetLocalFanart(const CFileItem& item) return GetLocalFanart(dbItem); } - std::string strFile2; - std::string strFile = item.GetPath(); + std::string file2; + std::string file = item.GetPath(); if (item.IsStack()) { - std::string strPath; - URIUtils::GetParentPath(item.GetPath(), strPath); + std::string path; + URIUtils::GetParentPath(item.GetPath(), path); CStackDirectory dir; - std::string strPath2; - strPath2 = dir.GetStackedTitlePath(strFile); - strFile = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strPath2)); + std::string path2; + path2 = dir.GetStackedTitlePath(file); + file = URIUtils::AddFileToFolder(path, URIUtils::GetFileName(path2)); CFileItem fan_item(dir.GetFirstStackedFile(item.GetPath()), false); - std::string strTBNFile(URIUtils::ReplaceExtension(GetTBNFile(fan_item), "-fanart")); - strFile2 = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strTBNFile)); + std::string TBNFile(URIUtils::ReplaceExtension(GetTBNFile(fan_item), "-fanart")); + file2 = URIUtils::AddFileToFolder(path, URIUtils::GetFileName(TBNFile)); } - if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) + if (URIUtils::IsInRAR(file) || URIUtils::IsInZIP(file)) { - std::string strPath = URIUtils::GetDirectory(strFile); - std::string strParent; - URIUtils::GetParentPath(strPath, strParent); - strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(item.GetPath())); + std::string path = URIUtils::GetDirectory(file); + std::string parent; + URIUtils::GetParentPath(path, parent); + file = URIUtils::AddFileToFolder(parent, URIUtils::GetFileName(item.GetPath())); } // no local fanart available for these - if (NETWORK::IsInternetStream(item) || URIUtils::IsUPnP(strFile) || URIUtils::IsBluray(strFile) || + if (NETWORK::IsInternetStream(item) || URIUtils::IsUPnP(file) || URIUtils::IsBluray(file) || item.IsLiveTV() || item.IsPlugin() || item.IsAddonsPath() || item.IsDVD() || - (URIUtils::IsFTP(strFile) && + (URIUtils::IsFTP(file) && !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bFTPThumbs) || item.GetPath().empty()) return ""; - std::string strDir = URIUtils::GetDirectory(strFile); + std::string dir = URIUtils::GetDirectory(file); - if (strDir.empty()) + if (dir.empty()) return ""; CFileItemList items; - CDirectory::GetDirectory(strDir, items, + CDirectory::GetDirectory(dir, items, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO); if (item.IsOpticalMediaFile()) @@ -91,13 +91,12 @@ std::string GetLocalFanart(const CFileItem& item) std::vector fanarts = {"fanart"}; - strFile = URIUtils::ReplaceExtension(strFile, "-fanart"); - fanarts.insert(item.m_bIsFolder ? fanarts.end() : fanarts.begin(), - URIUtils::GetFileName(strFile)); + file = URIUtils::ReplaceExtension(file, "-fanart"); + fanarts.insert(item.m_bIsFolder ? fanarts.end() : fanarts.begin(), URIUtils::GetFileName(file)); - if (!strFile2.empty()) + if (!file2.empty()) fanarts.insert(item.m_bIsFolder ? fanarts.end() : fanarts.begin(), - URIUtils::GetFileName(strFile2)); + URIUtils::GetFileName(file2)); for (const auto& fanart : fanarts) { @@ -105,9 +104,9 @@ std::string GetLocalFanart(const CFileItem& item) { std::string strCandidate = URIUtils::GetFileName(item->GetPath()); URIUtils::RemoveExtension(strCandidate); - std::string strFanart = fanart; - URIUtils::RemoveExtension(strFanart); - if (StringUtils::EqualsNoCase(strCandidate, strFanart)) + std::string fanart2 = fanart; + URIUtils::RemoveExtension(fanart2); + if (StringUtils::EqualsNoCase(strCandidate, fanart2)) return item->GetPath(); } } From f57b6fbfead058f3d1d738720573d17be738ef2b Mon Sep 17 00:00:00 2001 From: Ryan Rector Date: Wed, 29 May 2024 22:54:34 -0600 Subject: [PATCH 285/651] add database support for image cache cleaner --- xbmc/TextureDatabase.cpp | 65 ++++++++++++++++- xbmc/TextureDatabase.h | 16 ++++- xbmc/addons/AddonDatabase.cpp | 42 +++++++++++ xbmc/addons/AddonDatabase.h | 7 ++ xbmc/music/MusicDatabase.cpp | 53 ++++++++++++++ xbmc/music/MusicDatabase.h | 6 ++ xbmc/video/VideoDatabase.cpp | 129 ++++++++++++++++++++++++++++++++++ xbmc/video/VideoDatabase.h | 7 ++ 8 files changed, 323 insertions(+), 2 deletions(-) diff --git a/xbmc/TextureDatabase.cpp b/xbmc/TextureDatabase.cpp index 8356c4be6b4c4..aa851c13018e6 100644 --- a/xbmc/TextureDatabase.cpp +++ b/xbmc/TextureDatabase.cpp @@ -119,7 +119,8 @@ bool CTextureDatabase::Open() void CTextureDatabase::CreateTables() { CLog::Log(LOGINFO, "create texture table"); - m_pDS->exec("CREATE TABLE texture (id integer primary key, url text, cachedurl text, imagehash text, lasthashcheck text)"); + m_pDS->exec("CREATE TABLE texture (id integer primary key, url text, cachedurl text, " + "imagehash text, lasthashcheck text, lastlibrarycheck text)"); CLog::Log(LOGINFO, "create sizes table, index, and trigger"); m_pDS->exec("CREATE TABLE sizes (idtexture integer, size integer, width integer, height integer, usecount integer, lastusetime text)"); @@ -194,11 +195,18 @@ void CTextureDatabase::UpdateTables(int version) m_pDS->exec("CREATE TABLE texture (id integer primary key, url text, cachedurl text, imagehash text, lasthashcheck text)"); m_pDS->exec("CREATE TABLE sizes (idtexture integer, size integer, width integer, height integer, usecount integer, lastusetime text)"); } + if (version < 14) + { + m_pDS->exec("ALTER TABLE texture ADD lastlibrarycheck text"); + } } bool CTextureDatabase::IncrementUseCount(const CTextureDetails &details) { std::string sql = PrepareSQL("UPDATE sizes SET usecount=usecount+1, lastusetime=CURRENT_TIMESTAMP WHERE idtexture=%u AND width=%u AND height=%u", details.id, details.width, details.height); + if (!ExecuteQuery(sql)) + return false; + sql = PrepareSQL("UPDATE texture SET lastlibrarycheck=NULL WHERE id=%u", details.id); return ExecuteQuery(sql); } @@ -283,6 +291,61 @@ bool CTextureDatabase::GetTextures(CVariant &items, const Filter &filter) return false; } +std::vector CTextureDatabase::GetOldestCachedImages(unsigned int maxImages) const +{ + try + { + if (!m_pDB || !m_pDS) + return {}; + + // PVR manages own image cache, so exclude from here: + // `WHERE url NOT LIKE 'image://pvr%%' AND url NOT LIKE 'image://epg%%'` + // "re-check" between minimum of 30 days and maximum of total time required to check all + // current images by maxImages 4 times per day, in case of very many images in library. + std::string sql = PrepareSQL( + "SELECT url FROM texture JOIN sizes ON (texture.id=sizes.idtexture AND sizes.size=1) WHERE " + "url NOT LIKE 'image://pvr%%' AND url NOT LIKE 'image://epg%%' AND lastusetime < " + "datetime('now', '-30 days') AND (lastlibrarycheck IS NULL OR lastlibrarycheck < " + "datetime('now', '-'||min((select (count(*) / %u / 4) + 1 from texture WHERE url NOT LIKE " + "'image://pvr%%' AND url NOT LIKE 'image://epg%%'), max(30, (julianday(lastlibrarycheck) - " + "julianday(sizes.lastusetime)) / 2))||' days')) ORDER BY COALESCE(lastlibrarycheck, " + "lastusetime) ASC LIMIT %u", + maxImages, maxImages); + + if (!m_pDS->query(sql)) + return {}; + + std::vector result; + while (!m_pDS->eof()) + { + result.push_back(m_pDS->fv(0).get_asString()); + m_pDS->next(); + } + m_pDS->close(); + return result; + } + catch (...) + { + CLog::Log(LOGERROR, "{}, failed", __FUNCTION__); + } + return {}; +} + +bool CTextureDatabase::SetKeepCachedImages(const std::vector& imagesToKeep) +{ + if (!imagesToKeep.size()) + return true; + + std::string sql = "UPDATE texture SET lastlibrarycheck=CURRENT_TIMESTAMP WHERE url IN ("; + for (const auto& image : imagesToKeep) + { + sql += PrepareSQL("'%s',", image.c_str()); + } + sql.pop_back(); // remove last ',' + sql += ")"; + return ExecuteQuery(sql); +} + bool CTextureDatabase::SetCachedTextureValid(const std::string &url, bool updateable) { std::string date = updateable ? CDateTime::GetCurrentDateTime().GetAsDBDateTime() : ""; diff --git a/xbmc/TextureDatabase.h b/xbmc/TextureDatabase.h index 341e94ede2661..a0430020ce554 100644 --- a/xbmc/TextureDatabase.h +++ b/xbmc/TextureDatabase.h @@ -86,6 +86,20 @@ class CTextureDatabase : public CDatabase, public IDatabaseQueryRuleFactory bool GetTextures(CVariant &items, const Filter &filter); + /*! + * @brief Get a list of the oldest cached images eligible for cleaning. + * @param maxImages the maximum number of images to return + * @return + */ + std::vector GetOldestCachedImages(unsigned int maxImages) const; + + /*! + * @brief Set a list of images to be kept. Used to clean the image cache. + * @param imagesToKeep + * @return + */ + bool SetKeepCachedImages(const std::vector& imagesToKeep); + // rule creation CDatabaseQueryRule *CreateRule() const override; CDatabaseQueryRuleCombination *CreateCombination() const override; @@ -100,6 +114,6 @@ class CTextureDatabase : public CDatabase, public IDatabaseQueryRuleFactory void CreateTables() override; void CreateAnalytics() override; void UpdateTables(int version) override; - int GetSchemaVersion() const override { return 13; } + int GetSchemaVersion() const override { return 14; } const char* GetBaseDBName() const override { return "Textures"; } }; diff --git a/xbmc/addons/AddonDatabase.cpp b/xbmc/addons/AddonDatabase.cpp index 29837be2f6639..20cddcd3629ca 100644 --- a/xbmc/addons/AddonDatabase.cpp +++ b/xbmc/addons/AddonDatabase.cpp @@ -1252,3 +1252,45 @@ bool CAddonDatabase::AddInstalledAddon(const std::shared_ptr& addon, return true; } + +namespace +{ +bool IsAddonImageChecked(const std::string& addonImage, + const std::vector& imagesToCheck) +{ + if (addonImage.empty()) + return false; + + auto found = std::find(imagesToCheck.begin(), imagesToCheck.end(), addonImage); + return found != imagesToCheck.end(); +} +} // namespace + +std::vector CAddonDatabase::GetUsedImages( + const std::vector& imagesToCheck) const +{ + if (!imagesToCheck.size()) + return {}; + + VECADDONS allAddons; + if (!GetRepositoryContent(allAddons)) + return imagesToCheck; + + std::vector result; + for (const auto& addon : allAddons) + { + if (IsAddonImageChecked(addon->Icon(), imagesToCheck)) + result.push_back(addon->Icon()); + for (const auto& screenshot : addon->Screenshots()) + { + if (IsAddonImageChecked(screenshot, imagesToCheck)) + result.push_back(screenshot); + } + for (const auto& artPair : addon->Art()) + { + if (IsAddonImageChecked(artPair.second, imagesToCheck)) + result.push_back(artPair.second); + } + } + return result; +} diff --git a/xbmc/addons/AddonDatabase.h b/xbmc/addons/AddonDatabase.h index 72caa8564b382..5a19a2d4e591d 100644 --- a/xbmc/addons/AddonDatabase.h +++ b/xbmc/addons/AddonDatabase.h @@ -230,6 +230,13 @@ class CAddonDatabase : public CDatabase */ bool AddInstalledAddon(const std::shared_ptr& addon, const std::string& origin); + /*! + * @brief Check the passed in list of images if used in this database. Used to clean the image cache. + * @param imagesToCheck + * @return a list of the passed in images used by this database. + */ + std::vector GetUsedImages(const std::vector& imagesToCheck) const; + protected: void CreateTables() override; void CreateAnalytics() override; diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index 1e5ba67fd397c..33896766baf3b 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -13953,3 +13953,56 @@ bool CMusicDatabase::GetResumeBookmarkForAudioBook(const CFileItem& item, int& b bookmark = m_pDS->fv(0).get_asInt(); return true; } + +std::vector CMusicDatabase::GetUsedImages( + const std::vector& imagesToCheck) const +{ + try + { + if (!m_pDB || !m_pDS) + return imagesToCheck; + + if (!imagesToCheck.size()) + return {}; + + int artworkLevel = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_MUSICLIBRARY_ARTWORKLEVEL); + if (artworkLevel == CSettings::MUSICLIBRARY_ARTWORK_LEVEL_NONE) + { + return {}; + } + + std::string sql = "SELECT DISTINCT url FROM art WHERE url IN ("; + for (const auto& image : imagesToCheck) + { + sql += PrepareSQL("'%s',", image.c_str()); + } + sql.pop_back(); // remove last ',' + sql += ")"; + + // add arttype filters if set to "Basic" + if (artworkLevel == CSettings::MUSICLIBRARY_ARTWORK_LEVEL_BASIC) + { + sql += PrepareSQL(" AND (media_type = 'album' AND type = 'thumb' OR media_type = 'artist' " + "AND type IN ('thumb', 'fanart'))"); + } + + if (!m_pDS->query(sql)) + return {}; + + std::vector result; + while (!m_pDS->eof()) + { + result.push_back(m_pDS->fv(0).get_asString()); + m_pDS->next(); + } + m_pDS->close(); + + return result; + } + catch (...) + { + CLog::Log(LOGERROR, "{}, failed", __FUNCTION__); + } + return {}; +} diff --git a/xbmc/music/MusicDatabase.h b/xbmc/music/MusicDatabase.h index 20022f85a2876..f2aca6258ec36 100644 --- a/xbmc/music/MusicDatabase.h +++ b/xbmc/music/MusicDatabase.h @@ -882,6 +882,12 @@ class CMusicDatabase : public CDatabase std::string GetAlbumsLastModified(); std::string GetArtistsLastModified(); + /*! + * @brief Check the passed in list of images if used in this database. Used to clean the image cache. + * @param imagesToCheck + * @return a list of the passed in images used by this database. + */ + std::vector GetUsedImages(const std::vector& imagesToCheck) const; protected: std::map m_genreCache; diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index cc7bda1fd743a..7a837b6f11605 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -33,6 +33,7 @@ #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" #include "guilib/guiinfo/GUIInfoLabels.h" +#include "imagefiles/ImageFileURL.h" #include "interfaces/AnnouncementManager.h" #include "messaging/helpers/DialogOKHelper.h" #include "music/Artist.h" @@ -12750,3 +12751,131 @@ void CVideoDatabase::SetVideoVersionDefaultArt(int dbId, int idFrom, VideoDbCont SetArtForItem(dbId, MediaTypeVideoVersion, it.first, it.second); } } + +std::vector CVideoDatabase::GetUsedImages( + const std::vector& imagesToCheck) +{ + try + { + if (!m_pDB || !m_pDS) + return imagesToCheck; + + if (!imagesToCheck.size()) + return {}; + + int artworkLevel = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_VIDEOLIBRARY_ARTWORK_LEVEL); + if (artworkLevel == CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_NONE) + { + return {}; + } + + // first check the art table + + std::string sql = "SELECT DISTINCT url FROM art WHERE url IN ("; + for (const auto& image : imagesToCheck) + { + sql += PrepareSQL("'%s',", image.c_str()); + } + sql.pop_back(); // remove last ',' + sql += ")"; + + // add arttype filters if not set to "Maximum" + if (artworkLevel != CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_ALL) + { + static std::array mediatypes = { + MediaTypeEpisode, MediaTypeTvShow, MediaTypeSeason, MediaTypeMovie, + MediaTypeVideoCollection, MediaTypeMusicVideo, MediaTypeVideoVersion}; + + std::string arttypeSQL; + for (const auto& mediatype : mediatypes) + { + const auto& arttypes = CVideoThumbLoader::GetArtTypes(mediatype); + if (arttypes.empty()) + continue; + + if (!arttypeSQL.empty()) + arttypeSQL += ") OR "; + arttypeSQL += PrepareSQL("media_type = '%s' AND (", mediatype.c_str()); + bool workingNext = false; + for (const auto& arttype : arttypes) + { + if (workingNext) + arttypeSQL += " OR "; + workingNext = true; + if (artworkLevel == CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_BASIC) + { + // for basic match exact artwork type + arttypeSQL += PrepareSQL("type = '%s'", arttype.c_str()); + } + else + { + // otherwise check for arttype 'families', like fanart, fanart1, fanart13; + // still avoid most "happens to start with" like fanartstuff + arttypeSQL += + PrepareSQL("type BETWEEN '%s' AND '%s999'", arttype.c_str(), arttype.c_str()); + } + } + } + + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_VIDEOLIBRARY_ACTORTHUMBS)) + { + if (!arttypeSQL.empty()) + arttypeSQL += ") OR "; + arttypeSQL += "media_type = 'actor'"; + } + + if (!arttypeSQL.empty()) + sql += " AND (" + arttypeSQL + ")"; + } + + std::vector result; + if (m_pDS->query(sql)) + { + while (!m_pDS->eof()) + { + result.push_back(m_pDS->fv(0).get_asString()); + m_pDS->next(); + } + m_pDS->close(); + } + + // then check any chapter thumbnails against path and file tables + + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_MYVIDEOS_EXTRACTCHAPTERTHUMBS)) + { + std::vector foundVideoFiles; + for (const auto& image : imagesToCheck) + { + auto imageFile = IMAGE_FILES::CImageFileURL(image); + if (imageFile.GetSpecialType() == "video" && !imageFile.GetOption("chapter").empty()) + { + auto target = imageFile.GetTargetFile(); + auto quickFind = std::find(foundVideoFiles.begin(), foundVideoFiles.end(), target); + if (quickFind != foundVideoFiles.end()) + { + result.push_back(image); + } + else + { + int fileId = GetFileId(target); + if (fileId != -1) + { + result.push_back(image); + foundVideoFiles.push_back(target); + } + } + } + } + } + + return result; + } + catch (...) + { + CLog::LogF(LOGERROR, "failed"); + } + return {}; +} diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index 50b7feffdefc9..e8de69b88c057 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -1113,6 +1113,13 @@ class CVideoDatabase : public CDatabase int GetFileIdByMovie(int idMovie); std::string GetFileBasePathById(int idFile); + /*! + * @brief Check the passed in list of images if used in this database. Used to clean the image cache. + * @param imagesToCheck + * @return a list of the passed in images used by this database. + */ + std::vector GetUsedImages(const std::vector& imagesToCheck); + protected: int AddNewMovie(CVideoInfoTag& details); int AddNewMusicVideo(CVideoInfoTag& details); From 6e31bd7c959bde02d0606828e351096a3e32ba7c Mon Sep 17 00:00:00 2001 From: Ryan Rector Date: Wed, 29 May 2024 23:10:52 -0600 Subject: [PATCH 286/651] add recurring job to clean image cache --- xbmc/TextureCache.cpp | 44 +++++++++- xbmc/TextureCache.h | 7 ++ xbmc/imagefiles/CMakeLists.txt | 6 +- xbmc/imagefiles/ImageCacheCleaner.cpp | 112 ++++++++++++++++++++++++++ xbmc/imagefiles/ImageCacheCleaner.h | 58 +++++++++++++ 5 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 xbmc/imagefiles/ImageCacheCleaner.cpp create mode 100644 xbmc/imagefiles/ImageCacheCleaner.h diff --git a/xbmc/TextureCache.cpp b/xbmc/TextureCache.cpp index c2e1c37af20c7..4d8fb2c0471dd 100644 --- a/xbmc/TextureCache.cpp +++ b/xbmc/TextureCache.cpp @@ -15,11 +15,13 @@ #include "filesystem/File.h" #include "filesystem/IFileTypes.h" #include "guilib/Texture.h" +#include "imagefiles/ImageCacheCleaner.h" #include "imagefiles/ImageFileURL.h" #include "profiles/ProfileManager.h" #include "settings/SettingsComponent.h" #include "utils/Crc32.h" #include "utils/Job.h" +#include "utils/JobManager.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/log.h" @@ -27,12 +29,14 @@ #include #include #include +#include #include using namespace XFILE; using namespace std::chrono_literals; -CTextureCache::CTextureCache() : CJobQueue(false, 1, CJob::PRIORITY_LOW_PAUSABLE) +CTextureCache::CTextureCache() + : CJobQueue(false, 1, CJob::PRIORITY_LOW_PAUSABLE), m_cleanTimer{[this]() { CleanTimer(); }} { } @@ -40,6 +44,7 @@ CTextureCache::~CTextureCache() = default; void CTextureCache::Initialize() { + m_cleanTimer.Start(60s); std::unique_lock lock(m_databaseSection); if (!m_database.IsOpen()) m_database.Open(); @@ -344,3 +349,40 @@ bool CTextureCache::Export(const std::string &image, const std::string &destinat } return false; } + +void CTextureCache::CleanTimer() +{ + CServiceBroker::GetJobManager()->Submit( + [this]() + { + auto next = ScanOldestCache(); + m_cleanTimer.Start(next); + }, + CJob::PRIORITY_LOW_PAUSABLE); +} + +std::chrono::milliseconds CTextureCache::ScanOldestCache() +{ + auto cleaner = IMAGE_FILES::CImageCacheCleaner::Create(); + if (!cleaner) + return std::chrono::hours(1); + + const unsigned int cleanAmount = 1000; + const auto result = cleaner->ScanOldestCache(cleanAmount); + for (const auto& image : result.imagesToClean) + { + ClearCachedImage(image); + } + + // update in the next 6 - 48 hours depending on number of items processed + const auto minTime = 6; + const auto maxTime = 24; + const auto zeroItemTime = 48; + const unsigned int next = + result.processedCount == 0 + ? zeroItemTime + : minTime + (maxTime - minTime) * + (1 - (static_cast(result.processedCount) / cleanAmount)); + CLog::LogF(LOGDEBUG, "scheduling the next image cache cleaning in {} hours", next); + return std::chrono::hours(next); +} diff --git a/xbmc/TextureCache.h b/xbmc/TextureCache.h index 3c56db1f5f37c..6b0bf44d219e0 100644 --- a/xbmc/TextureCache.h +++ b/xbmc/TextureCache.h @@ -12,8 +12,10 @@ #include "TextureDatabase.h" #include "threads/CriticalSection.h" #include "threads/Event.h" +#include "threads/Timer.h" #include "utils/JobManager.h" +#include #include #include #include @@ -152,6 +154,7 @@ class CTextureCache : public CJobQueue */ bool Export(const std::string &image, const std::string &destination, bool overwrite); bool Export(const std::string &image, const std::string &destination); //! @todo BACKWARD COMPATIBILITY FOR MUSIC THUMBS + private: // private construction, and no assignments; use the provided singleton methods CTextureCache(const CTextureCache&) = delete; @@ -213,6 +216,10 @@ class CTextureCache : public CJobQueue */ void OnCachingComplete(bool success, CTextureCacheJob *job); + void CleanTimer(); + std::chrono::milliseconds ScanOldestCache(); + + CTimer m_cleanTimer; CCriticalSection m_databaseSection; CTextureDatabase m_database; std::set m_processinglist; ///< currently processing list to avoid 2 jobs being processed at once diff --git a/xbmc/imagefiles/CMakeLists.txt b/xbmc/imagefiles/CMakeLists.txt index 66d1b45aa8ef6..69b4551944798 100644 --- a/xbmc/imagefiles/CMakeLists.txt +++ b/xbmc/imagefiles/CMakeLists.txt @@ -1,7 +1,9 @@ -set(SOURCES ImageFileURL.cpp +set(SOURCES ImageCacheCleaner.cpp + ImageFileURL.cpp SpecialImageLoaderFactory.cpp) -set(HEADERS ImageFileURL.h +set(HEADERS ImageCacheCleaner.h + ImageFileURL.h SpecialImageFileLoader.h SpecialImageLoaderFactory.h) diff --git a/xbmc/imagefiles/ImageCacheCleaner.cpp b/xbmc/imagefiles/ImageCacheCleaner.cpp new file mode 100644 index 0000000000000..6821b8dc434c1 --- /dev/null +++ b/xbmc/imagefiles/ImageCacheCleaner.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 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 "ImageCacheCleaner.h" + +#include "ServiceBroker.h" +#include "TextureCache.h" +#include "TextureDatabase.h" +#include "addons/AddonDatabase.h" +#include "music/MusicDatabase.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" + +namespace IMAGE_FILES +{ +std::optional CImageCacheCleaner::Create() +{ + auto result = CImageCacheCleaner(); + if (result.m_valid) + return std::move(result); + return {}; +} + +CImageCacheCleaner::CImageCacheCleaner() +{ + bool valid = true; + m_textureDB = std::make_unique(); + if (!m_textureDB->Open()) + { + valid = false; + CLog::LogF(LOGWARNING, "failed to initialize image cache cleaner: failed to open texture DB"); + } + + m_videoDB = std::make_unique(); + if (!m_videoDB->Open()) + { + valid = false; + CLog::LogF(LOGWARNING, "failed to initialize image cache cleaner: failed to open video DB"); + } + + m_musicDB = std::make_unique(); + if (!m_musicDB->Open()) + { + valid = false; + CLog::LogF(LOGWARNING, "failed to initialize image cache cleaner: failed to open music DB"); + } + + m_addonDB = std::make_unique(); + if (!m_addonDB->Open()) + { + valid = false; + CLog::LogF(LOGWARNING, "failed to initialize image cache cleaner: failed to open add-on DB"); + } + m_valid = valid; +} + +CImageCacheCleaner::~CImageCacheCleaner() +{ + if (m_addonDB) + m_addonDB->Close(); + if (m_musicDB) + m_musicDB->Close(); + if (m_videoDB) + m_videoDB->Close(); + if (m_textureDB) + m_textureDB->Close(); +} + +CleanerResult CImageCacheCleaner::ScanOldestCache(unsigned int imageLimit) +{ + CLog::LogF(LOGDEBUG, "begin process to clean image cache"); + + auto images = m_textureDB->GetOldestCachedImages(imageLimit); + if (images.empty()) + { + CLog::LogF(LOGDEBUG, "found no old cached images to process"); + return CleanerResult{0, {}, {}}; + } + unsigned int processedCount = images.size(); + CLog::LogF(LOGDEBUG, "found {} old cached images to process", processedCount); + + auto usedImages = m_videoDB->GetUsedImages(images); + + auto nextUsedImages = m_musicDB->GetUsedImages(images); + usedImages.insert(usedImages.end(), std::make_move_iterator(nextUsedImages.begin()), + std::make_move_iterator(nextUsedImages.end())); + + nextUsedImages = m_addonDB->GetUsedImages(images); + usedImages.insert(usedImages.end(), std::make_move_iterator(nextUsedImages.begin()), + std::make_move_iterator(nextUsedImages.end())); + + images.erase(std::remove_if(images.begin(), images.end(), + [&usedImages](const std::string& image) { + return std::find(usedImages.cbegin(), usedImages.cend(), image) != + usedImages.cend(); + }), + images.end()); + + m_textureDB->SetKeepCachedImages(usedImages); + + unsigned int keptCount = usedImages.size(); + CLog::LogF(LOGDEBUG, "cleaning {} unused images from cache, keeping {}", images.size(), + keptCount); + + return CleanerResult{processedCount, keptCount, std::move(images)}; +} +} // namespace IMAGE_FILES diff --git a/xbmc/imagefiles/ImageCacheCleaner.h b/xbmc/imagefiles/ImageCacheCleaner.h new file mode 100644 index 0000000000000..d975ab9972145 --- /dev/null +++ b/xbmc/imagefiles/ImageCacheCleaner.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 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 +#include +#include +#include +#include + +class CTextureDatabase; +class CVideoDatabase; +class CMusicDatabase; +namespace ADDON +{ +class CAddonDatabase; +} + +namespace IMAGE_FILES +{ +struct CleanerResult +{ + unsigned int processedCount; + unsigned int keptCount; + std::vector imagesToClean; +}; + +/*! + * @brief Clean old unused images from the image cache. + */ +class CImageCacheCleaner +{ +public: + static std::optional Create(); + ~CImageCacheCleaner(); + CImageCacheCleaner(const CImageCacheCleaner&) = delete; + CImageCacheCleaner& operator=(const CImageCacheCleaner&) = delete; + CImageCacheCleaner(CImageCacheCleaner&&) = default; + CImageCacheCleaner& operator=(CImageCacheCleaner&&) = default; + + CleanerResult ScanOldestCache(unsigned int imageLimit); + +private: + CImageCacheCleaner(); + bool m_valid; + + std::unique_ptr m_textureDB; + std::unique_ptr m_videoDB; + std::unique_ptr m_musicDB; + std::unique_ptr m_addonDB; +}; +} // namespace IMAGE_FILES From 2aa75033d305f480f2557be41f2061646c1d67b9 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Thu, 11 Jul 2024 23:05:42 +0200 Subject: [PATCH 287/651] [Android] Move GetStorageUsage function to CAndroidStorageProvider --- xbmc/platform/android/activity/XBMCApp.cpp | 69 ------------------- xbmc/platform/android/activity/XBMCApp.h | 3 +- .../storage/AndroidStorageProvider.cpp | 47 +++++++++++-- .../android/storage/AndroidStorageProvider.h | 2 + 4 files changed, 45 insertions(+), 76 deletions(-) diff --git a/xbmc/platform/android/activity/XBMCApp.cpp b/xbmc/platform/android/activity/XBMCApp.cpp index 5f4bb7865a929..67a2ae030cfac 100644 --- a/xbmc/platform/android/activity/XBMCApp.cpp +++ b/xbmc/platform/android/activity/XBMCApp.cpp @@ -92,7 +92,6 @@ #include #include #include -#include #include #include #include @@ -106,8 +105,6 @@ #include #include -#define GIGABYTES 1073741824 - #define ACTION_XBMC_RESUME "android.intent.XBMC_RESUME" #define PLAYBACK_STATE_STOPPED 0x0000 @@ -1133,72 +1130,6 @@ bool CXBMCApp::GetExternalStorage(std::string &path, const std::string &type /* return mounted && !path.empty(); } -bool CXBMCApp::GetStorageUsage(const std::string &path, std::string &usage) -{ -#define PATH_MAXLEN 38 - - if (path.empty()) - { - std::ostringstream fmt; - - fmt.width(PATH_MAXLEN); - fmt << std::left << "Filesystem"; - - fmt.width(12); - fmt << std::right << "Size"; - - fmt.width(12); - fmt << "Used"; - - fmt.width(12); - fmt << "Avail"; - - fmt.width(12); - fmt << "Use %"; - - usage = fmt.str(); - return false; - } - - CJNIStatFs fileStat(path); - int blockSize = fileStat.getBlockSize(); - int blockCount = fileStat.getBlockCount(); - int freeBlocks = fileStat.getFreeBlocks(); - - if (blockSize <= 0 || blockCount <= 0 || freeBlocks < 0) - return false; - - float totalSize = (float)blockSize * blockCount / GIGABYTES; - float freeSize = (float)blockSize * freeBlocks / GIGABYTES; - float usedSize = totalSize - freeSize; - float usedPercentage = usedSize / totalSize * 100; - - std::ostringstream fmt; - - fmt << std::fixed; - fmt.precision(1); - - fmt.width(PATH_MAXLEN); - fmt << std::left - << (path.size() < PATH_MAXLEN - 1 ? path : StringUtils::Left(path, PATH_MAXLEN - 4) + "..."); - - fmt.width(11); - fmt << std::right << totalSize << "G"; - - fmt.width(11); - fmt << usedSize << "G"; - - fmt.width(11); - fmt << freeSize << "G"; - - fmt.precision(0); - fmt.width(11); - fmt << usedPercentage << "%"; - - usage = fmt.str(); - return true; -} - // Used in Application.cpp to figure out volume steps int CXBMCApp::GetMaxSystemVolume() { diff --git a/xbmc/platform/android/activity/XBMCApp.h b/xbmc/platform/android/activity/XBMCApp.h index ee62fc3f039e5..a2d27671d2179 100644 --- a/xbmc/platform/android/activity/XBMCApp.h +++ b/xbmc/platform/android/activity/XBMCApp.h @@ -173,8 +173,7 @@ class CXBMCApp : public IActivityHandler, * \param type optional type. Possible values are "", "files", "music", "videos", "pictures", "photos, "downloads" * \return true if external storage is available and a valid path has been stored in the path parameter */ - static bool GetExternalStorage(std::string &path, const std::string &type = ""); - static bool GetStorageUsage(const std::string &path, std::string &usage); + static bool GetExternalStorage(std::string& path, const std::string& type = ""); static int GetMaxSystemVolume(); static float GetSystemVolume(); static void SetSystemVolume(float percent); diff --git a/xbmc/platform/android/storage/AndroidStorageProvider.cpp b/xbmc/platform/android/storage/AndroidStorageProvider.cpp index 1993d45e421e9..33f7ab8a23084 100644 --- a/xbmc/platform/android/storage/AndroidStorageProvider.cpp +++ b/xbmc/platform/android/storage/AndroidStorageProvider.cpp @@ -27,6 +27,8 @@ #include #include +#include +#include #include #include @@ -378,19 +380,19 @@ std::vector CAndroidStorageProvider::GetDiskUsage() std::string usage; // add header - CXBMCApp::GetStorageUsage("", usage); + GetStorageUsage("", usage); result.push_back(usage); usage.clear(); // add rootfs - if (CXBMCApp::GetStorageUsage("/", usage) && !usage.empty()) + if (GetStorageUsage("/", usage) && !usage.empty()) result.push_back(usage); usage.clear(); // add external storage if available std::string path; - if (CXBMCApp::GetExternalStorage(path) && !path.empty() && - CXBMCApp::GetStorageUsage(path, usage) && !usage.empty()) + if (CXBMCApp::GetExternalStorage(path) && !path.empty() && GetStorageUsage(path, usage) && + !usage.empty()) result.push_back(usage); // add removable storage @@ -399,7 +401,7 @@ std::vector CAndroidStorageProvider::GetDiskUsage() for (unsigned int i = 0; i < drives.size(); i++) { usage.clear(); - if (CXBMCApp::GetStorageUsage(drives[i].strPath, usage) && !usage.empty()) + if (GetStorageUsage(drives[i].strPath, usage) && !usage.empty()) result.push_back(usage); } @@ -414,3 +416,38 @@ bool CAndroidStorageProvider::PumpDriveChangeEvents(IStorageEventsCallback *call m_removableDrives = std::move(drives); return changed; } + +namespace +{ +constexpr float GIGABYTES = 1073741824; +constexpr int PATH_MAXLEN = 38; +} // namespace + +bool CAndroidStorageProvider::GetStorageUsage(const std::string& path, std::string& usage) +{ + if (path.empty()) + { + usage = StringUtils::Format("{:<{}}{:>12}{:>12}{:>12}{:>12}", "Filesystem", PATH_MAXLEN, "Size", + "Used", "Avail", "Use %"); + return false; + } + + CJNIStatFs fileStat(path); + const int blockSize = fileStat.getBlockSize(); + const int blockCount = fileStat.getBlockCount(); + const int freeBlocks = fileStat.getFreeBlocks(); + + if (blockSize <= 0 || blockCount <= 0 || freeBlocks < 0) + return false; + + const float totalSize = static_cast(blockSize) * blockCount / GIGABYTES; + const float freeSize = static_cast(blockSize) * freeBlocks / GIGABYTES; + const float usedSize = totalSize - freeSize; + const float usedPercentage = usedSize / totalSize * 100; + + usage = StringUtils::Format( + "{:<{}}{:>11.1f}{}{:>11.1f}{}{:>11.1f}{}{:>11.0f}{}", + path.size() < PATH_MAXLEN - 1 ? path : StringUtils::Left(path, PATH_MAXLEN - 4) + "...", + PATH_MAXLEN, totalSize, "G", usedSize, "G", freeSize, "G", usedPercentage, "%"); + return true; +} diff --git a/xbmc/platform/android/storage/AndroidStorageProvider.h b/xbmc/platform/android/storage/AndroidStorageProvider.h index 429e6e873c5bd..e39ff60dea7c0 100644 --- a/xbmc/platform/android/storage/AndroidStorageProvider.h +++ b/xbmc/platform/android/storage/AndroidStorageProvider.h @@ -37,4 +37,6 @@ class CAndroidStorageProvider : public IStorageProvider static std::set GetRemovableDrives(); static std::set GetRemovableDrivesLinux(); + + bool GetStorageUsage(const std::string& path, std::string& usage); }; From d979c1a15705da0a74bbd0d112b6340e2f634fbc Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Sat, 13 Jul 2024 11:05:00 +0200 Subject: [PATCH 288/651] [Android] Move MemoryInfo logic to MemUtils --- xbmc/platform/android/MemUtils.cpp | 27 ++++++++++++++-------- xbmc/platform/android/activity/XBMCApp.cpp | 24 ------------------- xbmc/platform/android/activity/XBMCApp.h | 5 ---- 3 files changed, 18 insertions(+), 38 deletions(-) diff --git a/xbmc/platform/android/MemUtils.cpp b/xbmc/platform/android/MemUtils.cpp index d247e4699a32b..68f7f6402f240 100644 --- a/xbmc/platform/android/MemUtils.cpp +++ b/xbmc/platform/android/MemUtils.cpp @@ -8,10 +8,11 @@ #include "utils/MemUtils.h" -#include "platform/android/activity/XBMCApp.h" - #include +#include +#include + namespace KODI { namespace MEMORY @@ -35,16 +36,24 @@ void GetMemoryStatus(MemoryStatus* buffer) if (!buffer) return; - long availMem = 0; - long totalMem = 0; + auto activityManager = std::make_unique( + CJNIContext::getSystemService(CJNIContext::ACTIVITY_SERVICE)); - if (CXBMCApp::Get().GetMemoryInfo(availMem, totalMem)) + if (activityManager) { + CJNIActivityManager::MemoryInfo info; + activityManager->getMemoryInfo(info); + if (xbmc_jnienv()->ExceptionCheck()) + { + xbmc_jnienv()->ExceptionClear(); + return; + } + *buffer = {}; - buffer->totalPhys = static_cast(totalMem); - buffer->availPhys = static_cast(availMem); + buffer->totalPhys = static_cast(info.totalMem()); + buffer->availPhys = static_cast(info.availMem()); } } -} -} +} // namespace MEMORY +} // namespace KODI diff --git a/xbmc/platform/android/activity/XBMCApp.cpp b/xbmc/platform/android/activity/XBMCApp.cpp index 5f4bb7865a929..1169988f38c5f 100644 --- a/xbmc/platform/android/activity/XBMCApp.cpp +++ b/xbmc/platform/android/activity/XBMCApp.cpp @@ -70,7 +70,6 @@ #include #include #include -#include #include #include #include @@ -262,8 +261,6 @@ void CXBMCApp::onStart() intentFilter.addAction(CJNIConnectivityManager::CONNECTIVITY_ACTION); registerReceiver(*this, intentFilter); m_mediaSession = std::make_unique(); - m_activityManager = - std::make_unique(getSystemService(CJNIContext::ACTIVITY_SERVICE)); m_inputHandler.setDPI(GetDPI()); runNativeOnUiThread(RegisterDisplayListenerCallback, nullptr); } @@ -1559,27 +1556,6 @@ bool CXBMCApp::WaitVSync(unsigned int milliSeconds) return m_vsyncEvent.Wait(std::chrono::milliseconds(milliSeconds)); } -bool CXBMCApp::GetMemoryInfo(long& availMem, long& totalMem) -{ - if (m_activityManager) - { - CJNIActivityManager::MemoryInfo info; - m_activityManager->getMemoryInfo(info); - if (xbmc_jnienv()->ExceptionCheck()) - { - xbmc_jnienv()->ExceptionClear(); - return false; - } - - availMem = info.availMem(); - totalMem = info.totalMem(); - - return true; - } - - return false; -} - void CXBMCApp::SetupEnv() { setenv("KODI_ANDROID_SYSTEM_LIBS", CJNISystem::getProperty("java.library.path").c_str(), 0); diff --git a/xbmc/platform/android/activity/XBMCApp.h b/xbmc/platform/android/activity/XBMCApp.h index ee62fc3f039e5..c7b4f9bcbc4aa 100644 --- a/xbmc/platform/android/activity/XBMCApp.h +++ b/xbmc/platform/android/activity/XBMCApp.h @@ -40,7 +40,6 @@ class CVariant; class IInputDeviceCallbacks; class IInputDeviceEventHandler; class CVideoSyncAndroid; -class CJNIActivityManager; typedef struct _JNIEnv JNIEnv; @@ -217,8 +216,6 @@ class CXBMCApp : public IActivityHandler, bool getVideosurfaceInUse(); void setVideosurfaceInUse(bool videosurfaceInUse); - bool GetMemoryInfo(long& availMem, long& totalMem); - protected: // limit who can access Volume friend class CAESinkAUDIOTRACK; @@ -276,8 +273,6 @@ class CXBMCApp : public IActivityHandler, CEvent m_vsyncEvent; CEvent m_displayChangeEvent; - std::unique_ptr m_activityManager; - bool XBMC_DestroyDisplay(); bool XBMC_SetupDisplay(); From 2a6a783f378930b885d0ada95f0828e3c2385a4b Mon Sep 17 00:00:00 2001 From: Rudi Heitbaum Date: Sun, 14 Jul 2024 09:24:49 +0000 Subject: [PATCH 289/651] cmake: copyright: update PCRE2 license --- cmake/cpack/deb/copyright | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/cmake/cpack/deb/copyright b/cmake/cpack/deb/copyright index 3a3916cd9445b..c82d5a5ac87e1 100644 --- a/cmake/cpack/deb/copyright +++ b/cmake/cpack/deb/copyright @@ -1209,24 +1209,24 @@ Samba - Opening Windows to a Wider World You should have received a copy of the GNU General Public License along with this program. If not, see . -PCRE - Perl Compatible Regular Expressions +PCRE2 - Perl Compatible Regular Expressions - Copyright © 1997-2007 University of Cambridge + Copyright © 1997-2024 University of Cambridge Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, + * Redistributions of source code must retain the above copyright notices, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the + notices, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of the University of Cambridge nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. + * Neither the name of the University of Cambridge nor the names of any + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE @@ -1240,6 +1240,16 @@ PCRE - Perl Compatible Regular Expressions ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + EXEMPTION FOR BINARY LIBRARY-LIKE PACKAGES + ------------------------------------------ + + The second condition in the BSD licence (covering binary redistributions) does + not apply all the way down a chain of software. If binary package A includes + PCRE2, it must respect the condition, but if package B is software that + includes package A, the condition is not imposed on package B unless it uses + PCRE2 independently. + HDHomeRun - Networked Digital Tuner @@ -3044,4 +3054,4 @@ License: Alliance for Open Media Patent License 1.0 the Alliance for Open Media as a Final Deliverable for which this License was issued. --- End of Alliance for Open Media Patent License 1.0 License -- \ No newline at end of file +-- End of Alliance for Open Media Patent License 1.0 License -- From 4f903f0c9ea930e3abf75cfc0f6d08b8e37b0173 Mon Sep 17 00:00:00 2001 From: Geert Hendrickx Date: Sun, 14 Jul 2024 15:42:02 +0200 Subject: [PATCH 290/651] Disable TLS 1.1, formally deprecated by RFC 8996. This should not cause any impact, as most clients implemented TLS 1.1 and 1.2 at the same time. See https://www.ssllabs.com/ssltest/clients.html --- xbmc/network/WebServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/network/WebServer.cpp b/xbmc/network/WebServer.cpp index 0ee169603355b..bbd8938faa074 100644 --- a/xbmc/network/WebServer.cpp +++ b/xbmc/network/WebServer.cpp @@ -1192,7 +1192,7 @@ bool CWebServer::LoadCert(std::string& skey, std::string& scert) struct MHD_Daemon* CWebServer::StartMHD(unsigned int flags, int port) { unsigned int timeout = 60 * 60 * 24; - const char* ciphers = "NORMAL:-VERS-TLS1.0"; + const char* ciphers = "NORMAL:-VERS-TLS1.0:-VERS-TLS1.1"; MHD_set_panic_func(&panicHandlerForMHD, nullptr); From 88415e81b36917d3de66a02de432ea1fd3ad992b Mon Sep 17 00:00:00 2001 From: Geert Hendrickx Date: Sun, 14 Jul 2024 15:46:28 +0200 Subject: [PATCH 291/651] Disable non-PFS ciphers, strongly discouraged by BCP RFC 7525 and 9325 and to be formally deprecated by draft-ietf-tls-deprecate-obsolete-kex. This should not cause any impact, with the minimum already at TLS 1.2+ See https://www.ssllabs.com/ssltest/clients.html --- xbmc/network/WebServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/network/WebServer.cpp b/xbmc/network/WebServer.cpp index bbd8938faa074..2f82a0d0d29b0 100644 --- a/xbmc/network/WebServer.cpp +++ b/xbmc/network/WebServer.cpp @@ -1192,7 +1192,7 @@ bool CWebServer::LoadCert(std::string& skey, std::string& scert) struct MHD_Daemon* CWebServer::StartMHD(unsigned int flags, int port) { unsigned int timeout = 60 * 60 * 24; - const char* ciphers = "NORMAL:-VERS-TLS1.0:-VERS-TLS1.1"; + const char* ciphers = "PFS:-VERS-TLS1.0:-VERS-TLS1.1"; MHD_set_panic_func(&panicHandlerForMHD, nullptr); From fca2711f66a27d16f088f69f3cb05bff4b1bd249 Mon Sep 17 00:00:00 2001 From: Ryan Rector Date: Sat, 13 Jul 2024 13:31:02 -0600 Subject: [PATCH 292/651] add manual action to clean all unused images --- .../resources/strings.po | 21 ++++- system/settings/settings.xml | 6 ++ xbmc/TextureCache.cpp | 80 ++++++++++++++++++- xbmc/TextureCache.h | 6 ++ xbmc/settings/MediaSettings.cpp | 5 ++ xbmc/settings/Settings.cpp | 1 + xbmc/settings/Settings.h | 1 + 7 files changed, 118 insertions(+), 2 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index bc5d2f735bffe..d0064af1b9a88 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -1508,6 +1508,7 @@ msgid "(0=auto)" msgstr "" #. generic "cleaning database" label used in different places +#: xbmc/TextureCache.cpp #: xbmc/music/MusicDatabase.cpp #: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp #: xbmc/settings/MediaSettings.cpp @@ -8772,7 +8773,25 @@ msgctxt "#14277" msgid "Allow remote control from applications on other systems" msgstr "" -#empty strings from id 14278 to 14300 +#empty strings from id 14278 to 14279 + +#: system/settings/settings.xml +msgctxt "#14280" +msgid "Maintenance" +msgstr "" + +#: system/settings/settings.xml +#: xbmc/TextureCache.cpp +msgctxt "#14281" +msgid "Clean image cache" +msgstr "" + +#: system/settings/settings.xml +msgctxt "#14282" +msgid "Immediately remove unused images from the cache - images not associated with an item in the media library nor viewed recently. Kodi does this over time in the background." +msgstr "" + +#empty strings from id 14283 to 14300 #. pvr "channels" settings group label #: system/settings/settings.xml diff --git a/system/settings/settings.xml b/system/settings/settings.xml index ff748c2584c8c..1c54a059c6615 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -1018,6 +1018,12 @@ + + + 3 + + + diff --git a/xbmc/TextureCache.cpp b/xbmc/TextureCache.cpp index 4d8fb2c0471dd..89705ae06ce50 100644 --- a/xbmc/TextureCache.cpp +++ b/xbmc/TextureCache.cpp @@ -12,8 +12,11 @@ #include "TextureCacheJob.h" #include "URL.h" #include "commons/ilog.h" +#include "dialogs/GUIDialogProgress.h" #include "filesystem/File.h" #include "filesystem/IFileTypes.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" #include "guilib/Texture.h" #include "imagefiles/ImageCacheCleaner.h" #include "imagefiles/ImageFileURL.h" @@ -350,12 +353,87 @@ bool CTextureCache::Export(const std::string &image, const std::string &destinat return false; } +bool CTextureCache::CleanAllUnusedImages() +{ + if (m_cleaningInProgress.test_and_set()) + return false; + + auto progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow( + WINDOW_DIALOG_PROGRESS); + if (progress) + { + progress->SetHeading(CVariant{14281}); //"Clean image cache" + progress->SetText(CVariant{313}); //"Cleaning database" + progress->SetPercentage(0); + progress->Open(); + progress->ShowProgressBar(true); + } + + bool failure = false; + CServiceBroker::GetJobManager()->Submit([this, progress, &failure]() + { failure = CleanAllUnusedImagesJob(progress); }); + + // Wait for clean to complete or be canceled, but render every 10ms so that the + // pointer movements work on dialog even when clean is reporting progress infrequently + if (progress) + progress->Wait(); + + m_cleaningInProgress.clear(); + return !failure; +} + +bool CTextureCache::CleanAllUnusedImagesJob(CGUIDialogProgress* progress) +{ + auto cleaner = IMAGE_FILES::CImageCacheCleaner::Create(); + if (!cleaner) + { + if (progress) + progress->Close(); + return false; + } + + const unsigned int cleanAmount = 1000000; + const auto result = cleaner->ScanOldestCache(cleanAmount); + if (progress && progress->IsCanceled()) + { + progress->Close(); + return false; + } + + const auto total = result.imagesToClean.size(); + unsigned int current = 0; + for (const auto& image : result.imagesToClean) + { + ClearCachedImage(image); + if (progress) + { + if (progress->IsCanceled()) + { + progress->Close(); + return false; + } + int percentage = static_cast(current) * 100 / total; + if (progress->GetPercentage() != percentage) + { + progress->SetPercentage(percentage); + progress->Progress(); + } + current++; + } + } + + if (progress) + progress->Close(); + return true; +} + void CTextureCache::CleanTimer() { CServiceBroker::GetJobManager()->Submit( [this]() { - auto next = ScanOldestCache(); + auto next = m_cleaningInProgress.test_and_set() ? std::chrono::hours(1) : ScanOldestCache(); + m_cleaningInProgress.clear(); m_cleanTimer.Start(next); }, CJob::PRIORITY_LOW_PAUSABLE); diff --git a/xbmc/TextureCache.h b/xbmc/TextureCache.h index 6b0bf44d219e0..3d77f2c6ab760 100644 --- a/xbmc/TextureCache.h +++ b/xbmc/TextureCache.h @@ -15,12 +15,14 @@ #include "threads/Timer.h" #include "utils/JobManager.h" +#include #include #include #include #include #include +class CGUIDialogProgress; class CJob; class CURL; class CTexture; @@ -155,6 +157,8 @@ class CTextureCache : public CJobQueue bool Export(const std::string &image, const std::string &destination, bool overwrite); bool Export(const std::string &image, const std::string &destination); //! @todo BACKWARD COMPATIBILITY FOR MUSIC THUMBS + bool CleanAllUnusedImages(); + private: // private construction, and no assignments; use the provided singleton methods CTextureCache(const CTextureCache&) = delete; @@ -218,7 +222,9 @@ class CTextureCache : public CJobQueue void CleanTimer(); std::chrono::milliseconds ScanOldestCache(); + bool CleanAllUnusedImagesJob(CGUIDialogProgress* progress); + std::atomic_flag m_cleaningInProgress; CTimer m_cleanTimer; CCriticalSection m_databaseSection; CTextureDatabase m_database; diff --git a/xbmc/settings/MediaSettings.cpp b/xbmc/settings/MediaSettings.cpp index 097a9c11f0cbe..76c97b32895e1 100644 --- a/xbmc/settings/MediaSettings.cpp +++ b/xbmc/settings/MediaSettings.cpp @@ -10,6 +10,7 @@ #include "PlayListPlayer.h" #include "ServiceBroker.h" +#include "TextureCache.h" #include "cores/RetroPlayer/RetroPlayerUtils.h" #include "dialogs/GUIDialogFileBrowser.h" #include "guilib/LocalizeStrings.h" @@ -367,6 +368,10 @@ void CMediaSettings::OnSettingAction(const std::shared_ptr& sett videodatabase.Close(); } } + else if (settingId == CSettings::SETTING_MAINTENANCE_CLEANIMAGECACHE) + { + CServiceBroker::GetTextureCache()->CleanAllUnusedImages(); + } } void CMediaSettings::OnSettingChanged(const std::shared_ptr& setting) diff --git a/xbmc/settings/Settings.cpp b/xbmc/settings/Settings.cpp index a32f5f17bcf6d..d80354679a1a9 100644 --- a/xbmc/settings/Settings.cpp +++ b/xbmc/settings/Settings.cpp @@ -560,6 +560,7 @@ void CSettings::InitializeISettingCallbacks() settingSet.insert(CSettings::SETTING_VIDEOLIBRARY_IMPORT); settingSet.insert(CSettings::SETTING_VIDEOLIBRARY_EXPORT); settingSet.insert(CSettings::SETTING_VIDEOLIBRARY_SHOWUNWATCHEDPLOTS); + settingSet.insert(CSettings::SETTING_MAINTENANCE_CLEANIMAGECACHE); GetSettingsManager()->RegisterCallback(&CMediaSettings::GetInstance(), settingSet); settingSet.clear(); diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h index 354756d133475..dfc87db9f286b 100644 --- a/xbmc/settings/Settings.h +++ b/xbmc/settings/Settings.h @@ -282,6 +282,7 @@ class CSettings : public CSettingsBase, public CSettingCreator, public CSettingC static constexpr auto SETTING_MUSICLIBRARY_EXPORT_ARTWORK = "musiclibrary.exportartwork"; static constexpr auto SETTING_MUSICLIBRARY_EXPORT_SKIPNFO = "musiclibrary.exportskipnfo"; static constexpr auto SETTING_MUSICLIBRARY_IMPORT = "musiclibrary.import"; + static constexpr auto SETTING_MAINTENANCE_CLEANIMAGECACHE = "maintenance.cleanimagecache"; static constexpr auto SETTING_MUSICPLAYER_AUTOPLAYNEXTITEM = "musicplayer.autoplaynextitem"; static constexpr auto SETTING_MUSICPLAYER_QUEUEBYDEFAULT = "musicplayer.queuebydefault"; static constexpr auto SETTING_MUSICPLAYER_SEEKSTEPS = "musicplayer.seeksteps"; From f980c6f8fbd97226b24f8734336a82fb9a8284ca Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Tue, 16 Jul 2024 08:46:55 +0200 Subject: [PATCH 293/651] [Windows] remove CWIN32Util::GetSystemPath as is dead code --- xbmc/platform/win32/WIN32Util.cpp | 10 ---------- xbmc/platform/win32/WIN32Util.h | 1 - 2 files changed, 11 deletions(-) diff --git a/xbmc/platform/win32/WIN32Util.cpp b/xbmc/platform/win32/WIN32Util.cpp index 46726af482403..45fb259007b32 100644 --- a/xbmc/platform/win32/WIN32Util.cpp +++ b/xbmc/platform/win32/WIN32Util.cpp @@ -370,16 +370,6 @@ std::string CWIN32Util::GetSpecialFolder(int csidl) } #endif -std::string CWIN32Util::GetSystemPath() -{ -#ifdef TARGET_WINDOWS_STORE - // access to system folder is not allowed in a UWP app - return ""; -#else - return GetSpecialFolder(CSIDL_SYSTEM); -#endif -} - std::string CWIN32Util::GetProfilePath(const bool platformDirectories) { std::string strProfilePath; diff --git a/xbmc/platform/win32/WIN32Util.h b/xbmc/platform/win32/WIN32Util.h index 33033fb431829..db4921b2d2a7c 100644 --- a/xbmc/platform/win32/WIN32Util.h +++ b/xbmc/platform/win32/WIN32Util.h @@ -46,7 +46,6 @@ class CWIN32Util static int GetDesktopColorDepth(); static size_t GetSystemMemorySize(); - static std::string GetSystemPath(); static std::string GetProfilePath(const bool platformDirectories); static std::string UncToSmb(const std::string &strPath); static std::string SmbToUnc(const std::string &strPath); From 7850811b5b693ae961de92fdc7eed08a127dd98e Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Tue, 16 Jul 2024 09:00:40 +0200 Subject: [PATCH 294/651] [Windows] Modernize code to obtain system AppData folder - 'SHGetFolderPathW' is deprecated, updated to 'SHGetKnownFolderPath' - Allows use LocaAppData when RoamingAppData is in UNC path --- xbmc/platform/win32/WIN32Util.cpp | 56 +++++++++++++++++++------------ xbmc/platform/win32/WIN32Util.h | 2 +- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/xbmc/platform/win32/WIN32Util.cpp b/xbmc/platform/win32/WIN32Util.cpp index 45fb259007b32..b5c42a1d82d7c 100644 --- a/xbmc/platform/win32/WIN32Util.cpp +++ b/xbmc/platform/win32/WIN32Util.cpp @@ -349,27 +349,6 @@ size_t CWIN32Util::GetSystemMemorySize() #endif } -#ifdef TARGET_WINDOWS_DESKTOP -std::string CWIN32Util::GetSpecialFolder(int csidl) -{ - std::string strProfilePath; - static const int bufSize = MAX_PATH; - WCHAR* buf = new WCHAR[bufSize]; - - if(SUCCEEDED(SHGetFolderPathW(NULL, csidl, NULL, SHGFP_TYPE_CURRENT, buf))) - { - buf[bufSize-1] = 0; - g_charsetConverter.wToUTF8(buf, strProfilePath); - strProfilePath = UncToSmb(strProfilePath); - } - else - strProfilePath = ""; - - delete[] buf; - return strProfilePath; -} -#endif - std::string CWIN32Util::GetProfilePath(const bool platformDirectories) { std::string strProfilePath; @@ -380,7 +359,7 @@ std::string CWIN32Util::GetProfilePath(const bool platformDirectories) std::string strHomePath = CUtil::GetHomePath(); if (platformDirectories) - strProfilePath = URIUtils::AddFileToFolder(GetSpecialFolder(CSIDL_APPDATA|CSIDL_FLAG_CREATE), CCompileInfo::GetAppName()); + strProfilePath = URIUtils::AddFileToFolder(GetAppDataFolder(), CCompileInfo::GetAppName()); else strProfilePath = URIUtils::AddFileToFolder(strHomePath , "portable_data"); @@ -392,6 +371,39 @@ std::string CWIN32Util::GetProfilePath(const bool platformDirectories) return strProfilePath; } +#ifdef TARGET_WINDOWS_DESKTOP +std::string CWIN32Util::GetAppDataFolder() +{ + std::string profilePath; + WCHAR* path = nullptr; + + // First get the roaming appdata location. + // All current users use this folder, must not break their setup. + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, NULL, &path))) + { + g_charsetConverter.wToUTF8(path, profilePath); + // We do not support appdata on a UNC path. + if (profilePath.starts_with("\\\\")) + profilePath.clear(); + } + + // Must always free, even if failed. This handles NULL, no need to check. + CoTaskMemFree(path); + path = nullptr; + + // If we still do not have the data folder, get the local appdata path. + // This will only happen for new users with redirected roaming appdata. + if (profilePath.empty()) + { + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &path))) + g_charsetConverter.wToUTF8(path, profilePath); + CoTaskMemFree(path); + } + + return profilePath; +} +#endif + std::string CWIN32Util::UncToSmb(const std::string &strPath) { std::string strRetPath(strPath); diff --git a/xbmc/platform/win32/WIN32Util.h b/xbmc/platform/win32/WIN32Util.h index db4921b2d2a7c..5e948a39aff34 100644 --- a/xbmc/platform/win32/WIN32Util.h +++ b/xbmc/platform/win32/WIN32Util.h @@ -67,7 +67,7 @@ class CWIN32Util static BOOL IsCurrentUserLocalAdministrator(); #ifdef TARGET_WINDOWS_DESKTOP - static std::string GetSpecialFolder(int csidl); + static std::string GetAppDataFolder(); static LONG UtilRegGetValue( const HKEY hKey, const char *const pcKey, DWORD *const pdwType, char **const ppcBuffer, DWORD *const pdwSizeBuff, const DWORD dwSizeAdd ); static bool UtilRegOpenKeyEx( const HKEY hKeyParent, const char *const pcKey, const REGSAM rsAccessRights, HKEY *hKey, const bool bReadX64= false ); static bool GetFocussedProcess(std::string &strProcessFile); From 1b1f3635d4c777adeaaf70f3047511e9f728f5b1 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 19 Jun 2024 18:03:47 +1000 Subject: [PATCH 295/651] [cmake] Allow overriding CXX_STANDARD by using CACHE variable --- cmake/scripts/common/CompilerSettings.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/scripts/common/CompilerSettings.cmake b/cmake/scripts/common/CompilerSettings.cmake index c4ea3231e1146..d5dc875674c52 100644 --- a/cmake/scripts/common/CompilerSettings.cmake +++ b/cmake/scripts/common/CompilerSettings.cmake @@ -1,5 +1,5 @@ # Languages and global compiler settings -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 20 CACHE STRING "CXX_STANDARD") set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_ASM_FLAGS "${CMAKE_C_FLAGS} -x assembler-with-cpp") From 8e7203d3e812bc2bcf11146a4975d42165d9ffd0 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Tue, 16 Jul 2024 23:33:22 +0200 Subject: [PATCH 296/651] Use CJNIContext class to call getSystemService() function --- xbmc/platform/android/network/NetworkAndroid.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/xbmc/platform/android/network/NetworkAndroid.cpp b/xbmc/platform/android/network/NetworkAndroid.cpp index 0db2e99c3b715..738bed7cdd22d 100644 --- a/xbmc/platform/android/network/NetworkAndroid.cpp +++ b/xbmc/platform/android/network/NetworkAndroid.cpp @@ -6,17 +6,15 @@ * See LICENSES/README.md for more information. */ - #include "NetworkAndroid.h" #include "utils/StringUtils.h" #include "utils/log.h" -#include "platform/android/activity/XBMCApp.h" - #include #include +#include #include #include #include @@ -50,7 +48,7 @@ std::vector CNetworkInterfaceAndroid::GetNameServers() bool CNetworkInterfaceAndroid::IsEnabled() const { - CJNIConnectivityManager connman(CXBMCApp::getSystemService(CJNIContext::CONNECTIVITY_SERVICE)); + CJNIConnectivityManager connman(CJNIContext::getSystemService(CJNIContext::CONNECTIVITY_SERVICE)); CJNINetworkInfo ni = connman.getNetworkInfo(m_network); if (!ni) return false; @@ -60,7 +58,7 @@ bool CNetworkInterfaceAndroid::IsEnabled() const bool CNetworkInterfaceAndroid::IsConnected() const { - CJNIConnectivityManager connman(CXBMCApp::getSystemService(CJNIContext::CONNECTIVITY_SERVICE)); + CJNIConnectivityManager connman(CJNIContext::getSystemService(CJNIContext::CONNECTIVITY_SERVICE)); CJNINetworkInfo ni = connman.getNetworkInfo(m_network); if (!ni) return false; @@ -237,7 +235,7 @@ CNetworkAndroid::CNetworkAndroid() : CNetworkBase(), CJNIXBMCConnectivityManager { RetrieveInterfaces(); - CJNIConnectivityManager connman{CXBMCApp::getSystemService(CJNIContext::CONNECTIVITY_SERVICE)}; + CJNIConnectivityManager connman{CJNIContext::getSystemService(CJNIContext::CONNECTIVITY_SERVICE)}; connman.registerDefaultNetworkCallback(this->get_raw()); } @@ -248,7 +246,7 @@ CNetworkAndroid::~CNetworkAndroid() for (auto intf : m_oldInterfaces) delete intf; - CJNIConnectivityManager connman{CXBMCApp::getSystemService(CJNIContext::CONNECTIVITY_SERVICE)}; + CJNIConnectivityManager connman{CJNIContext::getSystemService(CJNIContext::CONNECTIVITY_SERVICE)}; connman.unregisterNetworkCallback(this->get_raw()); } @@ -331,7 +329,7 @@ void CNetworkAndroid::RetrieveInterfaces() m_oldInterfaces = m_interfaces; m_interfaces.clear(); - CJNIConnectivityManager connman(CXBMCApp::getSystemService(CJNIContext::CONNECTIVITY_SERVICE)); + CJNIConnectivityManager connman(CJNIContext::getSystemService(CJNIContext::CONNECTIVITY_SERVICE)); std::vector networks = connman.getAllNetworks(); for (const auto& n : networks) @@ -364,7 +362,7 @@ void CNetworkAndroid::onAvailable(const CJNINetwork n) { CLog::Log(LOGDEBUG, "CNetworkAndroid::onAvailable The default network is now: {}", n.toString()); - CJNIConnectivityManager connman{CXBMCApp::getSystemService(CJNIContext::CONNECTIVITY_SERVICE)}; + CJNIConnectivityManager connman{CJNIContext::getSystemService(CJNIContext::CONNECTIVITY_SERVICE)}; CJNILinkProperties lp = connman.getLinkProperties(n); if (lp) From 17fffc49a8e5c13890dcf9a05c67a09cecca5e11 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Tue, 16 Jul 2024 23:36:41 +0200 Subject: [PATCH 297/651] Unnecessary declaration of CXBMCApp as a friend class --- xbmc/platform/android/network/NetworkAndroid.h | 1 - 1 file changed, 1 deletion(-) diff --git a/xbmc/platform/android/network/NetworkAndroid.h b/xbmc/platform/android/network/NetworkAndroid.h index 52b65c443bf26..5886f8080f230 100644 --- a/xbmc/platform/android/network/NetworkAndroid.h +++ b/xbmc/platform/android/network/NetworkAndroid.h @@ -47,7 +47,6 @@ class CNetworkInterfaceAndroid : public CNetworkInterface class CNetworkAndroid : public CNetworkBase, public jni::CJNIXBMCConnectivityManagerNetworkCallback { - friend class CXBMCApp; public: CNetworkAndroid(); From 35daf740e58d60f2335de9961cbc69cd960e2980 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Fri, 19 Jul 2024 13:43:38 +0200 Subject: [PATCH 298/651] [Android] Move AndroidFeatures logic to CPUInfoAndroid --- xbmc/platform/android/CMakeLists.txt | 5 ++- xbmc/platform/android/CPUInfoAndroid.cpp | 30 ++++++++++++--- xbmc/platform/android/CPUInfoAndroid.h | 5 ++- .../android/activity/AndroidFeatures.cpp | 38 ------------------- .../android/activity/AndroidFeatures.h | 19 ---------- xbmc/platform/android/activity/CMakeLists.txt | 10 ++--- 6 files changed, 35 insertions(+), 72 deletions(-) delete mode 100644 xbmc/platform/android/activity/AndroidFeatures.cpp delete mode 100644 xbmc/platform/android/activity/AndroidFeatures.h diff --git a/xbmc/platform/android/CMakeLists.txt b/xbmc/platform/android/CMakeLists.txt index d4d65d5ad24a1..fd4e994d41414 100644 --- a/xbmc/platform/android/CMakeLists.txt +++ b/xbmc/platform/android/CMakeLists.txt @@ -1,10 +1,13 @@ set(SOURCES CPUInfoAndroid.cpp GPUInfoAndroid.cpp MemUtils.cpp - PlatformAndroid.cpp) + PlatformAndroid.cpp + ${NDKROOT}/sources/android/cpufeatures/cpu-features.c) set(HEADERS CPUInfoAndroid.h GPUInfoAndroid.h PlatformAndroid.h) core_add_library(androidsupport) +target_include_directories(${CORE_LIBRARY} SYSTEM + PRIVATE ${NDKROOT}/sources/android/cpufeatures) diff --git a/xbmc/platform/android/CPUInfoAndroid.cpp b/xbmc/platform/android/CPUInfoAndroid.cpp index 3ccc5752e99d1..cfe71f21ef2e1 100644 --- a/xbmc/platform/android/CPUInfoAndroid.cpp +++ b/xbmc/platform/android/CPUInfoAndroid.cpp @@ -9,10 +9,8 @@ #include "CPUInfoAndroid.h" #include "URL.h" +#include "cpu-features.h" #include "utils/StringUtils.h" -#include "utils/Temperature.h" - -#include "platform/android/activity/AndroidFeatures.h" #include @@ -54,7 +52,7 @@ CCPUInfoAndroid::CCPUInfoAndroid() : m_posixFile(std::make_unique()) m_posixFile->Close(); } - m_cpuCount = CAndroidFeatures::GetCPUCount(); + m_cpuCount = GetCPUCount(); for (int i = 0; i < m_cpuCount; i++) { @@ -63,7 +61,7 @@ CCPUInfoAndroid::CCPUInfoAndroid() : m_posixFile(std::make_unique()) m_cores.emplace_back(core); } - if (CAndroidFeatures::HasNeon()) + if (HasNeon()) m_cpuFeatures |= CPU_FEATURE_NEON; } @@ -86,3 +84,25 @@ float CCPUInfoAndroid::GetCPUFrequency() return freq; } + +int CCPUInfoAndroid::GetCPUCount() +{ + static int count = -1; + + if (count == -1) + count = android_getCpuCount(); + + return count; +} + +bool CCPUInfoAndroid::HasNeon() +{ + // All ARMv8-based devices support Neon - https://developer.android.com/ndk/guides/cpu-arm-neon + if (android_getCpuFamily() == ANDROID_CPU_FAMILY_ARM64) + return true; + + if (android_getCpuFamily() == ANDROID_CPU_FAMILY_ARM) + return ((android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0); + + return false; +} diff --git a/xbmc/platform/android/CPUInfoAndroid.h b/xbmc/platform/android/CPUInfoAndroid.h index edb26e5198a95..2ce5e7f451dea 100644 --- a/xbmc/platform/android/CPUInfoAndroid.h +++ b/xbmc/platform/android/CPUInfoAndroid.h @@ -8,8 +8,6 @@ #pragma once -#include "utils/Temperature.h" - #include "platform/posix/CPUInfoPosix.h" #include "platform/posix/filesystem/PosixFile.h" @@ -29,4 +27,7 @@ class CCPUInfoAndroid : public CCPUInfoPosix private: std::unique_ptr m_posixFile; + + int GetCPUCount(); + bool HasNeon(); }; diff --git a/xbmc/platform/android/activity/AndroidFeatures.cpp b/xbmc/platform/android/activity/AndroidFeatures.cpp deleted file mode 100644 index d934c6240732c..0000000000000 --- a/xbmc/platform/android/activity/AndroidFeatures.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2012-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 "AndroidFeatures.h" - -#include "utils/log.h" - -#include -#include - -bool CAndroidFeatures::HasNeon() -{ - // All ARMv8-based devices support Neon - https://developer.android.com/ndk/guides/cpu-arm-neon - if (android_getCpuFamily() == ANDROID_CPU_FAMILY_ARM64) - return true; - - if (android_getCpuFamily() == ANDROID_CPU_FAMILY_ARM) - return ((android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0); - - return false; -} - -int CAndroidFeatures::GetCPUCount() -{ - static int count = -1; - - if (count == -1) - { - count = android_getCpuCount(); - } - return count; -} - diff --git a/xbmc/platform/android/activity/AndroidFeatures.h b/xbmc/platform/android/activity/AndroidFeatures.h deleted file mode 100644 index 957d58a351ee1..0000000000000 --- a/xbmc/platform/android/activity/AndroidFeatures.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2012-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 - -class CAndroidFeatures -{ - public: - - static bool HasNeon(); - static int GetCPUCount(); -}; diff --git a/xbmc/platform/android/activity/CMakeLists.txt b/xbmc/platform/android/activity/CMakeLists.txt index 55714df929711..e2ba762a0d138 100644 --- a/xbmc/platform/android/activity/CMakeLists.txt +++ b/xbmc/platform/android/activity/CMakeLists.txt @@ -1,5 +1,4 @@ set(SOURCES android_main.cpp - AndroidFeatures.cpp AndroidJoyStick.cpp AndroidKey.cpp AndroidMouse.cpp @@ -22,11 +21,9 @@ set(SOURCES android_main.cpp JNIXBMCSpeechRecognitionListener.cpp JNIXBMCConnectivityManagerNetworkCallback.cpp JNIXBMCBroadcastReceiver.cpp - ${NDKROOT}/sources/android/native_app_glue/android_native_app_glue.c - ${NDKROOT}/sources/android/cpufeatures/cpu-features.c) + ${NDKROOT}/sources/android/native_app_glue/android_native_app_glue.c) -set(HEADERS AndroidFeatures.h - AndroidJoyStick.h +set(HEADERS AndroidJoyStick.h AndroidKey.h AndroidMouse.h AndroidTouch.h @@ -53,5 +50,4 @@ set(HEADERS AndroidFeatures.h core_add_library(platform_android_activity) target_include_directories(${CORE_LIBRARY} SYSTEM - PRIVATE ${NDKROOT}/sources/android/native_app_glue - ${NDKROOT}/sources/android/cpufeatures) + PRIVATE ${NDKROOT}/sources/android/native_app_glue) From 70d760bd1e02dfd9df9bdb547c1bfca61bbae822 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 21 Jul 2024 07:48:28 +1000 Subject: [PATCH 299/651] [buildsteps] android update to jenkins docker info Buildsteps are still used for addon CI jobs. we need this data to match the pipeline CI (and therefore the actual docker path structure) to succed --- tools/buildsteps/android-arm64-v8a/configure-depends | 2 +- tools/buildsteps/android-x86_64/configure-depends | 2 +- tools/buildsteps/android/configure-depends | 2 +- tools/buildsteps/androidx86/configure-depends | 2 +- tools/buildsteps/defaultenv | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/buildsteps/android-arm64-v8a/configure-depends b/tools/buildsteps/android-arm64-v8a/configure-depends index cec12b7e34466..ba84154a30600 100644 --- a/tools/buildsteps/android-arm64-v8a/configure-depends +++ b/tools/buildsteps/android-arm64-v8a/configure-depends @@ -3,7 +3,7 @@ XBMC_PLATFORM_DIR=android . $WORKSPACE/tools/buildsteps/defaultenv #the following path must exist on the slave and use the defined scheme here! -NDK_PATH=$ANDROID_DEV_ROOT/android-ndk-r$NDK_VERSION +NDK_PATH=$SDK_PATH/ndk/$NDK_VERSION if [ "$(pathChanged $WORKSPACE/tools/depends)" == "1" ] then diff --git a/tools/buildsteps/android-x86_64/configure-depends b/tools/buildsteps/android-x86_64/configure-depends index c27d6d87231d7..2904210955c39 100644 --- a/tools/buildsteps/android-x86_64/configure-depends +++ b/tools/buildsteps/android-x86_64/configure-depends @@ -3,7 +3,7 @@ XBMC_PLATFORM_DIR=android . $WORKSPACE/tools/buildsteps/defaultenv #the following path must exist on the slave and use the defined scheme here! -NDK_PATH=$ANDROID_DEV_ROOT/android-ndk-r$NDK_VERSION +NDK_PATH=$SDK_PATH/ndk/$NDK_VERSION if [ "$(pathChanged $WORKSPACE/tools/depends)" == "1" ] then diff --git a/tools/buildsteps/android/configure-depends b/tools/buildsteps/android/configure-depends index 649ff6dc1ffe6..c31f9638d32bf 100644 --- a/tools/buildsteps/android/configure-depends +++ b/tools/buildsteps/android/configure-depends @@ -3,7 +3,7 @@ XBMC_PLATFORM_DIR=android . $WORKSPACE/tools/buildsteps/defaultenv #the following path must exist on the slave and use the defined scheme here! -NDK_PATH=$ANDROID_DEV_ROOT/android-ndk-r$NDK_VERSION +NDK_PATH=$SDK_PATH/ndk/$NDK_VERSION if [ "$(pathChanged $WORKSPACE/tools/depends)" == "1" ] then diff --git a/tools/buildsteps/androidx86/configure-depends b/tools/buildsteps/androidx86/configure-depends index 9fb0e203f82f4..a6b88b3634039 100644 --- a/tools/buildsteps/androidx86/configure-depends +++ b/tools/buildsteps/androidx86/configure-depends @@ -3,7 +3,7 @@ XBMC_PLATFORM_DIR=android . $WORKSPACE/tools/buildsteps/defaultenv #the following path must exist on the slave and use the defined scheme here! -NDK_PATH=$ANDROID_DEV_ROOT/android-ndk-r$NDK_VERSION +NDK_PATH=$SDK_PATH/ndk/$NDK_VERSION if [ "$(pathChanged $WORKSPACE/tools/depends)" == "1" ] then diff --git a/tools/buildsteps/defaultenv b/tools/buildsteps/defaultenv index 8bc7bb04b7e46..faa33802c4f87 100644 --- a/tools/buildsteps/defaultenv +++ b/tools/buildsteps/defaultenv @@ -56,7 +56,7 @@ case $XBMC_PLATFORM_DIR in ;; android) - DEFAULT_NDK_VERSION="26c" # NDK package version (newer API can be inside) + DEFAULT_NDK_VERSION="26.2.11394342" # NDK package version (newer API can be inside) DEFAULT_NDK_API="24" # Nougat API level (24) defined in package ./sysroot/usr/include/android/api-level.h DEFAULT_XBMC_DEPENDS_ROOT=$WORKSPACE/tools/depends/xbmc-depends DEFAULT_CONFIGURATION="RelWithDebInfo" From 4f86f22f6fe73b1ca5c39eca9b1ce13ae86b3779 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sat, 20 Jul 2024 12:10:14 +0200 Subject: [PATCH 300/651] [DirectSound] Fix buffer underrun for Bluetooth audio devices --- .../AudioEngine/Sinks/AESinkDirectSound.cpp | 23 +++++++++++++++++-- .../Sinks/windows/AESinkFactoryWin.h | 1 + .../Sinks/windows/AESinkFactoryWin32.cpp | 11 +++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp index ca02e2bafa59b..43216796878e5 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp @@ -124,6 +124,7 @@ bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device) if (m_initialized) return false; + bool deviceIsBluetooth = false; LPGUID deviceGUID = nullptr; RPC_WSTR wszUuid = nullptr; HRESULT hr = E_FAIL; @@ -152,6 +153,21 @@ bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device) if (hr == RPC_S_OK) RpcStringFree(&wszUuid); } + // Detect a Bluetooth device + for (const auto& device : CAESinkFactoryWin::GetRendererDetails()) + { + if (StringUtils::CompareNoCase(device.strDeviceId, strDeviceGUID) != 0) + continue; + + if (device.strDeviceEnumerator == "BTHENUM") + { + deviceIsBluetooth = true; + CLog::LogF(LOGDEBUG, "Audio device '{}' is detected as Bluetooth device", deviceFriendlyName); + } + + break; + } + hr = DirectSoundCreate(deviceGUID, m_pDSound.ReleaseAndGetAddressOf(), nullptr); if (FAILED(hr)) @@ -218,10 +234,13 @@ bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device) m_AvgBytesPerSec = wfxex.Format.nAvgBytesPerSec; - const unsigned int uiFrameCount = static_cast(format.m_sampleRate * 0.050); // 50ms chunks + const unsigned int chunkDurationMs = deviceIsBluetooth ? 50 : 20; + const unsigned int uiFrameCount = format.m_sampleRate * chunkDurationMs / 1000U; m_dwFrameSize = wfxex.Format.nBlockAlign; m_dwChunkSize = m_dwFrameSize * uiFrameCount; - m_dwBufferLen = m_dwChunkSize * 8; // 400ms total buffer + + // BT: 500ms total buffer (50 * 10); non-BT: 200ms total buffer (20 * 10) + m_dwBufferLen = m_dwChunkSize * 10; // fill in the secondary sound buffer descriptor DSBUFFERDESC dsbdesc = {}; diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h index dbe53f3203fc8..61db03f5c472d 100644 --- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h @@ -178,6 +178,7 @@ struct RendererDetail std::string strDeviceId; std::string strDescription; std::string strWinDevType; + std::string strDeviceEnumerator; AEDeviceType eDeviceType; unsigned int nChannels; unsigned int uiChannelMask; diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp index c728155c56a2f..3b6abaae9038c 100644 --- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp @@ -129,6 +129,17 @@ std::vector CAESinkFactoryWin::GetRendererDetails() details.uiChannelMask = std::max(varName.uintVal, (unsigned int)KSAUDIO_SPEAKER_STEREO); PropVariantClear(&varName); + hr = pProperty->GetValue(PKEY_Device_EnumeratorName, &varName); + if (FAILED(hr)) + { + CLog::LogF(LOGERROR, "Retrieval of endpoint enumerator name failed."); + goto failed; + } + + details.strDeviceEnumerator = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal); + StringUtils::ToUpper(details.strDeviceEnumerator); + PropVariantClear(&varName); + if (pDevice->GetId(&pwszID) == S_OK) { if (wstrDDID.compare(pwszID) == 0) From dc13ac4fe229448ad9bacb296f4f956bc57a006d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Mon, 22 Jul 2024 01:12:27 +0200 Subject: [PATCH 301/651] SMBWSDiscoveryListener: Fix clang warning SMBWSDiscoveryListener.cpp:275:19: warning: variable length arrays in C++ are a Clang extension [-Wvla-cxx-extension] 275 | char msgbuf[UDPBUFFSIZE]; | ^~~~~~~~~~~ SMBWSDiscoveryListener.cpp:275:19: note: implicit use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function Also makes the instantiated class a little smaller. --- xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.h b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.h index 3459973c8293a..7f8f3da4e064b 100644 --- a/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.h +++ b/xbmc/platform/posix/filesystem/SMBWSDiscoveryListener.h @@ -111,16 +111,16 @@ class CWSDiscoveryListenerUDP : public CThread const std::string wsd_instance_address; // Number of sends for UDP messages - const int retries = 4; + static constexpr int retries{4}; // Max udp packet size (+ UDP header + IP header overhead = 65535) - const int UDPBUFFSIZE = 65507; + static constexpr int UDPBUFFSIZE{65507}; // Port for unicast/multicast WSD traffic - const int wsdUDP = 3702; + static constexpr int wsdUDP{3702}; // ipv4 multicast group WSD - https://specs.xmlsoap.org/ws/2005/04/discovery/ws-discovery.pdf - const char* WDSIPv4MultiGroup = "239.255.255.250"; + static constexpr char WDSIPv4MultiGroup[]{"239.255.255.250"}; // ToDo: ipv6 broadcast address // const char* WDSIPv6MultiGroup = "FF02::C" From 3a6f88c2f610b8e5b0fa773522ef6606c4043321 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 22 Jul 2024 22:19:21 +0200 Subject: [PATCH 302/651] xbmc/cdrip: cppcheck performance fixes --- xbmc/cdrip/CDDARipJob.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/cdrip/CDDARipJob.h b/xbmc/cdrip/CDDARipJob.h index 11d92eb976233..be98321b9170e 100644 --- a/xbmc/cdrip/CDDARipJob.h +++ b/xbmc/cdrip/CDDARipJob.h @@ -52,7 +52,7 @@ class CCDDARipJob : public CJob const char* GetType() const override { return "cdrip"; } bool operator==(const CJob* job) const override; bool DoWork() override; - std::string GetOutput() const { return m_output; } + const std::string& GetOutput() const { return m_output; } protected: /*! From cdef734627a1cbaef2c96e69374fbdba9f1f6cce Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 22 Jul 2024 22:26:27 +0200 Subject: [PATCH 303/651] xbmc/dialogs: cppcheck performance fixes --- xbmc/dialogs/GUIDialogColorPicker.cpp | 2 +- xbmc/dialogs/GUIDialogColorPicker.h | 2 +- xbmc/dialogs/GUIDialogMediaFilter.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/xbmc/dialogs/GUIDialogColorPicker.cpp b/xbmc/dialogs/GUIDialogColorPicker.cpp index cbbe1102d423d..467b2bcef3a3a 100644 --- a/xbmc/dialogs/GUIDialogColorPicker.cpp +++ b/xbmc/dialogs/GUIDialogColorPicker.cpp @@ -178,7 +178,7 @@ void CGUIDialogColorPicker::LoadColors(const std::string& filePath) __FUNCTION__); } -std::string CGUIDialogColorPicker::GetSelectedColor() const +const std::string& CGUIDialogColorPicker::GetSelectedColor() const { return m_selectedColor; } diff --git a/xbmc/dialogs/GUIDialogColorPicker.h b/xbmc/dialogs/GUIDialogColorPicker.h index fba8ebcb467d3..4cc7a6d9e3c0a 100644 --- a/xbmc/dialogs/GUIDialogColorPicker.h +++ b/xbmc/dialogs/GUIDialogColorPicker.h @@ -39,7 +39,7 @@ class CGUIDialogColorPicker : public CGUIDialogBoxBase */ void LoadColors(const std::string& filePath); /*! \brief Get the hex value of the selected color */ - std::string GetSelectedColor() const; + const std::string& GetSelectedColor() const; /*! \brief Set the selected color by hex value */ void SetSelectedColor(const std::string& hexColor); /*! \brief Set the focus to the control button */ diff --git a/xbmc/dialogs/GUIDialogMediaFilter.cpp b/xbmc/dialogs/GUIDialogMediaFilter.cpp index df904cb354c2c..fb85c6910221b 100644 --- a/xbmc/dialogs/GUIDialogMediaFilter.cpp +++ b/xbmc/dialogs/GUIDialogMediaFilter.cpp @@ -558,12 +558,12 @@ bool CGUIDialogMediaFilter::SetPath(const std::string &path) delete m_dbUrl; bool video = false; - if (path.find("videodb://") == 0) + if (path.starts_with("videodb://")) { m_dbUrl = new CVideoDbUrl(); video = true; } - else if (path.find("musicdb://") == 0) + else if (path.starts_with("musicdb://")) m_dbUrl = new CMusicDbUrl(); else { From 8e10088b9c5135b876a8cfe786e23c8a9097db76 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 22 Jul 2024 22:28:10 +0200 Subject: [PATCH 304/651] xbmc/events: cppcheck performance fixes --- xbmc/events/EventLog.cpp | 2 +- xbmc/events/EventLog.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/events/EventLog.cpp b/xbmc/events/EventLog.cpp index 89eff93195a13..ee84409966667 100644 --- a/xbmc/events/EventLog.cpp +++ b/xbmc/events/EventLog.cpp @@ -57,7 +57,7 @@ EventLevel CEventLog::EventLevelFromString(const std::string& level) return EventLevel::Information; } -Events CEventLog::Get() const +const Events& CEventLog::Get() const { return m_events; } diff --git a/xbmc/events/EventLog.h b/xbmc/events/EventLog.h index 035e448610f35..4c71eef767b15 100644 --- a/xbmc/events/EventLog.h +++ b/xbmc/events/EventLog.h @@ -28,7 +28,7 @@ class CEventLog CEventLog& operator=(CEventLog const&) = delete; ~CEventLog() = default; - Events Get() const; + const Events& Get() const; Events Get(EventLevel level, bool includeHigherLevels = false) const; EventPtr Get(const std::string& eventIdentifier) const; From 9483b111873ab8d5ee840dcee45b5b06bf9ed765 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 22 Jul 2024 22:30:02 +0200 Subject: [PATCH 305/651] xbmc/favourites: cppcheck performance fixes --- xbmc/favourites/FavouritesURL.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xbmc/favourites/FavouritesURL.h b/xbmc/favourites/FavouritesURL.h index 7261484632a08..8ab19aab93593 100644 --- a/xbmc/favourites/FavouritesURL.h +++ b/xbmc/favourites/FavouritesURL.h @@ -36,7 +36,7 @@ class CFavouritesURL virtual ~CFavouritesURL() = default; - std::string GetURL() const { return m_path; } + const std::string& GetURL() const { return m_path; } bool IsValid() const { return m_valid && m_exec.IsValid(); } @@ -45,10 +45,10 @@ class CFavouritesURL std::string GetExecString() const { return m_exec.GetExecString(); } Action GetAction() const { return m_action; } std::vector GetParams() const { return m_exec.GetParams(); } - std::string GetTarget() const { return m_target; } + const std::string& GetTarget() const { return m_target; } int GetWindowID() const { return m_windowId; } - std::string GetActionLabel() const { return m_actionLabel; } - std::string GetProviderLabel() const { return m_providerLabel; } + const std::string& GetActionLabel() const { return m_actionLabel; } + const std::string& GetProviderLabel() const { return m_providerLabel; } private: bool Parse(CFavouritesURL::Action action, const std::vector& params); From 56a02c08171d839b0243ca1fe60a82f6e11b2613 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 22 Jul 2024 22:35:03 +0200 Subject: [PATCH 306/651] xbmc/imagefiles: cppcheck performance fixes --- xbmc/imagefiles/ImageFileURL.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/imagefiles/ImageFileURL.h b/xbmc/imagefiles/ImageFileURL.h index e55d5b1d42256..d10f286e00de1 100644 --- a/xbmc/imagefiles/ImageFileURL.h +++ b/xbmc/imagefiles/ImageFileURL.h @@ -58,10 +58,10 @@ class CImageFileURL */ std::string ToCacheKey() const; - std::string GetTargetFile() const { return m_filePath; } + const std::string& GetTargetFile() const { return m_filePath; } bool IsSpecialImage() const { return !m_specialType.empty(); } - std::string GetSpecialType() const { return m_specialType; } + const std::string& GetSpecialType() const { return m_specialType; } bool flipped{false}; From 18ab60cc01c4a211215253f1c2416f91ed06d104 Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 22 Jul 2024 22:42:00 +0200 Subject: [PATCH 307/651] xbmc/pictures: cppcheck performance fixes --- xbmc/pictures/GUIWindowSlideShow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/pictures/GUIWindowSlideShow.cpp b/xbmc/pictures/GUIWindowSlideShow.cpp index 6dc76a5c9a014..9e3819e8099da 100644 --- a/xbmc/pictures/GUIWindowSlideShow.cpp +++ b/xbmc/pictures/GUIWindowSlideShow.cpp @@ -146,9 +146,9 @@ void CBackgroundPicLoader::LoadPic(int iPic, int iSlideNumber, const std::string } CGUIWindowSlideShow::CGUIWindowSlideShow(void) - : CGUIDialog(WINDOW_SLIDESHOW, "SlideShow.xml") + : CGUIDialog(WINDOW_SLIDESHOW, "SlideShow.xml"), + m_Resolution(RES_INVALID) { - m_Resolution = RES_INVALID; m_loadType = KEEP_IN_MEMORY; m_bLoadNextPic = false; CServiceBroker::GetSlideShowDelegator().SetDelegate(this); From 2707223162d6f0986cc6c83939b9a12ea014361a Mon Sep 17 00:00:00 2001 From: Tobias Markus Date: Mon, 22 Jul 2024 22:49:12 +0200 Subject: [PATCH 308/651] xbmc/settings: cppcheck performance fixes --- xbmc/settings/SettingControl.h | 4 ++-- xbmc/settings/lib/Setting.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/xbmc/settings/SettingControl.h b/xbmc/settings/SettingControl.h index 160fcec19fd1d..292e243370a1c 100644 --- a/xbmc/settings/SettingControl.h +++ b/xbmc/settings/SettingControl.h @@ -194,7 +194,7 @@ class CSettingControlList : public CSettingControlFormattedRange int GetAddButtonLabel() const { return m_addButtonLabel; } void SetAddButtonLabel(int label) { m_addButtonLabel = label; } - SettingControlListValueFormatter GetFormatter() const { return m_formatter; } + const SettingControlListValueFormatter& GetFormatter() const { return m_formatter; } void SetFormatter(SettingControlListValueFormatter formatter) { m_formatter = formatter; } bool UseDetails() const { return m_useDetails; } @@ -238,7 +238,7 @@ class CSettingControlSlider : public ISettingControl void SetFormatString(const std::string &formatString) { m_formatString = formatString; } std::string GetDefaultFormatString() const; - SettingControlSliderFormatter GetFormatter() const { return m_formatter; } + const SettingControlSliderFormatter& GetFormatter() const { return m_formatter; } void SetFormatter(SettingControlSliderFormatter formatter) { m_formatter = formatter; } protected: diff --git a/xbmc/settings/lib/Setting.h b/xbmc/settings/lib/Setting.h index 6236cfc478e58..bc4d2cb808f27 100644 --- a/xbmc/settings/lib/Setting.h +++ b/xbmc/settings/lib/Setting.h @@ -326,7 +326,7 @@ class CSettingInt : public CTraitedSetting m_optionsFiller = optionsFiller; m_optionsFillerData = data; } - IntegerSettingOptions GetDynamicOptions() const { return m_dynamicOptions; } + const IntegerSettingOptions& GetDynamicOptions() const { return m_dynamicOptions; } IntegerSettingOptions UpdateDynamicOptions(); SettingOptionsSort GetOptionsSort() const { return m_optionsSort; } void SetOptionsSort(SettingOptionsSort optionsSort) { m_optionsSort = optionsSort; } @@ -470,7 +470,7 @@ class CSettingString : public CTraitedSetting m_optionsFiller = optionsFiller; m_optionsFillerData = data; } - StringSettingOptions GetDynamicOptions() const { return m_dynamicOptions; } + const StringSettingOptions& GetDynamicOptions() const { return m_dynamicOptions; } StringSettingOptions UpdateDynamicOptions(); SettingOptionsSort GetOptionsSort() const { return m_optionsSort; } void SetOptionsSort(SettingOptionsSort optionsSort) { m_optionsSort = optionsSort; } From 40353b068674afa77baa01a8a5e23476ca6489f1 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 23 Jul 2024 17:40:12 +0200 Subject: [PATCH 309/651] Revert "Remove duplicated EpgEventTitle from PVR lists" This reverts commit 648c9a21f674bd71405e8e54111cd682e461ebd8. --- addons/skin.estuary/xml/Variables.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index a40f0e31e0845..6a9b291a76059 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -654,15 +654,15 @@ [COLOR grey]$INFO[ListItem.Timertype][/COLOR] - $INFO[ListItem.EpgEventTitle] | [COLOR grey]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/COLOR] + $INFO[ListItem.EpgEventTitle] | [COLOR grey]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/COLOR] [COLOR grey]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/COLOR] - $INFO[ListItem.EpgEventTitle] + $INFO[ListItem.EpgEventTitle] $INFO[ListItem.Timertype] - $INFO[ListItem.EpgEventTitle] | [COLOR grey]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/COLOR] + $INFO[ListItem.EpgEventTitle] | $VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName] $VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName] - $INFO[ListItem.EpgEventTitle] + $INFO[ListItem.EpgEventTitle] $INFO[ListItem.StartDate,[COLOR grey]$LOCALIZE[552]:[/COLOR] ,[CR]]$INFO[ListItem.StartTime,[COLOR grey]$LOCALIZE[555]:[/COLOR] ,[CR]]$INFO[ListItem.Duration,[COLOR grey]$LOCALIZE[180]:[/COLOR] ] From 7c39e5fda8a65a5041cd2a9091c7e6e480a4c6ae Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Mon, 22 Jul 2024 18:57:32 +0200 Subject: [PATCH 310/651] [gnutls] Retain the warning behavior of implicit function declarations and incompatible integer to pointer conversion --- tools/depends/target/gnutls/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/depends/target/gnutls/Makefile b/tools/depends/target/gnutls/Makefile index 1b1e5e3287767..609180b6b9427 100644 --- a/tools/depends/target/gnutls/Makefile +++ b/tools/depends/target/gnutls/Makefile @@ -35,7 +35,7 @@ CONFIGURE=./configure --prefix=$(PREFIX) \ $(CONFIGURE_OPTIONS) # LLVM 15 has raised this to error by default. drop back to warning -CFLAGS+= -Wno-error=implicit-int +CFLAGS+= -Wno-error=implicit-int -Wno-error=implicit-function-declaration -Wno-error=int-conversion export CFLAGS LIBDYLIB=$(PLATFORM)/lib/.libs/lib$(LIBNAME).a From 6f217b67bf85213f9a866833d249de7600a3ce03 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:55:54 +0200 Subject: [PATCH 311/651] [PVR] Group Manager: Do not show hidden channels in list of ungrouped channels, except for the 'All channels' group. --- xbmc/pvr/channels/PVRChannelGroups.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp index 97033d73fd88b..acf36f41c5f93 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.cpp +++ b/xbmc/pvr/channels/PVRChannelGroups.cpp @@ -250,7 +250,8 @@ std::vector> CPVRChannelGroups::GetMembe const auto allGroupMembers = GetGroupAll()->GetMembers(); for (const auto& groupMember : allGroupMembers) { - if (!group->IsGroupMember(groupMember)) + if (!group->IsGroupMember(groupMember) && + (group->IsChannelsOwner() || !groupMember->Channel()->IsHidden())) result.emplace_back(groupMember); } } From 538dc7d15f25f572bee7bfdfb538a27f48b337fe Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 26 Jul 2024 18:23:42 +0200 Subject: [PATCH 312/651] [PVR] Group Manager: Fix channel numbers in other groups not updated after adding/removing channels to/from 'All channels' group. --- xbmc/pvr/channels/PVRChannelGroups.cpp | 9 +++++++++ xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp | 13 ++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp index acf36f41c5f93..d15c8c530f081 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.cpp +++ b/xbmc/pvr/channels/PVRChannelGroups.cpp @@ -739,6 +739,10 @@ bool CPVRChannelGroups::AppendToGroup( if (group->AppendToGroup(groupMember)) { + // Changes in the all channels group may require resorting/renumbering of other groups. + if (group->IsChannelsOwner()) + UpdateChannelNumbersFromAllChannelsGroup(); + GroupStateChanged(group); return true; } @@ -761,6 +765,11 @@ bool CPVRChannelGroups::RemoveFromGroup(const std::shared_ptr& if (group->RemoveFromGroup(groupMember)) { group->DeleteGroupMember(groupMember); + + // Changes in the all channels group may require resorting/renumbering of other groups. + if (group->IsChannelsOwner()) + UpdateChannelNumbersFromAllChannelsGroup(); + GroupStateChanged(group); return true; } diff --git a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp index ad9ae3e50a25e..d785219a78d61 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp @@ -1046,10 +1046,9 @@ void CGUIDialogPVRChannelManager::SaveList() pDlgProgress->SetPercentage(0); /* persist all channels */ - std::shared_ptr group = - CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(m_bIsRadio); - if (!group) - return; + const std::shared_ptr groupsContainer{ + CServiceBroker::GetPVRManager().ChannelGroups()}; + const std::shared_ptr group{groupsContainer->GetGroupAll(m_bIsRadio)}; for (int iListPtr = 0; iListPtr < m_channelItems->Size(); ++iListPtr) { @@ -1080,11 +1079,7 @@ void CGUIDialogPVRChannelManager::SaveList() } group->SortAndRenumber(); - - const std::shared_ptr channelGroups{ - CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bIsRadio)}; - channelGroups->UpdateChannelNumbersFromAllChannelsGroup(); - channelGroups->PersistAll(); + groupsContainer->Get(m_bIsRadio)->PersistAll(); pDlgProgress->Close(); CONTROL_DISABLE(BUTTON_APPLY); From fcb54d37b17d87265ae04bcc28835d54d3cb4af3 Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Sun, 28 Jul 2024 12:30:11 +0100 Subject: [PATCH 313/651] [MUSIC] Fix separators so that artist sortnames split correctly --- xbmc/music/Song.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/xbmc/music/Song.cpp b/xbmc/music/Song.cpp index 93b2156d7b3bb..d099fc01f38b9 100644 --- a/xbmc/music/Song.cpp +++ b/xbmc/music/Song.cpp @@ -89,14 +89,17 @@ void CSong::SetArtistCredits(const std::vector& names, const std::v artistCredits.clear(); std::vector artistHints = hints; //Split the artist sort string to try and get sort names for individual artists - std::vector artistSort = StringUtils::Split(strArtistSort, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); + std::vector artistSort = StringUtils::Split( + strArtistSort, + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); + + // Vector of possible separators in the order least likely to be part of artist name + static const std::vector separators{ + " feat. ", " ft. ", " Feat. ", " Ft. ", ";", ":", "|", "#", "/", " with ", "&"}; if (!mbids.empty()) { // Have musicbrainz artist info, so use it - // Vector of possible separators in the order least likely to be part of artist name - const std::vector separators{ " feat. ", " ft. ", " Feat. "," Ft. ", ";", ":", "|", "#", "/", " with ", ",", "&" }; - // Establish tag consistency - do the number of musicbrainz ids and number of names in hints or artist match if (mbids.size() != artistHints.size() && mbids.size() != names.size()) { @@ -147,7 +150,7 @@ void CSong::SetArtistCredits(const std::vector& names, const std::v // Try to get number of artist sort names and musicbrainz ids to match. Split sort names // further using multiple possible delimiters, over single separator applied in Tag loader if (artistSort.size() != mbids.size()) - artistSort = StringUtils::SplitMulti(artistSort, { ";", ":", "|", "#" }); + artistSort = StringUtils::SplitMulti(artistSort, separators); for (size_t i = 0; i < mbids.size(); i++) { @@ -185,7 +188,7 @@ void CSong::SetArtistCredits(const std::vector& names, const std::v if (artistSort.size() != artists.size()) // Split artist sort names further using multiple possible delimiters, over single separator applied in Tag loader - artistSort = StringUtils::SplitMulti(artistSort, { ";", ":", "|", "#" }); + artistSort = StringUtils::SplitMulti(artistSort, separators); for (size_t i = 0; i < artists.size(); i++) { From 1a1503cc838b40c651f008eedf43cdc2f55be35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Sun, 28 Jul 2024 20:53:42 +0200 Subject: [PATCH 314/651] KeyboardTranslator: Fix call to `StringUtils::ToLower` 567960ee8e2e changed `strMod` from `std::string` to `const char*` resulting in the call to `StringUtils::ToLower` to not modify `strMod` inplace anymore. --- xbmc/input/keymaps/keyboard/KeyboardTranslator.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xbmc/input/keymaps/keyboard/KeyboardTranslator.cpp b/xbmc/input/keymaps/keyboard/KeyboardTranslator.cpp index 2767022ba8e76..637e551ab39b5 100644 --- a/xbmc/input/keymaps/keyboard/KeyboardTranslator.cpp +++ b/xbmc/input/keymaps/keyboard/KeyboardTranslator.cpp @@ -51,10 +51,10 @@ uint32_t CKeyboardTranslator::TranslateButton(const tinyxml2::XMLElement* pButto button_id = TranslateString(szButton); // Process the ctrl/shift/alt modifiers - const char* strMod; - if (pButton->QueryStringAttribute("mod", &strMod) == tinyxml2::XML_SUCCESS) + const char* cstrMod; + if (pButton->QueryStringAttribute("mod", &cstrMod) == tinyxml2::XML_SUCCESS) { - StringUtils::ToLower(strMod); + const std::string strMod = StringUtils::ToLower(cstrMod); std::vector modArray = StringUtils::Split(strMod, ","); for (auto substr : modArray) From 3b5c485e442d5a32b56539d29fcdd9481eb6c075 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Mon, 29 Jul 2024 15:30:21 -0700 Subject: [PATCH 315/651] [Settings] Show "Maximum rewind time" on basic level --- system/settings/settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/settings/settings.xml b/system/settings/settings.xml index 1c54a059c6615..987e366fd8012 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -2667,7 +2667,7 @@ - 2 + 0 60 10 From f28c6ad514035495f3d4d4eac7a6ad1124c0b2f6 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 17 Jul 2024 10:54:54 +0200 Subject: [PATCH 316/651] [addons][PVR] PVR add-on API: PVR_CHANNEL_GROUP: Replace fixed size char arrays with dynamic arrays. --- .../kodi-dev-kit/include/kodi/AddonBase.h | 118 ++++++++++++++++++ .../kodi/addon-instance/pvr/ChannelGroups.h | 17 ++- .../addon-instance/pvr/pvr_channel_groups.h | 2 +- xbmc/pvr/addons/PVRClient.cpp | 24 +++- xbmc/pvr/channels/PVRChannelGroup.cpp | 8 -- xbmc/pvr/channels/PVRChannelGroup.h | 8 -- .../channels/PVRChannelGroupFromClient.cpp | 9 +- xbmc/pvr/channels/PVRChannelGroupFromClient.h | 2 + 8 files changed, 160 insertions(+), 28 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h index 03b023170bfeb..33ef08ae55afd 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h @@ -75,6 +75,39 @@ struct ATTR_DLL_LOCAL CPrivateBase static AddonGlobalInterface* m_interface; }; +/*! + * @brief Internally used helper to dynamically allocate and fill a char array. + */ +inline char* ATTR_DLL_LOCAL AllocAndCopyString(const char* source) +{ + if (source) + { + const size_t len{strlen(source) + 1}; + char* target = new char[len]; + memcpy(target, source, len); + return target; + } + return nullptr; +} + +/*! + * @brief Internally used helper to delete a string that was allocated via AllocAndCopyString. + */ +inline void ATTR_DLL_LOCAL FreeString(const char* str) +{ + delete[] str; +} + +/*! + * @brief Internally used helper to dynamically reallocate and fill a char array. + */ +inline void ATTR_DLL_LOCAL ReallocAndCopyString(const char** stringToRealloc, + const char* stringToCopy) +{ + FreeString(*stringToRealloc); + *stringToRealloc = AllocAndCopyString(stringToCopy); +} + /* * Internally used helper class to manage processing of a "C" structure in "CPP" * class. @@ -188,6 +221,91 @@ class CStructHdl bool m_owner = false; }; +template +class DynamicCStructHdl +{ +public: + DynamicCStructHdl() : m_cStructure(new C_STRUCT()), m_owner(true) {} + + DynamicCStructHdl(const CPP_CLASS& cppClass) + : m_cStructure(new C_STRUCT(*cppClass.m_cStructure)), m_owner(true) + { + CPP_CLASS::AllocResources(cppClass.m_cStructure, m_cStructure); + } + + DynamicCStructHdl(const C_STRUCT* cStructure) + : m_cStructure(new C_STRUCT(*cStructure)), m_owner(true) + { + CPP_CLASS::AllocResources(cStructure, m_cStructure); + } + + DynamicCStructHdl(C_STRUCT* cStructure) : m_cStructure(cStructure) { assert(cStructure); } + + const DynamicCStructHdl& operator=(const DynamicCStructHdl& right) + { + if (this == &right) + return *this; + + CPP_CLASS::FreeResources(m_cStructure); + if (m_cStructure && !m_owner) + { + memcpy(m_cStructure, right.m_cStructure, sizeof(C_STRUCT)); + } + else + { + if (m_owner) + delete m_cStructure; + m_owner = true; + m_cStructure = new C_STRUCT(*right.m_cStructure); + } + CPP_CLASS::AllocResources(right.m_cStructure, m_cStructure); + return *this; + } + + const DynamicCStructHdl& operator=(const C_STRUCT& right) + { + assert(&right); + + if (m_cStructure == &right) + return *this; + + CPP_CLASS::FreeResources(m_cStructure); + if (m_cStructure && !m_owner) + { + memcpy(m_cStructure, &right, sizeof(C_STRUCT)); + } + else + { + if (m_owner) + delete m_cStructure; + m_owner = true; + m_cStructure = new C_STRUCT(*right); + } + CPP_CLASS::AllocResources(&right, m_cStructure); + return *this; + } + + virtual ~DynamicCStructHdl() + { + if (m_owner) + { + CPP_CLASS::FreeResources(m_cStructure); + delete m_cStructure; + } + } + + operator C_STRUCT*() { return m_cStructure; } + operator const C_STRUCT*() const { return m_cStructure; } + + const C_STRUCT* GetCStructure() const { return m_cStructure; } + +protected: + C_STRUCT* m_cStructure = nullptr; + +private: + bool m_owner = false; +}; + //============================================================================== /// @ingroup cpp_kodi_addon_addonbase_Defs /// @defgroup cpp_kodi_addon_addonbase_Defs_CSettingValue class CSettingValue diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h index 167ce8181f65b..43fee12974a61 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h @@ -34,14 +34,14 @@ namespace addon /// @copydetails cpp_kodi_addon_pvr_Defs_ChannelGroup_PVRChannelGroup_Help /// ///@{ -class PVRChannelGroup : public CStructHdl +class PVRChannelGroup : public DynamicCStructHdl { friend class CInstancePVRClient; public: /*! \cond PRIVATE */ PVRChannelGroup() { memset(m_cStructure, 0, sizeof(PVR_CHANNEL_GROUP)); } - PVRChannelGroup(const PVRChannelGroup& channel) : CStructHdl(channel) {} + PVRChannelGroup(const PVRChannelGroup& group) : DynamicCStructHdl(group) {} /*! \endcond */ /// @defgroup cpp_kodi_addon_pvr_Defs_ChannelGroup_PVRChannelGroup_Help Value Help @@ -62,7 +62,7 @@ class PVRChannelGroup : public CStructHdl /// Name of this channel group. void SetGroupName(const std::string& groupName) { - strncpy(m_cStructure->strGroupName, groupName.c_str(), sizeof(m_cStructure->strGroupName) - 1); + ReallocAndCopyString(&m_cStructure->strGroupName, groupName.c_str()); } /// @brief To get with @ref SetGroupName changed values. @@ -85,9 +85,16 @@ class PVRChannelGroup : public CStructHdl ///@} + static void AllocResources(const PVR_CHANNEL_GROUP* source, PVR_CHANNEL_GROUP* target) + { + target->strGroupName = AllocAndCopyString(source->strGroupName); + } + + static void FreeResources(PVR_CHANNEL_GROUP* target) { FreeString(target->strGroupName); } + private: - PVRChannelGroup(const PVR_CHANNEL_GROUP* channel) : CStructHdl(channel) {} - PVRChannelGroup(PVR_CHANNEL_GROUP* channel) : CStructHdl(channel) {} + PVRChannelGroup(const PVR_CHANNEL_GROUP* group) : DynamicCStructHdl(group) {} + PVRChannelGroup(PVR_CHANNEL_GROUP* group) : DynamicCStructHdl(group) {} }; ///@} //------------------------------------------------------------------------------ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channel_groups.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channel_groups.h index f8213fbc5cd88..d02d2ea3d7ee0 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channel_groups.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channel_groups.h @@ -29,7 +29,7 @@ extern "C" */ typedef struct PVR_CHANNEL_GROUP { - char strGroupName[PVR_ADDON_NAME_STRING_LENGTH]; + const char* strGroupName; bool bIsRadio; unsigned int iPosition; } PVR_CHANNEL_GROUP; diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index bb9ba191f38d8..eadc6f195c2da 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -55,6 +55,25 @@ extern "C" } using namespace ADDON; +using namespace PVR; + +namespace +{ +class CAddonChannelGroup : public PVR_CHANNEL_GROUP +{ +public: + explicit CAddonChannelGroup(const CPVRChannelGroup& group) : m_groupName(group.ClientGroupName()) + { + bIsRadio = group.IsRadio(); + strGroupName = m_groupName.c_str(); + iPosition = group.GetClientPosition(); + } + virtual ~CAddonChannelGroup() = default; + +private: + const std::string m_groupName; +}; +} // unnamed namespace namespace PVR { @@ -750,9 +769,8 @@ PVR_ERROR CPVRClient::GetChannelGroupMembers( handle.callerAddress = this; handle.dataAddress = &groupMembers; - PVR_CHANNEL_GROUP tag; - group->FillAddonData(tag); - return addon->toAddon->GetChannelGroupMembers(addon, &handle, &tag); + const CAddonChannelGroup addonGroup{*group}; + return addon->toAddon->GetChannelGroupMembers(addon, &handle, &addonGroup); }, m_clientCapabilities.SupportsChannelGroups() && ((radio && m_clientCapabilities.SupportsRadio()) || diff --git a/xbmc/pvr/channels/PVRChannelGroup.cpp b/xbmc/pvr/channels/PVRChannelGroup.cpp index cc4bd649fae91..a7339ed1a915d 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.cpp +++ b/xbmc/pvr/channels/PVRChannelGroup.cpp @@ -63,14 +63,6 @@ bool CPVRChannelGroup::operator!=(const CPVRChannelGroup& right) const return !(*this == right); } -void CPVRChannelGroup::FillAddonData(PVR_CHANNEL_GROUP& group) const -{ - group = {}; - group.bIsRadio = IsRadio(); - strncpy(group.strGroupName, ClientGroupName().c_str(), sizeof(group.strGroupName) - 1); - group.iPosition = GetClientPosition(); -} - CCriticalSection CPVRChannelGroup::m_settingsSingletonCritSection; std::weak_ptr CPVRChannelGroup::m_settingsSingleton; diff --git a/xbmc/pvr/channels/PVRChannelGroup.h b/xbmc/pvr/channels/PVRChannelGroup.h index b4cc6c7a3a181..0ce1bed4faaa4 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.h +++ b/xbmc/pvr/channels/PVRChannelGroup.h @@ -20,8 +20,6 @@ #include #include -struct PVR_CHANNEL_GROUP; - namespace PVR { static constexpr int PVR_GROUP_TYPE_CLIENT = 0; @@ -71,12 +69,6 @@ class CPVRChannelGroup : public IChannelGroupSettingsCallback bool operator==(const CPVRChannelGroup& right) const; bool operator!=(const CPVRChannelGroup& right) const; - /*! - * @brief Copy over data to the given PVR_CHANNEL_GROUP instance. - * @param group The group instance to fill. - */ - void FillAddonData(PVR_CHANNEL_GROUP& group) const; - /*! * @brief Query the events available for CEventStream */ diff --git a/xbmc/pvr/channels/PVRChannelGroupFromClient.cpp b/xbmc/pvr/channels/PVRChannelGroupFromClient.cpp index 1a7eb13e3fafb..72589914a0648 100644 --- a/xbmc/pvr/channels/PVRChannelGroupFromClient.cpp +++ b/xbmc/pvr/channels/PVRChannelGroupFromClient.cpp @@ -26,10 +26,13 @@ CPVRChannelGroupFromClient::CPVRChannelGroupFromClient( const PVR_CHANNEL_GROUP& group, int clientID, const std::shared_ptr& allChannelsGroup) - : CPVRChannelGroup(CPVRChannelsPath(group.bIsRadio, group.strGroupName, clientID), - allChannelsGroup) + : CPVRChannelGroup( + CPVRChannelsPath(group.bIsRadio, group.strGroupName ? group.strGroupName : "", clientID), + allChannelsGroup) { - SetClientGroupName(group.strGroupName); + if (group.strGroupName) + SetClientGroupName(group.strGroupName); + SetClientPosition(group.iPosition); } diff --git a/xbmc/pvr/channels/PVRChannelGroupFromClient.h b/xbmc/pvr/channels/PVRChannelGroupFromClient.h index a25cdd2b38990..21b0eddc124a0 100644 --- a/xbmc/pvr/channels/PVRChannelGroupFromClient.h +++ b/xbmc/pvr/channels/PVRChannelGroupFromClient.h @@ -10,6 +10,8 @@ #include "pvr/channels/PVRChannelGroup.h" +struct PVR_CHANNEL_GROUP; + namespace PVR { From 36f17751bda894b9ff669101b1f07c39854fbe4c Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 17 Jul 2024 11:04:56 +0200 Subject: [PATCH 317/651] [addons][PVR] PVR add-on API: PVR_CHANNEL_GROUP_MEMBER: Replace fixed size char arrays with dynamic arrays. --- .../kodi/addon-instance/pvr/ChannelGroups.h | 19 ++++++++++++++----- .../addon-instance/pvr/pvr_channel_groups.h | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h index 43fee12974a61..44fbed3c79316 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h @@ -151,14 +151,15 @@ class PVRChannelGroupsResultSet /// @copydetails cpp_kodi_addon_pvr_Defs_ChannelGroup_PVRChannelGroupMember_Help /// ///@{ -class PVRChannelGroupMember : public CStructHdl +class PVRChannelGroupMember + : public DynamicCStructHdl { friend class CInstancePVRClient; public: /*! \cond PRIVATE */ PVRChannelGroupMember() { memset(m_cStructure, 0, sizeof(PVR_CHANNEL_GROUP_MEMBER)); } - PVRChannelGroupMember(const PVRChannelGroupMember& channel) : CStructHdl(channel) {} + PVRChannelGroupMember(const PVRChannelGroupMember& member) : DynamicCStructHdl(member) {} /*! \endcond */ /// @defgroup cpp_kodi_addon_pvr_Defs_ChannelGroup_PVRChannelGroupMember_Help Value Help @@ -181,7 +182,7 @@ class PVRChannelGroupMember : public CStructHdlstrGroupName, groupName.c_str(), sizeof(m_cStructure->strGroupName) - 1); + ReallocAndCopyString(&m_cStructure->strGroupName, groupName.c_str()); } /// @brief To get with @ref SetGroupName changed values. @@ -226,9 +227,17 @@ class PVRChannelGroupMember : public CStructHdlstrGroupName = AllocAndCopyString(source->strGroupName); + } + + static void FreeResources(PVR_CHANNEL_GROUP_MEMBER* target) { FreeString(target->strGroupName); } + private: - PVRChannelGroupMember(const PVR_CHANNEL_GROUP_MEMBER* channel) : CStructHdl(channel) {} - PVRChannelGroupMember(PVR_CHANNEL_GROUP_MEMBER* channel) : CStructHdl(channel) {} + PVRChannelGroupMember(const PVR_CHANNEL_GROUP_MEMBER* member) : DynamicCStructHdl(member) {} + PVRChannelGroupMember(PVR_CHANNEL_GROUP_MEMBER* member) : DynamicCStructHdl(member) {} }; ///@} //------------------------------------------------------------------------------ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channel_groups.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channel_groups.h index d02d2ea3d7ee0..ad2d0665349eb 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channel_groups.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channel_groups.h @@ -43,7 +43,7 @@ extern "C" */ typedef struct PVR_CHANNEL_GROUP_MEMBER { - char strGroupName[PVR_ADDON_NAME_STRING_LENGTH]; + const char* strGroupName; unsigned int iChannelUniqueId; unsigned int iChannelNumber; unsigned int iSubChannelNumber; From 2fbda1de37958637ba96ec654a90dc060aee7042 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 17 Jul 2024 11:38:09 +0200 Subject: [PATCH 318/651] [addons][PVR] PVR add-on API: PVR_CHANNEL: Replace fixed size char arrays with dynamic arrays. --- .../kodi/addon-instance/pvr/Channels.h | 29 +++++-- .../c-api/addon-instance/pvr/pvr_channels.h | 6 +- .../c-api/addon-instance/pvr/pvr_defines.h | 1 - xbmc/pvr/addons/PVRClient.cpp | 87 ++++++++++++------- xbmc/pvr/channels/PVRChannel.cpp | 24 +---- xbmc/pvr/channels/PVRChannel.h | 6 -- 6 files changed, 84 insertions(+), 69 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h index 7155373f84a88..dcc0650b6b57f 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h @@ -35,7 +35,7 @@ namespace addon /// @copydetails cpp_kodi_addon_pvr_Defs_Channel_PVRChannel_Help /// ///@{ -class PVRChannel : public CStructHdl +class PVRChannel : public DynamicCStructHdl { friend class CInstancePVRClient; @@ -46,7 +46,7 @@ class PVRChannel : public CStructHdl memset(m_cStructure, 0, sizeof(PVR_CHANNEL)); m_cStructure->iClientProviderUid = PVR_PROVIDER_INVALID_UID; } - PVRChannel(const PVRChannel& channel) : CStructHdl(channel) {} + PVRChannel(const PVRChannel& channel) : DynamicCStructHdl(channel) {} /*! \endcond */ /// @defgroup cpp_kodi_addon_pvr_Defs_Channel_PVRChannel_Help Value Help @@ -110,8 +110,7 @@ class PVRChannel : public CStructHdl /// Channel name given to this channel. void SetChannelName(const std::string& channelName) { - strncpy(m_cStructure->strChannelName, channelName.c_str(), - sizeof(m_cStructure->strChannelName) - 1); + ReallocAndCopyString(&m_cStructure->strChannelName, channelName.c_str()); } /// @brief To get with @ref SetChannelName changed values. @@ -125,7 +124,7 @@ class PVRChannel : public CStructHdl /// void SetMimeType(const std::string& inputFormat) { - strncpy(m_cStructure->strMimeType, inputFormat.c_str(), sizeof(m_cStructure->strMimeType) - 1); + ReallocAndCopyString(&m_cStructure->strMimeType, inputFormat.c_str()); } /// @brief To get with @ref SetMimeType changed values. @@ -150,7 +149,7 @@ class PVRChannel : public CStructHdl /// Path to the channel icon (if present). void SetIconPath(const std::string& iconPath) { - strncpy(m_cStructure->strIconPath, iconPath.c_str(), sizeof(m_cStructure->strIconPath) - 1); + ReallocAndCopyString(&m_cStructure->strIconPath, iconPath.c_str()); } /// @brief To get with @ref SetIconPath changed values. @@ -190,9 +189,23 @@ class PVRChannel : public CStructHdl /// @brief To get with @ref SetClientProviderUid changed values int GetClientProviderUid() const { return m_cStructure->iClientProviderUid; } + static void AllocResources(const PVR_CHANNEL* source, PVR_CHANNEL* target) + { + target->strChannelName = AllocAndCopyString(source->strChannelName); + target->strMimeType = AllocAndCopyString(source->strMimeType); + target->strIconPath = AllocAndCopyString(source->strIconPath); + } + + static void FreeResources(PVR_CHANNEL* target) + { + FreeString(target->strChannelName); + FreeString(target->strMimeType); + FreeString(target->strIconPath); + } + private: - PVRChannel(const PVR_CHANNEL* channel) : CStructHdl(channel) {} - PVRChannel(PVR_CHANNEL* channel) : CStructHdl(channel) {} + PVRChannel(const PVR_CHANNEL* channel) : DynamicCStructHdl(channel) {} + PVRChannel(PVR_CHANNEL* channel) : DynamicCStructHdl(channel) {} }; ///@} //------------------------------------------------------------------------------ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h index 2a91e02d8611c..b7b02c6ae159d 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h @@ -43,10 +43,10 @@ extern "C" bool bIsRadio; unsigned int iChannelNumber; unsigned int iSubChannelNumber; - char strChannelName[PVR_ADDON_NAME_STRING_LENGTH]; - char strMimeType[PVR_ADDON_INPUT_FORMAT_STRING_LENGTH]; + const char* strChannelName; + const char* strMimeType; unsigned int iEncryptionSystem; - char strIconPath[PVR_ADDON_URL_STRING_LENGTH]; + const char* strIconPath; bool bIsHidden; bool bHasArchive; int iOrder; diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h index 9c8238a2258b8..1fde8a01554f7 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h @@ -27,7 +27,6 @@ extern "C" #define PVR_ADDON_NAME_STRING_LENGTH 1024 #define PVR_ADDON_URL_STRING_LENGTH 1024 #define PVR_ADDON_DESC_STRING_LENGTH 1024 -#define PVR_ADDON_INPUT_FORMAT_STRING_LENGTH 32 #define PVR_ADDON_EDL_LENGTH 64 #define PVR_ADDON_TIMERTYPE_ARRAY_SIZE 32 #define PVR_ADDON_TIMERTYPE_VALUES_ARRAY_SIZE 512 diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index eadc6f195c2da..6693c01081499 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -73,6 +73,34 @@ class CAddonChannelGroup : public PVR_CHANNEL_GROUP private: const std::string m_groupName; }; + +class CAddonChannel : public PVR_CHANNEL +{ +public: + CAddonChannel(const CPVRChannel& channel, const std::string& newChannelName = "") + : m_channelName(newChannelName.empty() ? channel.ClientChannelName() : newChannelName), + m_mimeType(channel.MimeType()), + m_iconPath(channel.ClientIconPath()) + { + iUniqueId = channel.UniqueID(); + iChannelNumber = channel.ClientChannelNumber().GetChannelNumber(); + iSubChannelNumber = channel.ClientChannelNumber().GetSubChannelNumber(); + strChannelName = m_channelName.c_str(); + strIconPath = m_iconPath.c_str(); + iEncryptionSystem = channel.EncryptionSystem(); + bIsRadio = channel.IsRadio(); + bIsHidden = channel.IsHidden(); + strMimeType = m_mimeType.c_str(); + iClientProviderUid = channel.ClientProviderUid(); + bHasArchive = channel.HasArchive(); + } + virtual ~CAddonChannel() = default; + +private: + const std::string m_channelName; + const std::string m_mimeType; + const std::string m_iconPath; +}; } // unnamed namespace namespace PVR @@ -483,9 +511,9 @@ PVR_ERROR CPVRClient::OpenDialogChannelAdd(const std::shared_ptrFillAddonData(addonChannel); + [channel](const AddonInstance* addon) + { + const CAddonChannel addonChannel{*channel}; return addon->toAddon->OpenDialogChannelAdd(addon, &addonChannel); }, m_clientCapabilities.SupportsChannelSettings()); @@ -495,9 +523,9 @@ PVR_ERROR CPVRClient::OpenDialogChannelSettings(const std::shared_ptrFillAddonData(addonChannel); + [channel](const AddonInstance* addon) + { + const CAddonChannel addonChannel{*channel}; return addon->toAddon->OpenDialogChannelSettings(addon, &addonChannel); }, m_clientCapabilities.SupportsChannelSettings()); @@ -507,9 +535,9 @@ PVR_ERROR CPVRClient::DeleteChannel(const std::shared_ptr& ch { return DoAddonCall( __func__, - [channel](const AddonInstance* addon) { - PVR_CHANNEL addonChannel; - channel->FillAddonData(addonChannel); + [channel](const AddonInstance* addon) + { + const CAddonChannel addonChannel{*channel}; return addon->toAddon->DeleteChannel(addon, &addonChannel); }, m_clientCapabilities.SupportsChannelSettings()); @@ -519,11 +547,9 @@ PVR_ERROR CPVRClient::RenameChannel(const std::shared_ptr& ch { return DoAddonCall( __func__, - [channel](const AddonInstance* addon) { - PVR_CHANNEL addonChannel; - channel->FillAddonData(addonChannel); - strncpy(addonChannel.strChannelName, channel->ChannelName().c_str(), - sizeof(addonChannel.strChannelName) - 1); + [channel](const AddonInstance* addon) + { + const CAddonChannel addonChannel{*channel, channel->ChannelName()}; return addon->toAddon->RenameChannel(addon, &addonChannel); }, m_clientCapabilities.SupportsChannelSettings()); @@ -1254,15 +1280,14 @@ PVR_ERROR CPVRClient::GetChannelStreamProperties(const std::shared_ptrFillAddonData(tag); + const CAddonChannel addonChannel{*channel}; unsigned int iPropertyCount = STREAM_MAX_PROPERTY_COUNT; std::unique_ptr properties(new PVR_NAMED_VALUE[iPropertyCount]); memset(properties.get(), 0, iPropertyCount * sizeof(PVR_NAMED_VALUE)); - PVR_ERROR error = - addon->toAddon->GetChannelStreamProperties(addon, &tag, properties.get(), &iPropertyCount); + const PVR_ERROR error{addon->toAddon->GetChannelStreamProperties( + addon, &addonChannel, properties.get(), &iPropertyCount)}; if (error == PVR_ERROR_NO_ERROR) WriteStreamProperties(properties.get(), iPropertyCount, props); @@ -1448,10 +1473,9 @@ PVR_ERROR CPVRClient::OpenLiveStream(const std::shared_ptr& c else { CLog::LogFC(LOGDEBUG, LOGPVR, "Opening live stream for channel '{}'", channel->ChannelName()); - PVR_CHANNEL tag; - channel->FillAddonData(tag); - return addon->toAddon->OpenLiveStream(addon, &tag) ? PVR_ERROR_NO_ERROR - : PVR_ERROR_NOT_IMPLEMENTED; + const CAddonChannel addonChannel{*channel}; + return addon->toAddon->OpenLiveStream(addon, &addonChannel) ? PVR_ERROR_NO_ERROR + : PVR_ERROR_NOT_IMPLEMENTED; } }); } @@ -1601,17 +1625,18 @@ PVR_ERROR CPVRClient::CallEpgTagMenuHook(const CPVRClientMenuHook& hook, PVR_ERROR CPVRClient::CallChannelMenuHook(const CPVRClientMenuHook& hook, const std::shared_ptr& channel) { - return DoAddonCall(__func__, [&hook, &channel](const AddonInstance* addon) { - PVR_CHANNEL tag; - channel->FillAddonData(tag); + return DoAddonCall(__func__, + [&hook, &channel](const AddonInstance* addon) + { + const CAddonChannel addonChannel{*channel}; - PVR_MENUHOOK menuHook; - menuHook.category = PVR_MENUHOOK_CHANNEL; - menuHook.iHookId = hook.GetId(); - menuHook.iLocalizedStringId = hook.GetLabelId(); + PVR_MENUHOOK menuHook; + menuHook.category = PVR_MENUHOOK_CHANNEL; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); - return addon->toAddon->CallChannelMenuHook(addon, &menuHook, &tag); - }); + return addon->toAddon->CallChannelMenuHook(addon, &menuHook, &addonChannel); + }); } PVR_ERROR CPVRClient::CallRecordingMenuHook(const CPVRClientMenuHook& hook, diff --git a/xbmc/pvr/channels/PVRChannel.cpp b/xbmc/pvr/channels/PVRChannel.cpp index f7fd106a01bb2..8abd2d890e75a 100644 --- a/xbmc/pvr/channels/PVRChannel.cpp +++ b/xbmc/pvr/channels/PVRChannel.cpp @@ -59,16 +59,16 @@ CPVRChannel::CPVRChannel(bool bRadio, const std::string& iconPath) CPVRChannel::CPVRChannel(const PVR_CHANNEL& channel, unsigned int iClientId) : m_bIsRadio(channel.bIsRadio), m_bIsHidden(channel.bIsHidden), - m_iconPath(channel.strIconPath, + m_iconPath(channel.strIconPath ? channel.strIconPath : "", StringUtils::Format(IMAGE_OWNER_PATTERN, channel.bIsRadio ? "radio" : "tv")), - m_strChannelName(channel.strChannelName), + m_strChannelName(channel.strChannelName ? channel.strChannelName : ""), m_bHasArchive(channel.bHasArchive), m_bEPGEnabled(!channel.bIsHidden), m_iUniqueId(channel.iUniqueId), m_iClientId(iClientId), m_clientChannelNumber(channel.iChannelNumber, channel.iSubChannelNumber), - m_strClientChannelName(channel.strChannelName), - m_strMimeType(channel.strMimeType), + m_strClientChannelName(channel.strChannelName ? channel.strChannelName : ""), + m_strMimeType(channel.strMimeType ? channel.strMimeType : ""), m_iClientEncryptionSystem(channel.iEncryptionSystem), m_iClientOrder(channel.iOrder), m_iClientProviderUid(channel.iClientProviderUid) @@ -84,22 +84,6 @@ CPVRChannel::~CPVRChannel() ResetEPG(); } -void CPVRChannel::FillAddonData(PVR_CHANNEL& channel) const -{ - channel = {}; - channel.iUniqueId = UniqueID(); - channel.iChannelNumber = ClientChannelNumber().GetChannelNumber(); - channel.iSubChannelNumber = ClientChannelNumber().GetSubChannelNumber(); - strncpy(channel.strChannelName, ClientChannelName().c_str(), sizeof(channel.strChannelName) - 1); - strncpy(channel.strIconPath, ClientIconPath().c_str(), sizeof(channel.strIconPath) - 1); - channel.iEncryptionSystem = EncryptionSystem(); - channel.bIsRadio = IsRadio(); - channel.bIsHidden = IsHidden(); - strncpy(channel.strMimeType, MimeType().c_str(), sizeof(channel.strMimeType) - 1); - channel.iClientProviderUid = ClientProviderUid(); - channel.bHasArchive = HasArchive(); -} - void CPVRChannel::Serialize(CVariant& value) const { value["channelid"] = m_iChannelId; diff --git a/xbmc/pvr/channels/PVRChannel.h b/xbmc/pvr/channels/PVRChannel.h index 91cfb9ac36ea0..defe9ec9a8d74 100644 --- a/xbmc/pvr/channels/PVRChannel.h +++ b/xbmc/pvr/channels/PVRChannel.h @@ -49,12 +49,6 @@ class CPVRChannel : public ISerializable, public ISortable bool operator==(const CPVRChannel& right) const; bool operator!=(const CPVRChannel& right) const; - /*! - * @brief Copy over data to the given PVR_CHANNEL instance. - * @param channel The channel instance to fill. - */ - void FillAddonData(PVR_CHANNEL& channel) const; - void Serialize(CVariant& value) const override; /*! @name Kodi related channel methods From fd2aa653ce09e434bdd0e01e6a71a320c111c5a7 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 17 Jul 2024 23:07:46 +0200 Subject: [PATCH 319/651] [addons][PVR] PVR add-on API: PVR_PROVIDER: Replace fixed size char arrays with dynamic arrays. --- .../kodi/addon-instance/pvr/Providers.h | 38 ++++++++++++++----- .../c-api/addon-instance/pvr/pvr_defines.h | 2 - .../c-api/addon-instance/pvr/pvr_providers.h | 8 ++-- xbmc/pvr/providers/PVRProvider.cpp | 8 ++-- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Providers.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Providers.h index 8ccd7a7e39c2e..a76f44ca9f424 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Providers.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Providers.h @@ -36,14 +36,14 @@ namespace addon /// @copydetails cpp_kodi_addon_pvr_Defs_PVRProvider_Help /// ///@{ -class PVRProvider : public CStructHdl +class PVRProvider : public DynamicCStructHdl { friend class CInstancePVRClient; public: /*! \cond PRIVATE */ PVRProvider() { memset(m_cStructure, 0, sizeof(PVR_PROVIDER)); } - PVRProvider(const PVRProvider& provider) : CStructHdl(provider) {} + PVRProvider(const PVRProvider& provider) : DynamicCStructHdl(provider) {} /*! \endcond */ /// @defgroup cpp_kodi_addon_pvr_Defs_PVRProvider_Help Value Help @@ -74,7 +74,7 @@ class PVRProvider : public CStructHdl /// Name given to this provider. void SetName(const std::string& name) { - strncpy(m_cStructure->strName, name.c_str(), sizeof(m_cStructure->strName) - 1); + ReallocAndCopyString(&m_cStructure->strName, name.c_str()); } /// @brief To get with @ref SetName changed values. @@ -103,7 +103,7 @@ class PVRProvider : public CStructHdl /// Path to the provider icon (if present). void SetIconPath(const std::string& iconPath) { - strncpy(m_cStructure->strIconPath, iconPath.c_str(), sizeof(m_cStructure->strIconPath) - 1); + ReallocAndCopyString(&m_cStructure->strIconPath, iconPath.c_str()); } /// @brief To get with @ref SetIconPath changed values. @@ -116,8 +116,9 @@ class PVRProvider : public CStructHdl /// @note ISO 3166 country codes required (e.g 'GB,IE,CA'). void SetCountries(const std::vector& countries) { - const std::string str = tools::StringUtils::Join(countries, PROVIDER_STRING_TOKEN_SEPARATOR); - strncpy(m_cStructure->strCountries, str.c_str(), sizeof(m_cStructure->strCountries) - 1); + ReallocAndCopyString( + &m_cStructure->strCountries, + tools::StringUtils::Join(countries, PROVIDER_STRING_TOKEN_SEPARATOR).c_str()); } /// @brief To get with @ref SetCountries changed values. @@ -133,8 +134,9 @@ class PVRProvider : public CStructHdl /// @note RFC 5646 standard codes required (e.g.: 'en_GB,fr_CA'). void SetLanguages(const std::vector& languages) { - const std::string str = tools::StringUtils::Join(languages, PROVIDER_STRING_TOKEN_SEPARATOR); - strncpy(m_cStructure->strLanguages, str.c_str(), sizeof(m_cStructure->strLanguages) - 1); + ReallocAndCopyString( + &m_cStructure->strLanguages, + tools::StringUtils::Join(languages, PROVIDER_STRING_TOKEN_SEPARATOR).c_str()); } /// @brief To get with @ref SetLanguages changed values. @@ -144,9 +146,25 @@ class PVRProvider : public CStructHdl } ///@} + static void AllocResources(const PVR_PROVIDER* source, PVR_PROVIDER* target) + { + target->strName = AllocAndCopyString(source->strName); + target->strIconPath = AllocAndCopyString(source->strIconPath); + target->strCountries = AllocAndCopyString(source->strCountries); + target->strLanguages = AllocAndCopyString(source->strLanguages); + } + + static void FreeResources(PVR_PROVIDER* target) + { + FreeString(target->strName); + FreeString(target->strIconPath); + FreeString(target->strCountries); + FreeString(target->strLanguages); + } + private: - PVRProvider(const PVR_PROVIDER* provider) : CStructHdl(provider) {} - PVRProvider(PVR_PROVIDER* provider) : CStructHdl(provider) {} + PVRProvider(const PVR_PROVIDER* provider) : DynamicCStructHdl(provider) {} + PVRProvider(PVR_PROVIDER* provider) : DynamicCStructHdl(provider) {} }; ///@} //------------------------------------------------------------------------------ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h index 1fde8a01554f7..78d8303c187a9 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h @@ -36,8 +36,6 @@ extern "C" #define PVR_ADDON_ATTRIBUTE_VALUES_ARRAY_SIZE 512 #define PVR_ADDON_DESCRAMBLE_INFO_STRING_LENGTH 64 #define PVR_ADDON_DATE_STRING_LENGTH 32 -#define PVR_ADDON_COUNTRIES_STRING_LENGTH 128 -#define PVR_ADDON_LANGUAGES_STRING_LENGTH 128 ///@} /*! diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h index a74f70e15b130..a9c04f38a1e9d 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h @@ -79,15 +79,15 @@ extern "C" typedef struct PVR_PROVIDER { unsigned int iUniqueId; - char strName[PVR_ADDON_NAME_STRING_LENGTH]; + const char* strName; enum PVR_PROVIDER_TYPE type; - char strIconPath[PVR_ADDON_URL_STRING_LENGTH]; + const char* strIconPath; //! @brief ISO 3166 country codes, separated by PROVIDER_STRING_TOKEN_SEPARATOR /// (e.g 'GB,IE,FR,CA'), an empty string means this value is undefined - char strCountries[PVR_ADDON_COUNTRIES_STRING_LENGTH]; + const char* strCountries; //! @brief RFC 5646 language codes, separated by PROVIDER_STRING_TOKEN_SEPARATOR /// (e.g. 'en_GB,fr_CA'), an empty string means this value is undefined - char strLanguages[PVR_ADDON_LANGUAGES_STRING_LENGTH]; + const char* strLanguages; } PVR_PROVIDER; #ifdef __cplusplus diff --git a/xbmc/pvr/providers/PVRProvider.cpp b/xbmc/pvr/providers/PVRProvider.cpp index ab2e7a34cf0c9..99671aa889db7 100644 --- a/xbmc/pvr/providers/PVRProvider.cpp +++ b/xbmc/pvr/providers/PVRProvider.cpp @@ -38,11 +38,11 @@ CPVRProvider::CPVRProvider(int iUniqueId, int iClientId) CPVRProvider::CPVRProvider(const PVR_PROVIDER& provider, int iClientId) : m_iUniqueId(provider.iUniqueId), m_iClientId(iClientId), - m_strName(provider.strName), + m_strName(provider.strName ? provider.strName : ""), m_type(provider.type), - m_iconPath(provider.strIconPath, IMAGE_OWNER_PATTERN), - m_strCountries(provider.strCountries), - m_strLanguages(provider.strLanguages), + m_iconPath(provider.strIconPath ? provider.strIconPath : "", IMAGE_OWNER_PATTERN), + m_strCountries(provider.strCountries ? provider.strCountries : ""), + m_strLanguages(provider.strLanguages ? provider.strLanguages : ""), m_thumbPath(IMAGE_OWNER_PATTERN) { } From 384c2526e98c6f9379a0686bbbdfd007b0119878 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Tue, 30 Jul 2024 18:02:33 +0200 Subject: [PATCH 320/651] Revert AirTunes service name change --- xbmc/network/AirTunesServer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xbmc/network/AirTunesServer.cpp b/xbmc/network/AirTunesServer.cpp index 79b08a2e77f76..0add358ba30b6 100644 --- a/xbmc/network/AirTunesServer.cpp +++ b/xbmc/network/AirTunesServer.cpp @@ -627,8 +627,7 @@ bool CAirTunesServer::StartServer(int port, bool nonlocal, bool usePassword, con txt.emplace_back("am", "Kodi,1"); txt.emplace_back("vs", "130.14"); - CZeroconf::GetInstance()->PublishService("servers.airtunes", "_raop._tcp", - CSysInfo::GetDeviceName() + " airtunes", port, txt); + CZeroconf::GetInstance()->PublishService("servers.airtunes", "_raop._tcp", appName, port, txt); } return success; From b95e5cbfdf61f835885076e3f57e2b247b3d7054 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 18 Jul 2024 00:08:02 +0200 Subject: [PATCH 321/651] [addons][PVR] PVR add-on API: PVR_RECORDING: Replace fixed size char arrays with dynamic arrays. --- .../kodi/addon-instance/pvr/Recordings.h | 77 ++++++--- .../c-api/addon-instance/pvr/pvr_defines.h | 1 - .../c-api/addon-instance/pvr/pvr_recordings.h | 26 +-- xbmc/pvr/addons/PVRClient.cpp | 155 +++++++++++++----- xbmc/pvr/recordings/PVRRecording.cpp | 82 +++------ xbmc/pvr/recordings/PVRRecording.h | 6 - 6 files changed, 202 insertions(+), 145 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h index b7f498aa84134..9e27c513f71a0 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h @@ -35,7 +35,7 @@ namespace addon /// @copydetails cpp_kodi_addon_pvr_Defs_Recording_PVRRecording_Help /// ///@{ -class PVRRecording : public CStructHdl +class PVRRecording : public DynamicCStructHdl { friend class CInstancePVRClient; @@ -60,7 +60,7 @@ class PVRRecording : public CStructHdl m_cStructure->iFlags = 0; m_cStructure->sizeInBytes = PVR_RECORDING_VALUE_NOT_AVAILABLE; } - PVRRecording(const PVRRecording& recording) : CStructHdl(recording) {} + PVRRecording(const PVRRecording& recording) : DynamicCStructHdl(recording) {} /*! \endcond */ /// @defgroup cpp_kodi_addon_pvr_Defs_Recording_PVRRecording_Help Value Help @@ -108,8 +108,7 @@ class PVRRecording : public CStructHdl /// Unique identifier of the recording on the client. void SetRecordingId(const std::string& recordingId) { - strncpy(m_cStructure->strRecordingId, recordingId.c_str(), - sizeof(m_cStructure->strRecordingId) - 1); + ReallocAndCopyString(&m_cStructure->strRecordingId, recordingId.c_str()); } /// @brief To get with @ref SetRecordingId changed values. @@ -119,7 +118,7 @@ class PVRRecording : public CStructHdl /// The title of this recording. void SetTitle(const std::string& title) { - strncpy(m_cStructure->strTitle, title.c_str(), sizeof(m_cStructure->strTitle) - 1); + ReallocAndCopyString(&m_cStructure->strTitle, title.c_str()); } /// @brief To get with @ref SetTitle changed values. @@ -129,8 +128,7 @@ class PVRRecording : public CStructHdl /// Episode name (also known as subtitle). void SetEpisodeName(const std::string& episodeName) { - strncpy(m_cStructure->strEpisodeName, episodeName.c_str(), - sizeof(m_cStructure->strEpisodeName) - 1); + ReallocAndCopyString(&m_cStructure->strEpisodeName, episodeName.c_str()); } /// @brief To get with @ref SetEpisodeName changed values. @@ -169,7 +167,7 @@ class PVRRecording : public CStructHdl /// Directory of this recording on the client. void SetDirectory(const std::string& directory) { - strncpy(m_cStructure->strDirectory, directory.c_str(), sizeof(m_cStructure->strDirectory) - 1); + ReallocAndCopyString(&m_cStructure->strDirectory, directory.c_str()); } /// @brief To get with @ref SetDirectory changed values. @@ -179,8 +177,7 @@ class PVRRecording : public CStructHdl /// Plot outline name. void SetPlotOutline(const std::string& plotOutline) { - strncpy(m_cStructure->strPlotOutline, plotOutline.c_str(), - sizeof(m_cStructure->strPlotOutline) - 1); + ReallocAndCopyString(&m_cStructure->strPlotOutline, plotOutline.c_str()); } /// @brief To get with @ref SetPlotOutline changed values. @@ -190,7 +187,7 @@ class PVRRecording : public CStructHdl /// Plot name. void SetPlot(const std::string& plot) { - strncpy(m_cStructure->strPlot, plot.c_str(), sizeof(m_cStructure->strPlot) - 1); + ReallocAndCopyString(&m_cStructure->strPlot, plot.c_str()); } /// @brief To get with @ref SetPlot changed values. @@ -200,8 +197,7 @@ class PVRRecording : public CStructHdl /// Channel name. void SetChannelName(const std::string& channelName) { - strncpy(m_cStructure->strChannelName, channelName.c_str(), - sizeof(m_cStructure->strChannelName) - 1); + ReallocAndCopyString(&m_cStructure->strChannelName, channelName.c_str()); } /// @brief To get with @ref SetChannelName changed values. @@ -211,7 +207,7 @@ class PVRRecording : public CStructHdl /// Channel logo (icon) path. void SetIconPath(const std::string& iconPath) { - strncpy(m_cStructure->strIconPath, iconPath.c_str(), sizeof(m_cStructure->strIconPath) - 1); + ReallocAndCopyString(&m_cStructure->strIconPath, iconPath.c_str()); } /// @brief To get with @ref SetIconPath changed values. @@ -221,8 +217,7 @@ class PVRRecording : public CStructHdl /// Thumbnail path. void SetThumbnailPath(const std::string& thumbnailPath) { - strncpy(m_cStructure->strThumbnailPath, thumbnailPath.c_str(), - sizeof(m_cStructure->strThumbnailPath) - 1); + ReallocAndCopyString(&m_cStructure->strThumbnailPath, thumbnailPath.c_str()); } /// @brief To get with @ref SetThumbnailPath changed values. @@ -232,8 +227,7 @@ class PVRRecording : public CStructHdl /// Fanart path. void SetFanartPath(const std::string& fanartPath) { - strncpy(m_cStructure->strFanartPath, fanartPath.c_str(), - sizeof(m_cStructure->strFanartPath) - 1); + ReallocAndCopyString(&m_cStructure->strFanartPath, fanartPath.c_str()); } /// @brief To get with @ref SetFanartPath changed values. @@ -356,8 +350,7 @@ class PVRRecording : public CStructHdl /// void SetGenreDescription(const std::string& genreDescription) { - strncpy(m_cStructure->strGenreDescription, genreDescription.c_str(), - sizeof(m_cStructure->strGenreDescription) - 1); + ReallocAndCopyString(&m_cStructure->strGenreDescription, genreDescription.c_str()); } /// @brief To get with @ref SetGenreDescription changed values. @@ -439,8 +432,7 @@ class PVRRecording : public CStructHdl /// void SetFirstAired(const std::string& firstAired) { - strncpy(m_cStructure->strFirstAired, firstAired.c_str(), - sizeof(m_cStructure->strFirstAired) - 1); + ReallocAndCopyString(&m_cStructure->strFirstAired, firstAired.c_str()); } /// @brief To get with @ref SetFirstAired changed values @@ -485,16 +477,49 @@ class PVRRecording : public CStructHdl /// Name for the provider of this channel. void SetProviderName(const std::string& providerName) { - strncpy(m_cStructure->strProviderName, providerName.c_str(), - sizeof(m_cStructure->strProviderName) - 1); + ReallocAndCopyString(&m_cStructure->strProviderName, providerName.c_str()); } /// @brief To get with @ref SetProviderName changed values. std::string GetProviderName() const { return m_cStructure->strProviderName; } + static void AllocResources(const PVR_RECORDING* source, PVR_RECORDING* target) + { + target->strRecordingId = AllocAndCopyString(source->strRecordingId); + target->strTitle = AllocAndCopyString(source->strTitle); + target->strEpisodeName = AllocAndCopyString(source->strEpisodeName); + target->strDirectory = AllocAndCopyString(source->strDirectory); + target->strPlotOutline = AllocAndCopyString(source->strPlotOutline); + target->strPlot = AllocAndCopyString(source->strPlot); + target->strGenreDescription = AllocAndCopyString(source->strGenreDescription); + target->strChannelName = AllocAndCopyString(source->strChannelName); + target->strIconPath = AllocAndCopyString(source->strIconPath); + target->strThumbnailPath = AllocAndCopyString(source->strThumbnailPath); + target->strFanartPath = AllocAndCopyString(source->strFanartPath); + target->strFirstAired = AllocAndCopyString(source->strFirstAired); + target->strProviderName = AllocAndCopyString(source->strProviderName); + } + + static void FreeResources(PVR_RECORDING* target) + { + FreeString(target->strRecordingId); + FreeString(target->strTitle); + FreeString(target->strEpisodeName); + FreeString(target->strDirectory); + FreeString(target->strPlotOutline); + FreeString(target->strPlot); + FreeString(target->strGenreDescription); + FreeString(target->strChannelName); + FreeString(target->strIconPath); + FreeString(target->strThumbnailPath); + FreeString(target->strFanartPath); + FreeString(target->strFirstAired); + FreeString(target->strProviderName); + } + private: - PVRRecording(const PVR_RECORDING* recording) : CStructHdl(recording) {} - PVRRecording(PVR_RECORDING* recording) : CStructHdl(recording) {} + PVRRecording(const PVR_RECORDING* recording) : DynamicCStructHdl(recording) {} + PVRRecording(PVR_RECORDING* recording) : DynamicCStructHdl(recording) {} }; ///@} //------------------------------------------------------------------------------ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h index 78d8303c187a9..91d3951646e16 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h @@ -35,7 +35,6 @@ extern "C" #define PVR_ADDON_ATTRIBUTE_DESC_LENGTH 128 #define PVR_ADDON_ATTRIBUTE_VALUES_ARRAY_SIZE 512 #define PVR_ADDON_DESCRAMBLE_INFO_STRING_LENGTH 64 -#define PVR_ADDON_DATE_STRING_LENGTH 32 ///@} /*! diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h index 8b9a207525ee6..8c57f33e2ab73 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h @@ -108,20 +108,20 @@ extern "C" */ typedef struct PVR_RECORDING { - char strRecordingId[PVR_ADDON_NAME_STRING_LENGTH]; - char strTitle[PVR_ADDON_NAME_STRING_LENGTH]; - char strEpisodeName[PVR_ADDON_NAME_STRING_LENGTH]; + const char* strRecordingId; + const char* strTitle; + const char* strEpisodeName; int iSeriesNumber; int iEpisodeNumber; int iYear; - char strDirectory[PVR_ADDON_URL_STRING_LENGTH]; - char strPlotOutline[PVR_ADDON_DESC_STRING_LENGTH]; - char strPlot[PVR_ADDON_DESC_STRING_LENGTH]; - char strGenreDescription[PVR_ADDON_DESC_STRING_LENGTH]; - char strChannelName[PVR_ADDON_NAME_STRING_LENGTH]; - char strIconPath[PVR_ADDON_URL_STRING_LENGTH]; - char strThumbnailPath[PVR_ADDON_URL_STRING_LENGTH]; - char strFanartPath[PVR_ADDON_URL_STRING_LENGTH]; + const char* strDirectory; + const char* strPlotOutline; + const char* strPlot; + const char* strGenreDescription; + const char* strChannelName; + const char* strIconPath; + const char* strThumbnailPath; + const char* strFanartPath; time_t recordingTime; int iDuration; int iPriority; @@ -134,11 +134,11 @@ extern "C" unsigned int iEpgEventId; int iChannelUid; enum PVR_RECORDING_CHANNEL_TYPE channelType; - char strFirstAired[PVR_ADDON_DATE_STRING_LENGTH]; + const char* strFirstAired; unsigned int iFlags; int64_t sizeInBytes; int iClientProviderUid; - char strProviderName[PVR_ADDON_NAME_STRING_LENGTH]; + const char* strProviderName; } PVR_RECORDING; #ifdef __cplusplus diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 6693c01081499..9911e719402fd 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -101,6 +101,81 @@ class CAddonChannel : public PVR_CHANNEL const std::string m_mimeType; const std::string m_iconPath; }; + +class CAddonRecording : public PVR_RECORDING +{ +public: + explicit CAddonRecording(const CPVRRecording& recording) + : m_recordingId(recording.ClientRecordingID()), + m_title(recording.m_strTitle), + m_episodeName(recording.m_strShowTitle), + m_directory(recording.Directory()), + m_plotOutline(recording.m_strPlotOutline), + m_plot(recording.m_strPlot), + m_genreDescription(recording.GetGenresLabel()), + m_channelName(recording.ChannelName()), + m_iconPath(recording.ClientIconPath()), + m_thumbnailPath(recording.ClientThumbnailPath()), + m_fanartPath(recording.ClientFanartPath()), + m_firstAired(recording.FirstAired().GetAsW3CDate()), + m_providerName(recording.ProviderName()) + { + time_t recTime; + recording.RecordingTimeAsUTC().GetAsTime(recTime); + + strRecordingId = m_recordingId.c_str(); + strTitle = m_title.c_str(); + strEpisodeName = m_episodeName.c_str(); + iSeriesNumber = recording.m_iSeason; + iEpisodeNumber = recording.m_iEpisode; + iYear = recording.GetYear(); + strDirectory = m_directory.c_str(); + strPlotOutline = m_plotOutline.c_str(); + strPlot = m_plot.c_str(); + strGenreDescription = m_genreDescription.c_str(); + strChannelName = m_channelName.c_str(); + strIconPath = m_iconPath.c_str(); + strThumbnailPath = m_thumbnailPath.c_str(); + strFanartPath = m_fanartPath.c_str(); + recordingTime = + recTime - + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection; + iDuration = recording.GetDuration(); + iPriority = recording.Priority(); + iLifetime = recording.LifeTime(); + iGenreType = recording.GenreType(); + iGenreSubType = recording.GenreSubType(); + iPlayCount = recording.GetLocalPlayCount(); + iLastPlayedPosition = std::lrint(recording.GetLocalResumePoint().timeInSeconds); + bIsDeleted = recording.IsDeleted(); + iEpgEventId = recording.BroadcastUid(); + iChannelUid = recording.ChannelUid(); + channelType = + recording.IsRadio() ? PVR_RECORDING_CHANNEL_TYPE_RADIO : PVR_RECORDING_CHANNEL_TYPE_TV; + if (recording.FirstAired().IsValid()) + strFirstAired = m_firstAired.c_str(); + iFlags = recording.Flags(); + sizeInBytes = recording.GetSizeInBytes(); + strProviderName = m_providerName.c_str(); + iClientProviderUid = recording.ClientProviderUniqueId(); + } + virtual ~CAddonRecording() = default; + +private: + const std::string m_recordingId; + const std::string m_title; + const std::string m_episodeName; + const std::string m_directory; + const std::string m_plotOutline; + const std::string m_plot; + const std::string m_genreDescription; + const std::string m_channelName; + const std::string m_iconPath; + const std::string m_thumbnailPath; + const std::string m_fanartPath; + const std::string m_firstAired; + const std::string m_providerName; +}; } // unnamed namespace namespace PVR @@ -874,9 +949,9 @@ PVR_ERROR CPVRClient::DeleteRecording(const CPVRRecording& recording) { return DoAddonCall( __func__, - [&recording](const AddonInstance* addon) { - PVR_RECORDING tag; - recording.FillAddonData(tag); + [&recording](const AddonInstance* addon) + { + const CAddonRecording tag{recording}; return addon->toAddon->DeleteRecording(addon, &tag); }, m_clientCapabilities.SupportsRecordings() && m_clientCapabilities.SupportsRecordingsDelete()); @@ -886,9 +961,9 @@ PVR_ERROR CPVRClient::UndeleteRecording(const CPVRRecording& recording) { return DoAddonCall( __func__, - [&recording](const AddonInstance* addon) { - PVR_RECORDING tag; - recording.FillAddonData(tag); + [&recording](const AddonInstance* addon) + { + const CAddonRecording tag{recording}; return addon->toAddon->UndeleteRecording(addon, &tag); }, m_clientCapabilities.SupportsRecordingsUndelete()); @@ -908,9 +983,9 @@ PVR_ERROR CPVRClient::RenameRecording(const CPVRRecording& recording) { return DoAddonCall( __func__, - [&recording](const AddonInstance* addon) { - PVR_RECORDING tag; - recording.FillAddonData(tag); + [&recording](const AddonInstance* addon) + { + const CAddonRecording tag{recording}; return addon->toAddon->RenameRecording(addon, &tag); }, m_clientCapabilities.SupportsRecordings()); @@ -920,9 +995,9 @@ PVR_ERROR CPVRClient::SetRecordingLifetime(const CPVRRecording& recording) { return DoAddonCall( __func__, - [&recording](const AddonInstance* addon) { - PVR_RECORDING tag; - recording.FillAddonData(tag); + [&recording](const AddonInstance* addon) + { + const CAddonRecording tag{recording}; return addon->toAddon->SetRecordingLifetime(addon, &tag); }, m_clientCapabilities.SupportsRecordingsLifetimeChange()); @@ -932,9 +1007,9 @@ PVR_ERROR CPVRClient::SetRecordingPlayCount(const CPVRRecording& recording, int { return DoAddonCall( __func__, - [&recording, count](const AddonInstance* addon) { - PVR_RECORDING tag; - recording.FillAddonData(tag); + [&recording, count](const AddonInstance* addon) + { + const CAddonRecording tag{recording}; return addon->toAddon->SetRecordingPlayCount(addon, &tag, count); }, m_clientCapabilities.SupportsRecordingsPlayCount()); @@ -945,9 +1020,9 @@ PVR_ERROR CPVRClient::SetRecordingLastPlayedPosition(const CPVRRecording& record { return DoAddonCall( __func__, - [&recording, lastplayedposition](const AddonInstance* addon) { - PVR_RECORDING tag; - recording.FillAddonData(tag); + [&recording, lastplayedposition](const AddonInstance* addon) + { + const CAddonRecording tag{recording}; return addon->toAddon->SetRecordingLastPlayedPosition(addon, &tag, lastplayedposition); }, m_clientCapabilities.SupportsRecordingsLastPlayedPosition()); @@ -959,9 +1034,9 @@ PVR_ERROR CPVRClient::GetRecordingLastPlayedPosition(const CPVRRecording& record iPosition = -1; return DoAddonCall( __func__, - [&recording, &iPosition](const AddonInstance* addon) { - PVR_RECORDING tag; - recording.FillAddonData(tag); + [&recording, &iPosition](const AddonInstance* addon) + { + const CAddonRecording tag{recording}; return addon->toAddon->GetRecordingLastPlayedPosition(addon, &tag, &iPosition); }, m_clientCapabilities.SupportsRecordingsLastPlayedPosition()); @@ -973,9 +1048,9 @@ PVR_ERROR CPVRClient::GetRecordingEdl(const CPVRRecording& recording, edls.clear(); return DoAddonCall( __func__, - [&recording, &edls](const AddonInstance* addon) { - PVR_RECORDING tag; - recording.FillAddonData(tag); + [&recording, &edls](const AddonInstance* addon) + { + const CAddonRecording tag{recording}; PVR_EDL_ENTRY edl_array[PVR_ADDON_EDL_LENGTH]; int size = PVR_ADDON_EDL_LENGTH; @@ -995,9 +1070,9 @@ PVR_ERROR CPVRClient::GetRecordingSize(const CPVRRecording& recording, int64_t& { return DoAddonCall( __func__, - [&recording, &sizeInBytes](const AddonInstance* addon) { - PVR_RECORDING tag; - recording.FillAddonData(tag); + [&recording, &sizeInBytes](const AddonInstance* addon) + { + const CAddonRecording tag{recording}; return addon->toAddon->GetRecordingSize(addon, &tag, &sizeInBytes); }, m_clientCapabilities.SupportsRecordingsSize()); @@ -1302,8 +1377,7 @@ PVR_ERROR CPVRClient::GetRecordingStreamProperties( if (!m_clientCapabilities.SupportsRecordings()) return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon - PVR_RECORDING tag = {}; - recording->FillAddonData(tag); + const CAddonRecording tag(*recording); unsigned int iPropertyCount = STREAM_MAX_PROPERTY_COUNT; std::unique_ptr properties(new PVR_NAMED_VALUE[iPropertyCount]); @@ -1490,8 +1564,7 @@ PVR_ERROR CPVRClient::OpenRecordedStream(const std::shared_ptrFillAddonData(tag); + const CAddonRecording tag(*recording); CLog::LogFC(LOGDEBUG, LOGPVR, "Opening stream for recording '{}'", recording->m_strTitle); return addon->toAddon->OpenRecordedStream(addon, &tag) ? PVR_ERROR_NO_ERROR : PVR_ERROR_NOT_IMPLEMENTED; @@ -1643,17 +1716,19 @@ PVR_ERROR CPVRClient::CallRecordingMenuHook(const CPVRClientMenuHook& hook, const std::shared_ptr& recording, bool bDeleted) { - return DoAddonCall(__func__, [&hook, &recording, &bDeleted](const AddonInstance* addon) { - PVR_RECORDING tag; - recording->FillAddonData(tag); + return DoAddonCall(__func__, + [&hook, &recording, &bDeleted](const AddonInstance* addon) + { + const CAddonRecording tag(*recording); - PVR_MENUHOOK menuHook; - menuHook.category = bDeleted ? PVR_MENUHOOK_DELETED_RECORDING : PVR_MENUHOOK_RECORDING; - menuHook.iHookId = hook.GetId(); - menuHook.iLocalizedStringId = hook.GetLabelId(); + PVR_MENUHOOK menuHook; + menuHook.category = + bDeleted ? PVR_MENUHOOK_DELETED_RECORDING : PVR_MENUHOOK_RECORDING; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); - return addon->toAddon->CallRecordingMenuHook(addon, &menuHook, &tag); - }); + return addon->toAddon->CallRecordingMenuHook(addon, &menuHook, &tag); + }); } PVR_ERROR CPVRClient::CallTimerMenuHook(const CPVRClientMenuHook& hook, diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp index 362bf8c064f3c..38c288d5f956d 100644 --- a/xbmc/pvr/recordings/PVRRecording.cpp +++ b/xbmc/pvr/recordings/PVRRecording.cpp @@ -75,15 +75,19 @@ CPVRRecording::CPVRRecording() } CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClientId) - : m_iconPath(recording.strIconPath, IMAGE_OWNER_PATTERN), - m_thumbnailPath(recording.strThumbnailPath, IMAGE_OWNER_PATTERN), - m_fanartPath(recording.strFanartPath, IMAGE_OWNER_PATTERN) + : m_iconPath(recording.strIconPath ? recording.strIconPath : "", IMAGE_OWNER_PATTERN), + m_thumbnailPath(recording.strThumbnailPath ? recording.strThumbnailPath : "", + IMAGE_OWNER_PATTERN), + m_fanartPath(recording.strFanartPath ? recording.strFanartPath : "", IMAGE_OWNER_PATTERN) { Reset(); - m_strRecordingId = recording.strRecordingId; - m_strTitle = recording.strTitle; - m_strShowTitle = recording.strEpisodeName; + if (recording.strRecordingId) + m_strRecordingId = recording.strRecordingId; + if (recording.strTitle) + m_strTitle = recording.strTitle; + if (recording.strEpisodeName) + m_strShowTitle = recording.strEpisodeName; m_iSeason = recording.iSeriesNumber; m_iEpisode = recording.iEpisodeNumber; if (recording.iYear > 0) @@ -95,22 +99,28 @@ CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClien m_iPriority = recording.iPriority; m_iLifetime = recording.iLifetime; // Deleted recording is placed at the root of the deleted view - m_strDirectory = recording.bIsDeleted ? "" : recording.strDirectory; - m_strPlot = recording.strPlot; - m_strPlotOutline = recording.strPlotOutline; - m_strChannelName = recording.strChannelName; + if (recording.strDirectory && !recording.bIsDeleted) + m_strDirectory = recording.strDirectory; + if (recording.strPlot) + m_strPlot = recording.strPlot; + if (recording.strPlotOutline) + m_strPlotOutline = recording.strPlotOutline; + if (recording.strChannelName) + m_strChannelName = recording.strChannelName; m_bIsDeleted = recording.bIsDeleted; m_iEpgEventId = recording.iEpgEventId; m_iChannelUid = recording.iChannelUid; - if (strlen(recording.strFirstAired) > 0) + if (recording.strFirstAired && strlen(recording.strFirstAired) > 0) m_firstAired.SetFromW3CDateTime(recording.strFirstAired); m_iFlags = recording.iFlags; if (recording.sizeInBytes >= 0) m_sizeInBytes = recording.sizeInBytes; - m_strProviderName = recording.strProviderName; + if (recording.strProviderName) + m_strProviderName = recording.strProviderName; m_iClientProviderUniqueId = recording.iClientProviderUid; - SetGenre(recording.iGenreType, recording.iGenreSubType, recording.strGenreDescription); + SetGenre(recording.iGenreType, recording.iGenreSubType, + recording.strGenreDescription ? recording.strGenreDescription : ""); CVideoInfoTag::SetPlayCount(recording.iPlayCount); if (recording.iLastPlayedPosition > 0 && recording.iDuration > recording.iLastPlayedPosition) CVideoInfoTag::SetResumePoint(recording.iLastPlayedPosition, recording.iDuration, ""); @@ -177,52 +187,6 @@ bool CPVRRecording::operator!=(const CPVRRecording& right) const return !(*this == right); } -void CPVRRecording::FillAddonData(PVR_RECORDING& recording) const -{ - time_t recTime; - RecordingTimeAsUTC().GetAsTime(recTime); - - recording = {}; - strncpy(recording.strRecordingId, ClientRecordingID().c_str(), - sizeof(recording.strRecordingId) - 1); - strncpy(recording.strTitle, m_strTitle.c_str(), sizeof(recording.strTitle) - 1); - strncpy(recording.strEpisodeName, m_strShowTitle.c_str(), sizeof(recording.strEpisodeName) - 1); - recording.iSeriesNumber = m_iSeason; - recording.iEpisodeNumber = m_iEpisode; - recording.iYear = GetYear(); - strncpy(recording.strDirectory, Directory().c_str(), sizeof(recording.strDirectory) - 1); - strncpy(recording.strPlotOutline, m_strPlotOutline.c_str(), sizeof(recording.strPlotOutline) - 1); - strncpy(recording.strPlot, m_strPlot.c_str(), sizeof(recording.strPlot) - 1); - strncpy(recording.strGenreDescription, GetGenresLabel().c_str(), - sizeof(recording.strGenreDescription) - 1); - strncpy(recording.strChannelName, ChannelName().c_str(), sizeof(recording.strChannelName) - 1); - strncpy(recording.strIconPath, ClientIconPath().c_str(), sizeof(recording.strIconPath) - 1); - strncpy(recording.strThumbnailPath, ClientThumbnailPath().c_str(), - sizeof(recording.strThumbnailPath) - 1); - strncpy(recording.strFanartPath, ClientFanartPath().c_str(), sizeof(recording.strFanartPath) - 1); - recording.recordingTime = - recTime - CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection; - recording.iDuration = GetDuration(); - recording.iPriority = Priority(); - recording.iLifetime = LifeTime(); - recording.iGenreType = GenreType(); - recording.iGenreSubType = GenreSubType(); - recording.iPlayCount = GetLocalPlayCount(); - recording.iLastPlayedPosition = std::lrint(GetLocalResumePoint().timeInSeconds); - recording.bIsDeleted = IsDeleted(); - recording.iEpgEventId = m_iEpgEventId; - recording.iChannelUid = ChannelUid(); - recording.channelType = - IsRadio() ? PVR_RECORDING_CHANNEL_TYPE_RADIO : PVR_RECORDING_CHANNEL_TYPE_TV; - if (FirstAired().IsValid()) - strncpy(recording.strFirstAired, FirstAired().GetAsW3CDate().c_str(), - sizeof(recording.strFirstAired) - 1); - recording.iFlags = Flags(); - recording.sizeInBytes = GetSizeInBytes(); - strncpy(recording.strProviderName, ProviderName().c_str(), sizeof(recording.strProviderName) - 1); - recording.iClientProviderUid = ClientProviderUniqueId(); -} - void CPVRRecording::Serialize(CVariant& value) const { CVideoInfoTag::Serialize(value); diff --git a/xbmc/pvr/recordings/PVRRecording.h b/xbmc/pvr/recordings/PVRRecording.h index bdd8378225961..1a7e438111628 100644 --- a/xbmc/pvr/recordings/PVRRecording.h +++ b/xbmc/pvr/recordings/PVRRecording.h @@ -60,12 +60,6 @@ class CPVRRecording final : public CVideoInfoTag bool operator==(const CPVRRecording& right) const; bool operator!=(const CPVRRecording& right) const; - /*! - * @brief Copy over data to the given PVR_RECORDING instance. - * @param recording The recording instance to fill. - */ - void FillAddonData(PVR_RECORDING& recording) const; - void Serialize(CVariant& value) const override; // ISortable implementation From 919f943896144fd825d73e95f8ff0f0bdeaa43ab Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 18 Jul 2024 09:04:33 +0200 Subject: [PATCH 322/651] [addons][PVR] PVR add-on API: PVR_TIMER: Replace fixed size char arrays with dynamic arrays. --- .../include/kodi/addon-instance/pvr/Timers.h | 38 +++++--- .../c-api/addon-instance/pvr/pvr_defines.h | 2 - .../c-api/addon-instance/pvr/pvr_timers.h | 10 +- xbmc/pvr/addons/PVRClient.cpp | 95 +++++++++++++++---- xbmc/pvr/timers/PVRTimerInfoTag.cpp | 49 +--------- xbmc/pvr/timers/PVRTimerInfoTag.h | 42 ++++++-- 6 files changed, 149 insertions(+), 87 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h index 118f8e5d2681f..3042a877bd3e8 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h @@ -35,7 +35,7 @@ namespace addon /// @copydetails cpp_kodi_addon_pvr_Defs_Timer_PVRTimer_Help /// ///@{ -class PVRTimer : public CStructHdl +class PVRTimer : public DynamicCStructHdl { friend class CInstancePVRClient; @@ -66,7 +66,7 @@ class PVRTimer : public CStructHdl m_cStructure->iGenreType = PVR_TIMER_VALUE_NOT_AVAILABLE; m_cStructure->iGenreSubType = PVR_TIMER_VALUE_NOT_AVAILABLE; } - PVRTimer(const PVRTimer& data) : CStructHdl(data) {} + PVRTimer(const PVRTimer& timer) : DynamicCStructHdl(timer) {} /*! \endcond */ /// @defgroup cpp_kodi_addon_pvr_Defs_Timer_PVRTimer_Help Value Help @@ -167,7 +167,7 @@ class PVRTimer : public CStructHdl /// A title for this timer. void SetTitle(const std::string& title) { - strncpy(m_cStructure->strTitle, title.c_str(), sizeof(m_cStructure->strTitle) - 1); + ReallocAndCopyString(&m_cStructure->strTitle, title.c_str()); } /// @brief To get with @ref SetTitle changed values. @@ -241,8 +241,7 @@ class PVRTimer : public CStructHdl /// Format is backend-dependent, for example regexp. void SetEPGSearchString(const std::string& epgSearchString) { - strncpy(m_cStructure->strEpgSearchString, epgSearchString.c_str(), - sizeof(m_cStructure->strEpgSearchString) - 1); + ReallocAndCopyString(&m_cStructure->strEpgSearchString, epgSearchString.c_str()); } /// @brief To get with @ref SetEPGSearchString changed values @@ -263,7 +262,7 @@ class PVRTimer : public CStructHdl /// The (relative) directory where the recording will be stored in. void SetDirectory(const std::string& directory) { - strncpy(m_cStructure->strDirectory, directory.c_str(), sizeof(m_cStructure->strDirectory) - 1); + ReallocAndCopyString(&m_cStructure->strDirectory, directory.c_str()); } /// @brief To get with @ref SetDirectory changed values. @@ -273,7 +272,7 @@ class PVRTimer : public CStructHdl /// The summary for this timer. void SetSummary(const std::string& summary) { - strncpy(m_cStructure->strSummary, summary.c_str(), sizeof(m_cStructure->strSummary) - 1); + ReallocAndCopyString(&m_cStructure->strSummary, summary.c_str()); } /// @brief To get with @ref SetDirectory changed values. @@ -458,17 +457,34 @@ class PVRTimer : public CStructHdl /// checking with here, instead of @ref SetTitle() (and @ref SetFullTextEpgSearch()). void SetSeriesLink(const std::string& seriesLink) { - strncpy(m_cStructure->strSeriesLink, seriesLink.c_str(), - sizeof(m_cStructure->strSeriesLink) - 1); + ReallocAndCopyString(&m_cStructure->strSeriesLink, seriesLink.c_str()); } /// @brief To get with @ref SetSeriesLink changed values. std::string GetSeriesLink() const { return m_cStructure->strSeriesLink; } ///@} + static void AllocResources(const PVR_TIMER* source, PVR_TIMER* target) + { + target->strTitle = AllocAndCopyString(source->strTitle); + target->strEpgSearchString = AllocAndCopyString(source->strEpgSearchString); + target->strDirectory = AllocAndCopyString(source->strDirectory); + target->strSummary = AllocAndCopyString(source->strSummary); + target->strSeriesLink = AllocAndCopyString(source->strSeriesLink); + } + + static void FreeResources(PVR_TIMER* target) + { + FreeString(target->strTitle); + FreeString(target->strEpgSearchString); + FreeString(target->strDirectory); + FreeString(target->strSummary); + FreeString(target->strSeriesLink); + } + private: - PVRTimer(const PVR_TIMER* data) : CStructHdl(data) {} - PVRTimer(PVR_TIMER* data) : CStructHdl(data) {} + PVRTimer(const PVR_TIMER* timer) : DynamicCStructHdl(timer) {} + PVRTimer(PVR_TIMER* timer) : DynamicCStructHdl(timer) {} }; ///@} diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h index 91d3951646e16..bfc154efdc16b 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h @@ -25,8 +25,6 @@ extern "C" */ ///@{ #define PVR_ADDON_NAME_STRING_LENGTH 1024 -#define PVR_ADDON_URL_STRING_LENGTH 1024 -#define PVR_ADDON_DESC_STRING_LENGTH 1024 #define PVR_ADDON_EDL_LENGTH 64 #define PVR_ADDON_TIMERTYPE_ARRAY_SIZE 32 #define PVR_ADDON_TIMERTYPE_VALUES_ARRAY_SIZE 512 diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h index 3fb50f15e53bb..7d333be1ad434 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h @@ -347,11 +347,11 @@ extern "C" bool bEndAnyTime; enum PVR_TIMER_STATE state; unsigned int iTimerType; - char strTitle[PVR_ADDON_NAME_STRING_LENGTH]; - char strEpgSearchString[PVR_ADDON_NAME_STRING_LENGTH]; + const char* strTitle; + const char* strEpgSearchString; bool bFullTextEpgSearch; - char strDirectory[PVR_ADDON_URL_STRING_LENGTH]; - char strSummary[PVR_ADDON_DESC_STRING_LENGTH]; + const char* strDirectory; + const char* strSummary; int iPriority; int iLifetime; int iMaxRecordings; @@ -364,7 +364,7 @@ extern "C" unsigned int iMarginEnd; int iGenreType; int iGenreSubType; - char strSeriesLink[PVR_ADDON_URL_STRING_LENGTH]; + const char* strSeriesLink; } PVR_TIMER; /*! diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 9911e719402fd..d3315cc17c1c3 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -176,6 +176,64 @@ class CAddonRecording : public PVR_RECORDING const std::string m_firstAired; const std::string m_providerName; }; + +class CAddonTimer : public PVR_TIMER +{ +public: + explicit CAddonTimer(const CPVRTimerInfoTag& timer) + : m_title(timer.Title()), + m_epgSearchString(timer.EpgSearchString()), + m_directory(timer.Directory()), + m_summary(timer.Summary()), + m_seriesLink(timer.SeriesLink()) + { + time_t start; + timer.StartAsUTC().GetAsTime(start); + time_t end; + timer.EndAsUTC().GetAsTime(end); + time_t firstDay; + timer.FirstDayAsUTC().GetAsTime(firstDay); + const std::shared_ptr epgTag{timer.GetEpgInfoTag()}; + const int timeCorrection{ + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection}; + + iClientIndex = timer.ClientIndex(); + iParentClientIndex = timer.ParentClientIndex(); + state = timer.State(); + iTimerType = timer.GetTimerType()->GetTypeId(); + iClientChannelUid = timer.ClientChannelUID(); + strTitle = m_title.c_str(); + strEpgSearchString = m_epgSearchString.c_str(); + bFullTextEpgSearch = timer.IsFullTextEpgSearch(); + strDirectory = m_directory.c_str(); + iPriority = timer.Priority(); + iLifetime = timer.Lifetime(); + iMaxRecordings = timer.MaxRecordings(); + iPreventDuplicateEpisodes = timer.PreventDupEpisodesPolicy(); + iRecordingGroup = timer.RecordingGroup(); + iWeekdays = timer.WeekDays(); + startTime = start - timeCorrection; + endTime = end - timeCorrection; + bStartAnyTime = timer.IsStartAnyTime(); + bEndAnyTime = timer.IsEndAnyTime(); + firstDay = firstDay - timeCorrection; + iEpgUid = epgTag ? epgTag->UniqueBroadcastID() : PVR_TIMER_NO_EPG_UID; + strSummary = m_summary.c_str(); + iMarginStart = timer.MarginStart(); + iMarginEnd = timer.MarginEnd(); + iGenreType = epgTag ? epgTag->GenreType() : 0; + iGenreSubType = epgTag ? epgTag->GenreSubType() : 0; + strSeriesLink = m_seriesLink.c_str(); + } + virtual ~CAddonTimer() = default; + +private: + const std::string m_title; + const std::string m_epgSearchString; + const std::string m_directory; + const std::string m_summary; + const std::string m_seriesLink; +}; } // unnamed namespace namespace PVR @@ -1105,9 +1163,9 @@ PVR_ERROR CPVRClient::AddTimer(const CPVRTimerInfoTag& timer) { return DoAddonCall( __func__, - [&timer](const AddonInstance* addon) { - PVR_TIMER tag; - timer.FillAddonData(tag); + [&timer](const AddonInstance* addon) + { + const CAddonTimer tag{timer}; return addon->toAddon->AddTimer(addon, &tag); }, m_clientCapabilities.SupportsTimers()); @@ -1117,9 +1175,9 @@ PVR_ERROR CPVRClient::DeleteTimer(const CPVRTimerInfoTag& timer, bool bForce /* { return DoAddonCall( __func__, - [&timer, bForce](const AddonInstance* addon) { - PVR_TIMER tag; - timer.FillAddonData(tag); + [&timer, bForce](const AddonInstance* addon) + { + const CAddonTimer tag{timer}; return addon->toAddon->DeleteTimer(addon, &tag, bForce); }, m_clientCapabilities.SupportsTimers()); @@ -1129,9 +1187,9 @@ PVR_ERROR CPVRClient::UpdateTimer(const CPVRTimerInfoTag& timer) { return DoAddonCall( __func__, - [&timer](const AddonInstance* addon) { - PVR_TIMER tag; - timer.FillAddonData(tag); + [&timer](const AddonInstance* addon) + { + const CAddonTimer tag{timer}; return addon->toAddon->UpdateTimer(addon, &tag); }, m_clientCapabilities.SupportsTimers()); @@ -1734,17 +1792,18 @@ PVR_ERROR CPVRClient::CallRecordingMenuHook(const CPVRClientMenuHook& hook, PVR_ERROR CPVRClient::CallTimerMenuHook(const CPVRClientMenuHook& hook, const std::shared_ptr& timer) { - return DoAddonCall(__func__, [&hook, &timer](const AddonInstance* addon) { - PVR_TIMER tag; - timer->FillAddonData(tag); + return DoAddonCall(__func__, + [&hook, &timer](const AddonInstance* addon) + { + const CAddonTimer tag(*timer); - PVR_MENUHOOK menuHook; - menuHook.category = PVR_MENUHOOK_TIMER; - menuHook.iHookId = hook.GetId(); - menuHook.iLocalizedStringId = hook.GetLabelId(); + PVR_MENUHOOK menuHook; + menuHook.category = PVR_MENUHOOK_TIMER; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); - return addon->toAddon->CallTimerMenuHook(addon, &menuHook, &tag); - }); + return addon->toAddon->CallTimerMenuHook(addon, &menuHook, &tag); + }); } PVR_ERROR CPVRClient::CallSettingsMenuHook(const CPVRClientMenuHook& hook) diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.cpp b/xbmc/pvr/timers/PVRTimerInfoTag.cpp index dba8a35656deb..100cfa6674901 100644 --- a/xbmc/pvr/timers/PVRTimerInfoTag.cpp +++ b/xbmc/pvr/timers/PVRTimerInfoTag.cpp @@ -90,10 +90,10 @@ CPVRTimerInfoTag::CPVRTimerInfoTag(bool bRadio /* = false */) CPVRTimerInfoTag::CPVRTimerInfoTag(const PVR_TIMER& timer, const std::shared_ptr& channel, unsigned int iClientId) - : m_strTitle(timer.strTitle), - m_strEpgSearchString(timer.strEpgSearchString), + : m_strTitle(timer.strTitle ? timer.strTitle : ""), + m_strEpgSearchString(timer.strEpgSearchString ? timer.strEpgSearchString : ""), m_bFullTextEpgSearch(timer.bFullTextEpgSearch), - m_strDirectory(timer.strDirectory), + m_strDirectory(timer.strDirectory ? timer.strDirectory : ""), m_state(timer.state), m_iClientId(iClientId), m_iClientIndex(timer.iClientIndex), @@ -115,7 +115,7 @@ CPVRTimerInfoTag::CPVRTimerInfoTag(const PVR_TIMER& timer, m_iMarginStart(timer.iMarginStart), m_iMarginEnd(timer.iMarginEnd), m_iEpgUid(timer.iEpgUid), - m_strSeriesLink(timer.strSeriesLink), + m_strSeriesLink(timer.strSeriesLink ? timer.strSeriesLink : ""), m_StartTime( timer.startTime + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection), @@ -229,47 +229,6 @@ bool CPVRTimerInfoTag::operator!=(const CPVRTimerInfoTag& right) const return !(*this == right); } -void CPVRTimerInfoTag::FillAddonData(PVR_TIMER& timer) const -{ - time_t start, end, firstDay; - StartAsUTC().GetAsTime(start); - EndAsUTC().GetAsTime(end); - FirstDayAsUTC().GetAsTime(firstDay); - const std::shared_ptr epgTag = GetEpgInfoTag(); - const int iPVRTimeCorrection = - CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection; - - timer = {}; - timer.iClientIndex = m_iClientIndex; - timer.iParentClientIndex = m_iParentClientIndex; - timer.state = m_state; - timer.iTimerType = GetTimerType()->GetTypeId(); - timer.iClientChannelUid = m_iClientChannelUid; - strncpy(timer.strTitle, m_strTitle.c_str(), sizeof(timer.strTitle) - 1); - strncpy(timer.strEpgSearchString, m_strEpgSearchString.c_str(), - sizeof(timer.strEpgSearchString) - 1); - timer.bFullTextEpgSearch = m_bFullTextEpgSearch; - strncpy(timer.strDirectory, m_strDirectory.c_str(), sizeof(timer.strDirectory) - 1); - timer.iPriority = m_iPriority; - timer.iLifetime = m_iLifetime; - timer.iMaxRecordings = m_iMaxRecordings; - timer.iPreventDuplicateEpisodes = m_iPreventDupEpisodes; - timer.iRecordingGroup = m_iRecordingGroup; - timer.iWeekdays = m_iWeekdays; - timer.startTime = start - iPVRTimeCorrection; - timer.endTime = end - iPVRTimeCorrection; - timer.bStartAnyTime = m_bStartAnyTime; - timer.bEndAnyTime = m_bEndAnyTime; - timer.firstDay = firstDay - iPVRTimeCorrection; - timer.iEpgUid = epgTag ? epgTag->UniqueBroadcastID() : PVR_TIMER_NO_EPG_UID; - strncpy(timer.strSummary, m_strSummary.c_str(), sizeof(timer.strSummary) - 1); - timer.iMarginStart = m_iMarginStart; - timer.iMarginEnd = m_iMarginEnd; - timer.iGenreType = epgTag ? epgTag->GenreType() : 0; - timer.iGenreSubType = epgTag ? epgTag->GenreSubType() : 0; - strncpy(timer.strSeriesLink, SeriesLink().c_str(), sizeof(timer.strSeriesLink) - 1); -} - void CPVRTimerInfoTag::Serialize(CVariant& value) const { value["channelid"] = m_channel != NULL ? m_channel->ChannelID() : -1; diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.h b/xbmc/pvr/timers/PVRTimerInfoTag.h index 664bdb1d42e25..4098c067c7665 100644 --- a/xbmc/pvr/timers/PVRTimerInfoTag.h +++ b/xbmc/pvr/timers/PVRTimerInfoTag.h @@ -45,12 +45,6 @@ class CPVRTimerInfoTag final : public ISerializable bool operator==(const CPVRTimerInfoTag& right) const; bool operator!=(const CPVRTimerInfoTag& right) const; - /*! - * @brief Copy over data to the given PVR_TIMER instance. - * @param timer The timer instance to fill. - */ - void FillAddonData(PVR_TIMER& timer) const; - // ISerializable implementation void Serialize(CVariant& value) const override; @@ -484,6 +478,42 @@ class CPVRTimerInfoTag final : public ISerializable */ const std::string& SeriesLink() const; + /*! + * @brief The directory for the recordings for this timer. + * @return The directory. + */ + const std::string& Directory() const { return m_strDirectory; } + + /*! + * @brief The priority for this timer. + * @return The priority. + */ + int Priority() const { return m_iPriority; } + + /*! + * @brief The life time for this timer. + * @return The life time. + */ + int Lifetime() const { return m_iLifetime; } + + /*! + * @brief The maximum number of recordings for this timer. + * @return The number of maximum recordings. + */ + int MaxRecordings() const { return m_iMaxRecordings; } + + /*! + * @brief The policy to be used to prevent duplicate episodes for this timer. + * @return The policy for preventing duplicate episodes. + */ + int PreventDupEpisodesPolicy() const { return m_iPreventDupEpisodes; } + + /*! + * @brief The recording group for this timer. + * @return The recording group. + */ + int RecordingGroup() const { return m_iRecordingGroup; } + /*! * @brief Get the UID of the epg event associated with this timer tag, if any. * @return The UID or EPG_TAG_INVALID_UID. From a64547a218198567049166ba5ffbd9af37e19054 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:44:21 +0200 Subject: [PATCH 323/651] [addons][PVR] PVR add-on API: Change PVREPGTag implementation for dynamic char arrays. --- .../include/kodi/addon-instance/PVR.h | 2 +- .../include/kodi/addon-instance/pvr/EPG.h | 205 +++++++++--------- 2 files changed, 102 insertions(+), 105 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h index 911657b9908cf..41f4fba5dbc61 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h @@ -1682,7 +1682,7 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance /// inline void EpgEventStateChange(kodi::addon::PVREPGTag& tag, EPG_EVENT_STATE newState) { - m_instanceData->toKodi->EpgEventStateChange(m_instanceData->toKodi->kodiInstance, tag.GetTag(), + m_instanceData->toKodi->EpgEventStateChange(m_instanceData->toKodi->kodiInstance, tag, newState); } //---------------------------------------------------------------------------- diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h index 302b717ac6c56..41d9258cc9fa7 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h @@ -36,7 +36,7 @@ namespace addon /// @copydetails cpp_kodi_addon_pvr_Defs_epg_PVREPGTag_Help /// ///@{ -class PVREPGTag : public CStructHdl +class PVREPGTag : public DynamicCStructHdl { friend class CInstancePVRClient; @@ -49,24 +49,7 @@ class PVREPGTag : public CStructHdl m_cStructure->iEpisodeNumber = EPG_TAG_INVALID_SERIES_EPISODE; m_cStructure->iEpisodePartNumber = EPG_TAG_INVALID_SERIES_EPISODE; } - PVREPGTag(const PVREPGTag& epg) - : CStructHdl(epg), - m_title(epg.m_title), - m_plotOutline(epg.m_plotOutline), - m_plot(epg.m_plot), - m_originalTitle(epg.m_originalTitle), - m_cast(epg.m_cast), - m_director(epg.m_director), - m_writer(epg.m_writer), - m_IMDBNumber(epg.m_IMDBNumber), - m_episodeName(epg.m_episodeName), - m_iconPath(epg.m_iconPath), - m_seriesLink(epg.m_seriesLink), - m_genreDescription(epg.m_genreDescription), - m_parentalRatingCode(epg.m_parentalRatingCode), - m_firstAired(epg.m_firstAired) - { - } + PVREPGTag(const PVREPGTag& epg) : DynamicCStructHdl(epg) {} /*! \endcond */ /// @defgroup cpp_kodi_addon_pvr_Defs_epg_PVREPGTag_Help Value Help @@ -129,10 +112,13 @@ class PVREPGTag : public CStructHdl /// @brief **required**\n /// This event's title. - void SetTitle(const std::string& title) { m_title = title; } + void SetTitle(const std::string& title) + { + ReallocAndCopyString(&m_cStructure->strTitle, title.c_str()); + } /// @brief To get with @ref SetTitle changed values. - std::string GetTitle() const { return m_title; } + std::string GetTitle() const { return m_cStructure->strTitle; } /// @brief **required**\n /// Start time in UTC. @@ -154,51 +140,69 @@ class PVREPGTag : public CStructHdl /// @brief **optional**\n /// Plot outline name. - void SetPlotOutline(const std::string& plotOutline) { m_plotOutline = plotOutline; } + void SetPlotOutline(const std::string& plotOutline) + { + ReallocAndCopyString(&m_cStructure->strPlotOutline, plotOutline.c_str()); + } /// @brief To get with @ref SetPlotOutline changed values. - std::string GetPlotOutline() const { return m_plotOutline; } + std::string GetPlotOutline() const { return m_cStructure->strPlotOutline; } /// @brief **optional**\n /// Plot name. - void SetPlot(const std::string& plot) { m_plot = plot; } + void SetPlot(const std::string& plot) + { + ReallocAndCopyString(&m_cStructure->strPlot, plot.c_str()); + } /// @brief To get with @ref GetPlot changed values. - std::string GetPlot() const { return m_plot; } + std::string GetPlot() const { return m_cStructure->strPlot; } /// @brief **optional**\n /// Original title. - void SetOriginalTitle(const std::string& originalTitle) { m_originalTitle = originalTitle; } + void SetOriginalTitle(const std::string& originalTitle) + { + ReallocAndCopyString(&m_cStructure->strOriginalTitle, originalTitle.c_str()); + } /// @brief To get with @ref SetOriginalTitle changed values - std::string GetOriginalTitle() const { return m_originalTitle; } + std::string GetOriginalTitle() const { return m_cStructure->strOriginalTitle; } /// @brief **optional**\n /// Cast name(s). /// /// @note Use @ref EPG_STRING_TOKEN_SEPARATOR to separate different persons. - void SetCast(const std::string& cast) { m_cast = cast; } + void SetCast(const std::string& cast) + { + ReallocAndCopyString(&m_cStructure->strCast, cast.c_str()); + } /// @brief To get with @ref SetCast changed values - std::string GetCast() const { return m_cast; } + std::string GetCast() const { return m_cStructure->strCast; } /// @brief **optional**\n /// Director name(s). /// /// @note Use @ref EPG_STRING_TOKEN_SEPARATOR to separate different persons. - void SetDirector(const std::string& director) { m_director = director; } + void SetDirector(const std::string& director) + { + ReallocAndCopyString(&m_cStructure->strDirector, director.c_str()); + } /// @brief To get with @ref SetDirector changed values. - std::string GetDirector() const { return m_director; } + std::string GetDirector() const { return m_cStructure->strDirector; } /// @brief **optional**\n /// Writer name(s). /// /// @note Use @ref EPG_STRING_TOKEN_SEPARATOR to separate different persons. - void SetWriter(const std::string& writer) { m_writer = writer; } + void SetWriter(const std::string& writer) + { + ReallocAndCopyString(&m_cStructure->strWriter, writer.c_str()); + } /// @brief To get with @ref SetDirector changed values - std::string GetWriter() const { return m_writer; } + std::string GetWriter() const { return m_cStructure->strWriter; } /// @brief **optional**\n /// Year. @@ -209,17 +213,23 @@ class PVREPGTag : public CStructHdl /// @brief **optional**\n /// [IMDB](https://en.wikipedia.org/wiki/IMDb) identification number. - void SetIMDBNumber(const std::string& IMDBNumber) { m_IMDBNumber = IMDBNumber; } + void SetIMDBNumber(const std::string& IMDBNumber) + { + ReallocAndCopyString(&m_cStructure->strIMDBNumber, IMDBNumber.c_str()); + } /// @brief To get with @ref SetIMDBNumber changed values. - std::string GetIMDBNumber() const { return m_IMDBNumber; } + std::string GetIMDBNumber() const { return m_cStructure->strIMDBNumber; } /// @brief **optional**\n /// Icon path. - void SetIconPath(const std::string& iconPath) { m_iconPath = iconPath; } + void SetIconPath(const std::string& iconPath) + { + ReallocAndCopyString(&m_cStructure->strIconPath, iconPath.c_str()); + } /// @brief To get with @ref SetIconPath changed values. - std::string GetIconPath() const { return m_iconPath; } + std::string GetIconPath() const { return m_cStructure->strIconPath; } /// @brief **optional**\n /// Genre type. @@ -313,18 +323,21 @@ class PVREPGTag : public CStructHdl /// void SetGenreDescription(const std::string& genreDescription) { - m_genreDescription = genreDescription; + ReallocAndCopyString(&m_cStructure->strGenreDescription, genreDescription.c_str()); } /// @brief To get with @ref SetGenreDescription changed values. - std::string GetGenreDescription() const { return m_genreDescription; } + std::string GetGenreDescription() const { return m_cStructure->strGenreDescription; } /// @brief **optional**\n /// First aired in UTC. - void SetFirstAired(const std::string& firstAired) { m_firstAired = firstAired; } + void SetFirstAired(const std::string& firstAired) + { + ReallocAndCopyString(&m_cStructure->strFirstAired, firstAired.c_str()); + } /// @brief To get with @ref SetFirstAired changed values. - std::string GetFirstAired() const { return m_firstAired; } + std::string GetFirstAired() const { return m_cStructure->strFirstAired; } /// @brief **optional**\n /// Parental rating. @@ -337,11 +350,11 @@ class PVREPGTag : public CStructHdl /// This event's parental rating code. void SetParentalRatingCode(const std::string& parentalRatingCode) { - m_parentalRatingCode = parentalRatingCode; + ReallocAndCopyString(&m_cStructure->strParentalRatingCode, parentalRatingCode.c_str()); } /// @brief To get with @ref SetParentalRatingCode changed values. - std::string GetParentalRatingCode() const { return m_parentalRatingCode; } + std::string GetParentalRatingCode() const { return m_cStructure->strParentalRatingCode; } /// @brief **optional**\n /// Star rating. @@ -376,10 +389,13 @@ class PVREPGTag : public CStructHdl /// @brief **optional**\n /// Episode name. - void SetEpisodeName(const std::string& episodeName) { m_episodeName = episodeName; } + void SetEpisodeName(const std::string& episodeName) + { + ReallocAndCopyString(&m_cStructure->strEpisodeName, episodeName.c_str()); + } /// @brief To get with @ref SetEpisodeName changed values. - std::string GetEpisodeName() const { return m_episodeName; } + std::string GetEpisodeName() const { return m_cStructure->strEpisodeName; } /// @brief **optional**\n /// Bit field of independent flags associated with the EPG entry. @@ -397,74 +413,55 @@ class PVREPGTag : public CStructHdl /// @brief **optional**\n /// Series link for this event. - void SetSeriesLink(const std::string& seriesLink) { m_seriesLink = seriesLink; } + void SetSeriesLink(const std::string& seriesLink) + { + ReallocAndCopyString(&m_cStructure->strSeriesLink, seriesLink.c_str()); + } /// @brief To get with @ref SetSeriesLink changed values. - std::string GetSeriesLink() const { return m_seriesLink; } + std::string GetSeriesLink() const { return m_cStructure->strSeriesLink; } ///@} - // Internal used, as this have own memory for strings and to translate them to "C" - EPG_TAG* GetTag() const + static void AllocResources(const EPG_TAG* source, EPG_TAG* target) { - m_cStructure->strTitle = m_title.c_str(); - m_cStructure->strPlotOutline = m_plotOutline.c_str(); - m_cStructure->strPlot = m_plot.c_str(); - m_cStructure->strOriginalTitle = m_originalTitle.c_str(); - m_cStructure->strCast = m_cast.c_str(); - m_cStructure->strDirector = m_director.c_str(); - m_cStructure->strWriter = m_writer.c_str(); - m_cStructure->strIMDBNumber = m_IMDBNumber.c_str(); - m_cStructure->strIconPath = m_iconPath.c_str(); - m_cStructure->strGenreDescription = m_genreDescription.c_str(); - m_cStructure->strParentalRatingCode = m_parentalRatingCode.c_str(); - m_cStructure->strEpisodeName = m_episodeName.c_str(); - m_cStructure->strSeriesLink = m_seriesLink.c_str(); - m_cStructure->strFirstAired = m_firstAired.c_str(); - - return m_cStructure; + target->strTitle = AllocAndCopyString(source->strTitle); + target->strPlotOutline = AllocAndCopyString(source->strPlotOutline); + target->strPlot = AllocAndCopyString(source->strPlot); + target->strOriginalTitle = AllocAndCopyString(source->strOriginalTitle); + target->strCast = AllocAndCopyString(source->strCast); + target->strDirector = AllocAndCopyString(source->strDirector); + target->strWriter = AllocAndCopyString(source->strWriter); + target->strIMDBNumber = AllocAndCopyString(source->strIMDBNumber); + target->strIconPath = AllocAndCopyString(source->strIconPath); + target->strGenreDescription = AllocAndCopyString(source->strGenreDescription); + target->strParentalRatingCode = AllocAndCopyString(source->strParentalRatingCode); + target->strEpisodeName = AllocAndCopyString(source->strEpisodeName); + target->strSeriesLink = AllocAndCopyString(source->strSeriesLink); + target->strFirstAired = AllocAndCopyString(source->strFirstAired); } -private: - PVREPGTag(const EPG_TAG* epg) : CStructHdl(epg) { SetData(epg); } - PVREPGTag(EPG_TAG* epg) : CStructHdl(epg) { SetData(epg); } - - const PVREPGTag& operator=(const PVREPGTag& right); - const PVREPGTag& operator=(const EPG_TAG& right); - operator EPG_TAG*(); - - std::string m_title; - std::string m_plotOutline; - std::string m_plot; - std::string m_originalTitle; - std::string m_cast; - std::string m_director; - std::string m_writer; - std::string m_IMDBNumber; - std::string m_episodeName; - std::string m_iconPath; - std::string m_seriesLink; - std::string m_genreDescription; - std::string m_parentalRatingCode; - std::string m_firstAired; - - void SetData(const EPG_TAG* tag) + static void FreeResources(EPG_TAG* target) { - m_title = tag->strTitle == nullptr ? "" : tag->strTitle; - m_plotOutline = tag->strPlotOutline == nullptr ? "" : tag->strPlotOutline; - m_plot = tag->strPlot == nullptr ? "" : tag->strPlot; - m_originalTitle = tag->strOriginalTitle == nullptr ? "" : tag->strOriginalTitle; - m_cast = tag->strCast == nullptr ? "" : tag->strCast; - m_director = tag->strDirector == nullptr ? "" : tag->strDirector; - m_writer = tag->strWriter == nullptr ? "" : tag->strWriter; - m_IMDBNumber = tag->strIMDBNumber == nullptr ? "" : tag->strIMDBNumber; - m_iconPath = tag->strIconPath == nullptr ? "" : tag->strIconPath; - m_genreDescription = tag->strGenreDescription == nullptr ? "" : tag->strGenreDescription; - m_parentalRatingCode = tag->strParentalRatingCode == nullptr ? "" : tag->strParentalRatingCode; - m_episodeName = tag->strEpisodeName == nullptr ? "" : tag->strEpisodeName; - m_seriesLink = tag->strSeriesLink == nullptr ? "" : tag->strSeriesLink; - m_firstAired = tag->strFirstAired == nullptr ? "" : tag->strFirstAired; + FreeString(target->strTitle); + FreeString(target->strPlotOutline); + FreeString(target->strPlot); + FreeString(target->strOriginalTitle); + FreeString(target->strCast); + FreeString(target->strDirector); + FreeString(target->strWriter); + FreeString(target->strIMDBNumber); + FreeString(target->strIconPath); + FreeString(target->strGenreDescription); + FreeString(target->strParentalRatingCode); + FreeString(target->strEpisodeName); + FreeString(target->strSeriesLink); + FreeString(target->strFirstAired); } + +private: + PVREPGTag(const EPG_TAG* epg) : DynamicCStructHdl(epg) {} + PVREPGTag(EPG_TAG* epg) : DynamicCStructHdl(epg) {} }; ///@} //------------------------------------------------------------------------------ @@ -497,7 +494,7 @@ class PVREPGTagsResultSet /// @param[in] tag The to transferred data. void Add(const kodi::addon::PVREPGTag& tag) { - m_instance->toKodi->TransferEpgEntry(m_instance->toKodi->kodiInstance, m_handle, tag.GetTag()); + m_instance->toKodi->TransferEpgEntry(m_instance->toKodi->kodiInstance, m_handle, tag); } ///@} From 38eadc167fecc5d60bc81a12059ead9ff2993e98 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:24:48 +0200 Subject: [PATCH 324/651] [addons][PVR] PVR add-on API: PVR_ATTRIBUTE_INT_VALUE: Replace fixed size char arrays with dynamic arrays. --- .../include/kodi/addon-instance/PVR.h | 25 +++++- .../include/kodi/addon-instance/pvr/General.h | 75 ++++++++++++---- .../include/kodi/addon-instance/pvr/Timers.h | 88 +++++++++---------- .../include/kodi/c-api/addon-instance/pvr.h | 11 ++- .../c-api/addon-instance/pvr/pvr_defines.h | 3 +- xbmc/pvr/addons/PVRClient.cpp | 25 ++++-- xbmc/pvr/timers/PVRTimerType.cpp | 51 ++++++----- 7 files changed, 181 insertions(+), 97 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h index 41f4fba5dbc61..6be0d5e8e330c 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h @@ -2825,6 +2825,9 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance instance->pvr->toAddon->SetSpeed = ADDON_SetSpeed; instance->pvr->toAddon->FillBuffer = ADDON_FillBuffer; instance->pvr->toAddon->GetStreamTimes = ADDON_GetStreamTimes; + //--==----==----==----==----==----==----==----==----==----==----==----==----== + instance->pvr->toAddon->FreeCapabilities = ADDON_FreeCapabilities; + instance->pvr->toAddon->FreeTimerTypes = ADDON_FreeTimerTypes; m_instanceData = instance->pvr; m_instanceData->toAddon->addonInstance = this; @@ -2838,6 +2841,13 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance ->GetCapabilities(cppCapabilities); } + inline static PVR_ERROR ADDON_FreeCapabilities(const AddonInstance_PVR* instance, + PVR_ADDON_CAPABILITIES* capabilities) + { + PVRCapabilities::FreeResources(capabilities); + return PVR_ERROR_NO_ERROR; + } + inline static PVR_ERROR ADDON_GetBackendName(const AddonInstance_PVR* instance, char* str, int memSize) @@ -3299,7 +3309,7 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance inline static PVR_ERROR ADDON_GetTimerTypes(const AddonInstance_PVR* instance, PVR_TIMER_TYPE* types, - int* typesCount) + unsigned int* typesCount) { *typesCount = 0; std::vector timerTypes; @@ -3310,6 +3320,7 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance for (const auto& timerType : timerTypes) { types[*typesCount] = *timerType; + PVRTimerType::AllocResources(timerType, &types[*typesCount]); ++*typesCount; if (*typesCount >= PVR_ADDON_TIMERTYPE_ARRAY_SIZE) break; @@ -3318,6 +3329,18 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance return error; } + inline static PVR_ERROR ADDON_FreeTimerTypes(const AddonInstance_PVR* instance, + PVR_TIMER_TYPE* types, + unsigned int typesCount) + { + for (unsigned int i = 0; i < typesCount; ++i) + { + PVR_TIMER_TYPE* type = &types[i]; + PVRTimerType::FreeResources(type); + } + return PVR_ERROR_NO_ERROR; + } + inline static PVR_ERROR ADDON_GetTimersAmount(const AddonInstance_PVR* instance, int* amount) { return static_cast(instance->toAddon->addonInstance) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h index 9009b767054e4..250b23c0791eb 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h @@ -31,13 +31,13 @@ namespace addon /// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeIntValue_Help /// ///@{ -class PVRTypeIntValue : public CStructHdl +class PVRTypeIntValue : public DynamicCStructHdl { friend class CInstancePVRClient; public: /*! \cond PRIVATE */ - PVRTypeIntValue(const PVRTypeIntValue& data) : CStructHdl(data) {} + PVRTypeIntValue(const PVRTypeIntValue& data) : DynamicCStructHdl(data) {} /*! \endcond */ /// @defgroup cpp_kodi_addon_pvr_Defs_PVRTypeIntValue_Help Value Help @@ -78,17 +78,56 @@ class PVRTypeIntValue : public CStructHdlstrDescription, description.c_str(), - sizeof(m_cStructure->strDescription) - 1); + ReallocAndCopyString(&m_cStructure->strDescription, description.c_str()); } /// @brief To get with the description text of the value. std::string GetDescription() const { return m_cStructure->strDescription; } ///@} + static void CopyData(const std::vector& source, + PVR_ATTRIBUTE_INT_VALUE* values, + unsigned int size) + { + for (unsigned int i = 0; i < size; ++i) + { + values[i].iValue = source[i].GetCStructure()->iValue; + values[i].strDescription = AllocAndCopyString(source[i].GetCStructure()->strDescription); + } + } + + static void AllocResources(const PVR_ATTRIBUTE_INT_VALUE* source, PVR_ATTRIBUTE_INT_VALUE* target) + { + target->strDescription = AllocAndCopyString(source->strDescription); + } + + static void FreeResources(PVR_ATTRIBUTE_INT_VALUE* target) + { + FreeString(target->strDescription); + target->strDescription = nullptr; + } + + static void AllocResources(const PVR_ATTRIBUTE_INT_VALUE* source, + PVR_ATTRIBUTE_INT_VALUE* values, + unsigned int size) + { + for (unsigned int i = 0; i < size; ++i) + { + AllocResources(&source[i], &values[i]); + } + } + + static void FreeResources(PVR_ATTRIBUTE_INT_VALUE* values, unsigned int size) + { + for (unsigned int i = 0; i < size; ++i) + { + FreeResources(&values[i]); + } + } + private: - PVRTypeIntValue(const PVR_ATTRIBUTE_INT_VALUE* data) : CStructHdl(data) {} - PVRTypeIntValue(PVR_ATTRIBUTE_INT_VALUE* data) : CStructHdl(data) {} + PVRTypeIntValue(const PVR_ATTRIBUTE_INT_VALUE* data) : DynamicCStructHdl(data) {} + PVRTypeIntValue(PVR_ATTRIBUTE_INT_VALUE* data) : DynamicCStructHdl(data) {} }; ///@} //------------------------------------------------------------------------------ @@ -384,18 +423,12 @@ class PVRCapabilities /// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeIntValue_Help void SetRecordingsLifetimeValues(const std::vector& recordingsLifetimeValues) { - m_capabilities->iRecordingsLifetimesSize = 0; - for (unsigned int i = 0; i < recordingsLifetimeValues.size() && - i < sizeof(m_capabilities->recordingsLifetimeValues); - ++i) - { - m_capabilities->recordingsLifetimeValues[i].iValue = - recordingsLifetimeValues[i].GetCStructure()->iValue; - strncpy(m_capabilities->recordingsLifetimeValues[i].strDescription, - recordingsLifetimeValues[i].GetCStructure()->strDescription, - sizeof(m_capabilities->recordingsLifetimeValues[i].strDescription) - 1); - ++m_capabilities->iRecordingsLifetimesSize; - } + PVRTypeIntValue::FreeResources(m_capabilities->recordingsLifetimeValues, + m_capabilities->iRecordingsLifetimesSize); + m_capabilities->iRecordingsLifetimesSize = + static_cast(recordingsLifetimeValues.size()); + PVRTypeIntValue::CopyData(recordingsLifetimeValues, m_capabilities->recordingsLifetimeValues, + m_capabilities->iRecordingsLifetimesSize); } /// @brief To get with @ref SetRecordingsLifetimeValues changed values. @@ -410,6 +443,12 @@ class PVRCapabilities } ///@} + static void FreeResources(PVR_ADDON_CAPABILITIES* capabilities) + { + PVRTypeIntValue::FreeResources(capabilities->recordingsLifetimeValues, + capabilities->iRecordingsLifetimesSize); + } + private: PVRCapabilities(PVR_ADDON_CAPABILITIES* capabilities) : m_capabilities(capabilities) {} diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h index 3042a877bd3e8..f0f47bb2c83e5 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h @@ -542,7 +542,7 @@ class PVRTimersResultSet /// @copydetails cpp_kodi_addon_pvr_Defs_Timer_PVRTimerType_Help /// ///@{ -class PVRTimerType : public CStructHdl +class PVRTimerType : public DynamicCStructHdl { friend class CInstancePVRClient; @@ -557,7 +557,7 @@ class PVRTimerType : public CStructHdl m_cStructure->iRecordingGroupDefault = -1; m_cStructure->iMaxRecordingsDefault = -1; } - PVRTimerType(const PVRTimerType& type) : CStructHdl(type) {} + PVRTimerType(const PVRTimerType& type) : DynamicCStructHdl(type) {} /*! \endcond */ /// @defgroup cpp_kodi_addon_pvr_Defs_Timer_PVRTimerType_Help Value Help @@ -647,15 +647,9 @@ class PVRTimerType : public CStructHdl /// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeIntValue_Help void SetPriorities(const std::vector& priorities, int prioritiesDefault = -1) { + PVRTypeIntValue::FreeResources(m_cStructure->priorities, m_cStructure->iPrioritiesSize); m_cStructure->iPrioritiesSize = static_cast(priorities.size()); - for (unsigned int i = 0; - i < m_cStructure->iPrioritiesSize && i < sizeof(m_cStructure->priorities); ++i) - { - m_cStructure->priorities[i].iValue = priorities[i].GetCStructure()->iValue; - strncpy(m_cStructure->priorities[i].strDescription, - priorities[i].GetCStructure()->strDescription, - sizeof(m_cStructure->priorities[i].strDescription) - 1); - } + PVRTypeIntValue::CopyData(priorities, m_cStructure->priorities, m_cStructure->iPrioritiesSize); if (prioritiesDefault != -1) m_cStructure->iPrioritiesDefault = prioritiesDefault; } @@ -699,15 +693,9 @@ class PVRTimerType : public CStructHdl /// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeIntValue_Help void SetLifetimes(const std::vector& lifetimes, int lifetimesDefault = -1) { + PVRTypeIntValue::FreeResources(m_cStructure->lifetimes, m_cStructure->iLifetimesSize); m_cStructure->iLifetimesSize = static_cast(lifetimes.size()); - for (unsigned int i = 0; - i < m_cStructure->iLifetimesSize && i < sizeof(m_cStructure->lifetimes); ++i) - { - m_cStructure->lifetimes[i].iValue = lifetimes[i].GetCStructure()->iValue; - strncpy(m_cStructure->lifetimes[i].strDescription, - lifetimes[i].GetCStructure()->strDescription, - sizeof(m_cStructure->lifetimes[i].strDescription) - 1); - } + PVRTypeIntValue::CopyData(lifetimes, m_cStructure->lifetimes, m_cStructure->iLifetimesSize); if (lifetimesDefault != -1) m_cStructure->iLifetimesDefault = lifetimesDefault; } @@ -754,18 +742,12 @@ class PVRTimerType : public CStructHdl void SetPreventDuplicateEpisodes(const std::vector& preventDuplicateEpisodes, int preventDuplicateEpisodesDefault = -1) { + PVRTypeIntValue::FreeResources(m_cStructure->preventDuplicateEpisodes, + m_cStructure->iPreventDuplicateEpisodesSize); m_cStructure->iPreventDuplicateEpisodesSize = static_cast(preventDuplicateEpisodes.size()); - for (unsigned int i = 0; i < m_cStructure->iPreventDuplicateEpisodesSize && - i < sizeof(m_cStructure->preventDuplicateEpisodes); - ++i) - { - m_cStructure->preventDuplicateEpisodes[i].iValue = - preventDuplicateEpisodes[i].GetCStructure()->iValue; - strncpy(m_cStructure->preventDuplicateEpisodes[i].strDescription, - preventDuplicateEpisodes[i].GetCStructure()->strDescription, - sizeof(m_cStructure->preventDuplicateEpisodes[i].strDescription) - 1); - } + PVRTypeIntValue::CopyData(preventDuplicateEpisodes, m_cStructure->preventDuplicateEpisodes, + m_cStructure->iPreventDuplicateEpisodesSize); if (preventDuplicateEpisodesDefault != -1) m_cStructure->iPreventDuplicateEpisodesDefault = preventDuplicateEpisodesDefault; } @@ -811,15 +793,10 @@ class PVRTimerType : public CStructHdl void SetRecordingGroups(const std::vector& recordingGroup, int recordingGroupDefault = -1) { + PVRTypeIntValue::FreeResources(m_cStructure->recordingGroup, m_cStructure->iRecordingGroupSize); m_cStructure->iRecordingGroupSize = static_cast(recordingGroup.size()); - for (unsigned int i = 0; - i < m_cStructure->iRecordingGroupSize && i < sizeof(m_cStructure->recordingGroup); ++i) - { - m_cStructure->recordingGroup[i].iValue = recordingGroup[i].GetCStructure()->iValue; - strncpy(m_cStructure->recordingGroup[i].strDescription, - recordingGroup[i].GetCStructure()->strDescription, - sizeof(m_cStructure->recordingGroup[i].strDescription) - 1); - } + PVRTypeIntValue::CopyData(recordingGroup, m_cStructure->recordingGroup, + m_cStructure->iRecordingGroupSize); if (recordingGroupDefault != -1) m_cStructure->iRecordingGroupDefault = recordingGroupDefault; } @@ -862,15 +839,10 @@ class PVRTimerType : public CStructHdl void SetMaxRecordings(const std::vector& maxRecordings, int maxRecordingsDefault = -1) { + PVRTypeIntValue::FreeResources(m_cStructure->maxRecordings, m_cStructure->iMaxRecordingsSize); m_cStructure->iMaxRecordingsSize = static_cast(maxRecordings.size()); - for (unsigned int i = 0; - i < m_cStructure->iMaxRecordingsSize && i < sizeof(m_cStructure->maxRecordings); ++i) - { - m_cStructure->maxRecordings[i].iValue = maxRecordings[i].GetCStructure()->iValue; - strncpy(m_cStructure->maxRecordings[i].strDescription, - maxRecordings[i].GetCStructure()->strDescription, - sizeof(m_cStructure->maxRecordings[i].strDescription) - 1); - } + PVRTypeIntValue::CopyData(maxRecordings, m_cStructure->maxRecordings, + m_cStructure->iMaxRecordingsSize); if (maxRecordingsDefault != -1) m_cStructure->iMaxRecordingsDefault = maxRecordingsDefault; } @@ -898,9 +870,33 @@ class PVRTimerType : public CStructHdl int GetMaxRecordingsDefault() const { return m_cStructure->iMaxRecordingsDefault; } ///@} + static void AllocResources(const PVR_TIMER_TYPE* source, PVR_TIMER_TYPE* target) + { + PVRTypeIntValue::AllocResources(source->priorities, target->priorities, + target->iPrioritiesSize); + PVRTypeIntValue::AllocResources(source->lifetimes, target->lifetimes, target->iLifetimesSize); + PVRTypeIntValue::AllocResources(source->preventDuplicateEpisodes, + target->preventDuplicateEpisodes, + target->iPreventDuplicateEpisodesSize); + PVRTypeIntValue::AllocResources(source->recordingGroup, target->recordingGroup, + target->iRecordingGroupSize); + PVRTypeIntValue::AllocResources(source->maxRecordings, target->maxRecordings, + target->iMaxRecordingsSize); + } + + static void FreeResources(PVR_TIMER_TYPE* target) + { + PVRTypeIntValue::FreeResources(target->priorities, target->iPrioritiesSize); + PVRTypeIntValue::FreeResources(target->lifetimes, target->iLifetimesSize); + PVRTypeIntValue::FreeResources(target->preventDuplicateEpisodes, + target->iPreventDuplicateEpisodesSize); + PVRTypeIntValue::FreeResources(target->recordingGroup, target->iRecordingGroupSize); + PVRTypeIntValue::FreeResources(target->maxRecordings, target->iMaxRecordingsSize); + } + private: - PVRTimerType(const PVR_TIMER_TYPE* type) : CStructHdl(type) {} - PVRTimerType(PVR_TIMER_TYPE* type) : CStructHdl(type) {} + PVRTimerType(const PVR_TIMER_TYPE* type) : DynamicCStructHdl(type) {} + PVRTimerType(PVR_TIMER_TYPE* type) : DynamicCStructHdl(type) {} }; ///@} //------------------------------------------------------------------------------ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h index 96e1eea9710a7..e0c1a96effabb 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h @@ -159,7 +159,6 @@ extern "C" //--==----==----==----==----==----==----==----==----==----==----==----==----== // Channel interface functions - enum PVR_ERROR(__cdecl* GetChannelsAmount)(const struct AddonInstance_PVR*, int*); enum PVR_ERROR(__cdecl* GetChannels)(const struct AddonInstance_PVR*, PVR_HANDLE, bool); enum PVR_ERROR(__cdecl* GetChannelStreamProperties)(const struct AddonInstance_PVR*, @@ -267,7 +266,7 @@ extern "C" // Timer interface functions enum PVR_ERROR(__cdecl* GetTimerTypes)(const struct AddonInstance_PVR*, struct PVR_TIMER_TYPE[], - int*); + unsigned int*); enum PVR_ERROR(__cdecl* GetTimersAmount)(const struct AddonInstance_PVR*, int*); enum PVR_ERROR(__cdecl* GetTimers)(const struct AddonInstance_PVR*, PVR_HANDLE); enum PVR_ERROR(__cdecl* AddTimer)(const struct AddonInstance_PVR*, const struct PVR_TIMER*); @@ -324,6 +323,14 @@ extern "C" struct PVR_STREAM_TIMES*); enum PVR_ERROR(__cdecl* GetStreamReadChunkSize)(const struct AddonInstance_PVR*, int*); + //--==----==----==----==----==----==----==----==----==----==----==----==----== + // Resource deallocation interface functions + enum PVR_ERROR(__cdecl* FreeCapabilities)(const struct AddonInstance_PVR*, + struct PVR_ADDON_CAPABILITIES*); + enum PVR_ERROR(__cdecl* FreeTimerTypes)(const struct AddonInstance_PVR*, + struct PVR_TIMER_TYPE*, + unsigned int); + //--==----==----==----==----==----==----==----==----==----==----==----==----== // New functions becomes added below and can be on another API change (where // breaks min API version) moved up. diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h index bfc154efdc16b..fbef889604a17 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h @@ -30,7 +30,6 @@ extern "C" #define PVR_ADDON_TIMERTYPE_VALUES_ARRAY_SIZE 512 #define PVR_ADDON_TIMERTYPE_VALUES_ARRAY_SIZE_SMALL 128 #define PVR_ADDON_TIMERTYPE_STRING_LENGTH 128 -#define PVR_ADDON_ATTRIBUTE_DESC_LENGTH 128 #define PVR_ADDON_ATTRIBUTE_VALUES_ARRAY_SIZE 512 #define PVR_ADDON_DESCRAMBLE_INFO_STRING_LENGTH 64 ///@} @@ -41,7 +40,7 @@ extern "C" typedef struct PVR_ATTRIBUTE_INT_VALUE { int iValue; - char strDescription[PVR_ADDON_ATTRIBUTE_DESC_LENGTH]; + const char* strDescription; } PVR_ATTRIBUTE_INT_VALUE; /*! diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index d3315cc17c1c3..518d6f1025ec1 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -494,13 +494,20 @@ bool CPVRClient::GetAddonProperties() }, true, false); - if (retVal != PVR_ERROR_NO_ERROR) - return false; + if (retVal == PVR_ERROR_NO_ERROR) + { + std::unique_lock lock(m_critSection); + m_clientCapabilities = addonCapabilities; + } - std::unique_lock lock(m_critSection); - m_clientCapabilities = addonCapabilities; + /* free the resources of the capabilities instance */ + DoAddonCall( + __func__, + [&addonCapabilities](const AddonInstance* addon) + { return addon->toAddon->FreeCapabilities(addon, &addonCapabilities); }, + true, false); - return true; + return (retVal == PVR_ERROR_NO_ERROR); } bool CPVRClient::GetAddonNameStringProperties() @@ -1210,7 +1217,7 @@ PVR_ERROR CPVRClient::UpdateTimerTypes() [this, &timerTypes](const AddonInstance* addon) { std::unique_ptr types_array( new PVR_TIMER_TYPE[PVR_ADDON_TIMERTYPE_ARRAY_SIZE]); - int size = PVR_ADDON_TIMERTYPE_ARRAY_SIZE; + unsigned int size = PVR_ADDON_TIMERTYPE_ARRAY_SIZE; PVR_ERROR retval = addon->toAddon->GetTimerTypes(addon, types_array.get(), &size); @@ -1272,7 +1279,7 @@ PVR_ERROR CPVRClient::UpdateTimerTypes() if (retval == PVR_ERROR_NO_ERROR) { timerTypes.reserve(size); - for (int i = 0; i < size; ++i) + for (unsigned int i = 0; i < size; ++i) { if (types_array[i].iId == PVR_TIMER_TYPE_NONE) { @@ -1282,6 +1289,10 @@ PVR_ERROR CPVRClient::UpdateTimerTypes() timerTypes.emplace_back(std::make_shared(types_array[i], m_iClientId)); } } + + /* free the resources of the timer types array */ + addon->toAddon->FreeTimerTypes(addon, types_array.get(), size); + return retval; }, m_clientCapabilities.SupportsTimers(), false); diff --git a/xbmc/pvr/timers/PVRTimerType.cpp b/xbmc/pvr/timers/PVRTimerType.cpp index b678071ff9fa7..9a68e49305cab 100644 --- a/xbmc/pvr/timers/PVRTimerType.cpp +++ b/xbmc/pvr/timers/PVRTimerType.cpp @@ -172,11 +172,11 @@ CPVRTimerType::CPVRTimerType() : { } -CPVRTimerType::CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId) : - m_iClientId(iClientId), - m_iTypeId(type.iId), - m_iAttributes(type.iAttributes), - m_strDescription(type.strDescription) +CPVRTimerType::CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId) + : m_iClientId(iClientId), + m_iTypeId(type.iId), + m_iAttributes(type.iAttributes), + m_strDescription(type.strDescription ? type.strDescription : "") { InitDescription(); InitAttributeValues(type); @@ -277,13 +277,15 @@ void CPVRTimerType::InitPriorityValues(const PVR_TIMER_TYPE& type) { for (unsigned int i = 0; i < type.iPrioritiesSize; ++i) { - std::string strDescr(type.priorities[i].strDescription); + const int value{type.priorities[i].iValue}; + const char* desc{type.priorities[i].strDescription}; + std::string strDescr{desc ? desc : ""}; if (strDescr.empty()) { // No description given by addon. Create one from value. - strDescr = std::to_string(type.priorities[i].iValue); + strDescr = std::to_string(value); } - m_priorityValues.emplace_back(strDescr, type.priorities[i].iValue); + m_priorityValues.emplace_back(strDescr, value); } m_iPriorityDefault = type.iPrioritiesDefault; @@ -314,14 +316,15 @@ void CPVRTimerType::InitLifetimeValues(const PVR_TIMER_TYPE& type) { for (unsigned int i = 0; i < type.iLifetimesSize; ++i) { - int iValue = type.lifetimes[i].iValue; - std::string strDescr(type.lifetimes[i].strDescription); + const int value{type.lifetimes[i].iValue}; + const char* desc{type.lifetimes[i].strDescription}; + std::string strDescr{desc ? desc : ""}; if (strDescr.empty()) { // No description given by addon. Create one from value. - strDescr = std::to_string(iValue); + strDescr = std::to_string(value); } - m_lifetimeValues.emplace_back(strDescr, iValue); + m_lifetimeValues.emplace_back(strDescr, value); } m_iLifetimeDefault = type.iLifetimesDefault; @@ -354,13 +357,15 @@ void CPVRTimerType::InitMaxRecordingsValues(const PVR_TIMER_TYPE& type) { for (unsigned int i = 0; i < type.iMaxRecordingsSize; ++i) { - std::string strDescr(type.maxRecordings[i].strDescription); + const int value{type.maxRecordings[i].iValue}; + const char* desc{type.maxRecordings[i].strDescription}; + std::string strDescr{desc ? desc : ""}; if (strDescr.empty()) { // No description given by addon. Create one from value. - strDescr = std::to_string(type.maxRecordings[i].iValue); + strDescr = std::to_string(value); } - m_maxRecordingsValues.emplace_back(strDescr, type.maxRecordings[i].iValue); + m_maxRecordingsValues.emplace_back(strDescr, value); } m_iMaxRecordingsDefault = type.iMaxRecordingsDefault; @@ -378,13 +383,15 @@ void CPVRTimerType::InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& typ { for (unsigned int i = 0; i < type.iPreventDuplicateEpisodesSize; ++i) { - std::string strDescr(type.preventDuplicateEpisodes[i].strDescription); + const int value{type.preventDuplicateEpisodes[i].iValue}; + const char* desc{type.preventDuplicateEpisodes[i].strDescription}; + std::string strDescr{desc ? desc : ""}; if (strDescr.empty()) { // No description given by addon. Create one from value. - strDescr = std::to_string(type.preventDuplicateEpisodes[i].iValue); + strDescr = std::to_string(value); } - m_preventDupEpisodesValues.emplace_back(strDescr, type.preventDuplicateEpisodes[i].iValue); + m_preventDupEpisodesValues.emplace_back(strDescr, value); } m_iPreventDupEpisodesDefault = type.iPreventDuplicateEpisodesDefault; @@ -415,15 +422,17 @@ void CPVRTimerType::InitRecordingGroupValues(const PVR_TIMER_TYPE& type) { for (unsigned int i = 0; i < type.iRecordingGroupSize; ++i) { - std::string strDescr(type.recordingGroup[i].strDescription); + const int value{type.recordingGroup[i].iValue}; + const char* desc{type.recordingGroup[i].strDescription}; + std::string strDescr{desc ? desc : ""}; if (strDescr.empty()) { // No description given by addon. Create one from value. strDescr = StringUtils::Format("{} {}", g_localizeStrings.Get(811), // Recording group - type.recordingGroup[i].iValue); + value); } - m_recordingGroupValues.emplace_back(strDescr, type.recordingGroup[i].iValue); + m_recordingGroupValues.emplace_back(strDescr, value); } m_iRecordingGroupDefault = type.iRecordingGroupDefault; From 70caf5b344e3cfdfc4008e98a37cfe99d4fa2d61 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:28:07 +0200 Subject: [PATCH 325/651] [addons][PVR] PVR add-on API: PVR_NAMED_VALUE: Replace fixed size char arrays with dynamic arrays. --- .../kodi-dev-kit/include/kodi/AddonBase.h | 46 +++++++++++++++ .../include/kodi/addon-instance/PVR.h | 57 +++++++------------ .../include/kodi/addon-instance/pvr/General.h | 24 ++++++-- .../include/kodi/c-api/addon-instance/pvr.h | 9 ++- .../c-api/addon-instance/pvr/pvr_defines.h | 4 +- xbmc/pvr/addons/PVRClient.cpp | 46 +++++++-------- xbmc/pvr/addons/PVRClient.h | 4 +- 7 files changed, 117 insertions(+), 73 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h index 33ef08ae55afd..25bbcd8a4bc46 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h @@ -108,6 +108,46 @@ inline void ATTR_DLL_LOCAL ReallocAndCopyString(const char** stringToRealloc, *stringToRealloc = AllocAndCopyString(stringToCopy); } +/*! + * @brief Internally used helper to convert c-struct data contained in a vector to an array of c-struct pointers. + */ +template +inline static ATTR_DLL_LOCAL C_STRUCT** AllocAndCopyPointerArray( + std::vector& sourceVector, unsigned int& targetArraySize) +{ + targetArraySize = static_cast(sourceVector.size()); + if (targetArraySize > 0) + { + C_STRUCT** targetArray = new C_STRUCT* [targetArraySize] {}; + + unsigned int i{0}; + for (auto& entry : sourceVector) + { + // Assign data to target array. Take pointer ownership. + C_STRUCT** arrayElem{&targetArray[i]}; + *arrayElem = entry.release(); + ++i; + } + return targetArray; + } + return nullptr; +} + +/*! + * @brief Internally used helper to free an array of of c-struct pointers. + */ +template +inline static void FreeDynamicPointerArray(C_STRUCT** targetArray, unsigned int targetArraySize) +{ + for (unsigned int i = 0; i < targetArraySize; ++i) + { + C_STRUCT** arrayElem{&targetArray[i]}; + CPP_CLASS::FreeResources(*arrayElem); + delete *arrayElem; + } + delete[] targetArray; +} + /* * Internally used helper class to manage processing of a "C" structure in "CPP" * class. @@ -299,6 +339,12 @@ class DynamicCStructHdl const C_STRUCT* GetCStructure() const { return m_cStructure; } + C_STRUCT* release() + { + m_owner = false; + return m_cStructure; + } + protected: C_STRUCT* m_cStructure = nullptr; diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h index 6be0d5e8e330c..d51be6b336bc8 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h @@ -2828,6 +2828,7 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance //--==----==----==----==----==----==----==----==----==----==----==----==----== instance->pvr->toAddon->FreeCapabilities = ADDON_FreeCapabilities; instance->pvr->toAddon->FreeTimerTypes = ADDON_FreeTimerTypes; + instance->pvr->toAddon->FreeProperties = ADDON_FreeProperties; m_instanceData = instance->pvr; m_instanceData->toAddon->addonInstance = this; @@ -2930,25 +2931,17 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance inline static PVR_ERROR ADDON_GetChannelStreamProperties(const AddonInstance_PVR* instance, const PVR_CHANNEL* channel, - PVR_NAMED_VALUE* properties, + PVR_NAMED_VALUE*** properties, unsigned int* propertiesCount) { *propertiesCount = 0; std::vector propertiesList; PVR_ERROR error = static_cast(instance->toAddon->addonInstance) ->GetChannelStreamProperties(channel, propertiesList); - if (error == PVR_ERROR_NO_ERROR) + if (error == PVR_ERROR_NO_ERROR && !propertiesList.empty()) { - for (const auto& property : propertiesList) - { - strncpy(properties[*propertiesCount].strName, property.GetCStructure()->strName, - sizeof(properties[*propertiesCount].strName) - 1); - strncpy(properties[*propertiesCount].strValue, property.GetCStructure()->strValue, - sizeof(properties[*propertiesCount].strValue) - 1); - ++*propertiesCount; - if (*propertiesCount > STREAM_MAX_PROPERTY_COUNT) - break; - } + *properties = AllocAndCopyPointerArray(propertiesList, + *propertiesCount); } return error; } @@ -3115,25 +3108,17 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance inline static PVR_ERROR ADDON_GetEPGTagStreamProperties(const AddonInstance_PVR* instance, const EPG_TAG* tag, - PVR_NAMED_VALUE* properties, + PVR_NAMED_VALUE*** properties, unsigned int* propertiesCount) { *propertiesCount = 0; std::vector propertiesList; PVR_ERROR error = static_cast(instance->toAddon->addonInstance) ->GetEPGTagStreamProperties(tag, propertiesList); - if (error == PVR_ERROR_NO_ERROR) + if (error == PVR_ERROR_NO_ERROR && !propertiesList.empty()) { - for (const auto& property : propertiesList) - { - strncpy(properties[*propertiesCount].strName, property.GetCStructure()->strName, - sizeof(properties[*propertiesCount].strName) - 1); - strncpy(properties[*propertiesCount].strValue, property.GetCStructure()->strValue, - sizeof(properties[*propertiesCount].strValue) - 1); - ++*propertiesCount; - if (*propertiesCount > STREAM_MAX_PROPERTY_COUNT) - break; - } + *properties = AllocAndCopyPointerArray(propertiesList, + *propertiesCount); } return error; } @@ -3274,29 +3259,29 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance inline static PVR_ERROR ADDON_GetRecordingStreamProperties(const AddonInstance_PVR* instance, const PVR_RECORDING* recording, - PVR_NAMED_VALUE* properties, + PVR_NAMED_VALUE*** properties, unsigned int* propertiesCount) { *propertiesCount = 0; std::vector propertiesList; PVR_ERROR error = static_cast(instance->toAddon->addonInstance) ->GetRecordingStreamProperties(recording, propertiesList); - if (error == PVR_ERROR_NO_ERROR) + if (error == PVR_ERROR_NO_ERROR && !propertiesList.empty()) { - for (const auto& property : propertiesList) - { - strncpy(properties[*propertiesCount].strName, property.GetCStructure()->strName, - sizeof(properties[*propertiesCount].strName) - 1); - strncpy(properties[*propertiesCount].strValue, property.GetCStructure()->strValue, - sizeof(properties[*propertiesCount].strValue) - 1); - ++*propertiesCount; - if (*propertiesCount > STREAM_MAX_PROPERTY_COUNT) - break; - } + *properties = AllocAndCopyPointerArray(propertiesList, + *propertiesCount); } return error; } + inline static PVR_ERROR ADDON_FreeProperties(const AddonInstance_PVR* instance, + PVR_NAMED_VALUE** properties, + unsigned int propertiesCount) + { + FreeDynamicPointerArray(properties, propertiesCount); + return PVR_ERROR_NO_ERROR; + } + inline static PVR_ERROR ADDON_CallRecordingMenuHook(const AddonInstance_PVR* instance, const PVR_MENUHOOK* menuhook, const PVR_RECORDING* recording) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h index 250b23c0791eb..cbc31762999c0 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h @@ -504,13 +504,13 @@ class PVRCapabilities /// ~~~~~~~~~~~~~ /// ///@{ -class PVRStreamProperty : public CStructHdl +class PVRStreamProperty : public DynamicCStructHdl { friend class CInstancePVRClient; public: /*! \cond PRIVATE */ - PVRStreamProperty(const PVRStreamProperty& data) : CStructHdl(data) {} + PVRStreamProperty(const PVRStreamProperty& property) : DynamicCStructHdl(property) {} /*! \endcond */ /// @defgroup cpp_kodi_addon_pvr_Defs_General_Inputstream_PVRStreamProperty_Help Value Help @@ -545,7 +545,7 @@ class PVRStreamProperty : public CStructHdl /// @brief To set with the identification name. void SetName(const std::string& name) { - strncpy(m_cStructure->strName, name.c_str(), sizeof(m_cStructure->strName) - 1); + ReallocAndCopyString(&m_cStructure->strName, name.c_str()); } /// @brief To get with the identification name. @@ -554,16 +554,28 @@ class PVRStreamProperty : public CStructHdl /// @brief To set with the used property value. void SetValue(const std::string& value) { - strncpy(m_cStructure->strValue, value.c_str(), sizeof(m_cStructure->strValue) - 1); + ReallocAndCopyString(&m_cStructure->strValue, value.c_str()); } /// @brief To get with the used property value. std::string GetValue() const { return m_cStructure->strValue; } ///@} + static void AllocResources(const PVR_NAMED_VALUE* source, PVR_NAMED_VALUE* target) + { + target->strName = AllocAndCopyString(source->strName); + target->strValue = AllocAndCopyString(source->strValue); + } + + static void FreeResources(PVR_NAMED_VALUE* target) + { + FreeString(target->strName); + FreeString(target->strValue); + } + private: - PVRStreamProperty(const PVR_NAMED_VALUE* data) : CStructHdl(data) {} - PVRStreamProperty(PVR_NAMED_VALUE* data) : CStructHdl(data) {} + PVRStreamProperty(const PVR_NAMED_VALUE* property) : DynamicCStructHdl(property) {} + PVRStreamProperty(PVR_NAMED_VALUE* property) : DynamicCStructHdl(property) {} }; ///@} //------------------------------------------------------------------------------ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h index e0c1a96effabb..6c21f0b543f3a 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h @@ -163,7 +163,7 @@ extern "C" enum PVR_ERROR(__cdecl* GetChannels)(const struct AddonInstance_PVR*, PVR_HANDLE, bool); enum PVR_ERROR(__cdecl* GetChannelStreamProperties)(const struct AddonInstance_PVR*, const struct PVR_CHANNEL*, - struct PVR_NAMED_VALUE*, + struct PVR_NAMED_VALUE***, unsigned int*); enum PVR_ERROR(__cdecl* GetSignalStatus)(const struct AddonInstance_PVR*, int, @@ -217,7 +217,7 @@ extern "C" int*); enum PVR_ERROR(__cdecl* GetEPGTagStreamProperties)(const struct AddonInstance_PVR*, const struct EPG_TAG*, - struct PVR_NAMED_VALUE*, + struct PVR_NAMED_VALUE***, unsigned int*); enum PVR_ERROR(__cdecl* SetEPGMaxPastDays)(const struct AddonInstance_PVR*, int); enum PVR_ERROR(__cdecl* SetEPGMaxFutureDays)(const struct AddonInstance_PVR*, int); @@ -256,7 +256,7 @@ extern "C" int64_t*); enum PVR_ERROR(__cdecl* GetRecordingStreamProperties)(const struct AddonInstance_PVR*, const struct PVR_RECORDING*, - struct PVR_NAMED_VALUE*, + struct PVR_NAMED_VALUE***, unsigned int*); enum PVR_ERROR(__cdecl* CallRecordingMenuHook)(const struct AddonInstance_PVR*, const struct PVR_MENUHOOK*, @@ -330,6 +330,9 @@ extern "C" enum PVR_ERROR(__cdecl* FreeTimerTypes)(const struct AddonInstance_PVR*, struct PVR_TIMER_TYPE*, unsigned int); + enum PVR_ERROR(__cdecl* FreeProperties)(const struct AddonInstance_PVR*, + struct PVR_NAMED_VALUE**, + unsigned int); //--==----==----==----==----==----==----==----==----==----==----==----==----== // New functions becomes added below and can be on another API change (where diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h index fbef889604a17..5e7c7ecee0f5d 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h @@ -48,8 +48,8 @@ extern "C" */ typedef struct PVR_NAMED_VALUE { - char strName[PVR_ADDON_NAME_STRING_LENGTH]; - char strValue[PVR_ADDON_NAME_STRING_LENGTH]; + const char* strName; + const char* strValue; } PVR_NAMED_VALUE; /*! diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 518d6f1025ec1..a3f022356dd04 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -842,13 +842,14 @@ PVR_ERROR CPVRClient::IsPlayable(const std::shared_ptr& ta m_clientCapabilities.SupportsEPG()); } -void CPVRClient::WriteStreamProperties(const PVR_NAMED_VALUE* properties, +void CPVRClient::WriteStreamProperties(PVR_NAMED_VALUE** properties, unsigned int iPropertyCount, CPVRStreamProperties& props) { for (unsigned int i = 0; i < iPropertyCount; ++i) { - props.emplace_back(std::make_pair(properties[i].strName, properties[i].strValue)); + const PVR_NAMED_VALUE* prop{properties[i]}; + props.emplace_back(std::make_pair(prop->strName, prop->strValue)); } } @@ -858,15 +859,14 @@ PVR_ERROR CPVRClient::GetEpgTagStreamProperties(const std::shared_ptr properties(new PVR_NAMED_VALUE[iPropertyCount]); - memset(properties.get(), 0, iPropertyCount * sizeof(PVR_NAMED_VALUE)); - - PVR_ERROR error = addon->toAddon->GetEPGTagStreamProperties(addon, &addonTag, properties.get(), - &iPropertyCount); + PVR_NAMED_VALUE** property_array{nullptr}; + unsigned int size{0}; + const PVR_ERROR error{ + addon->toAddon->GetEPGTagStreamProperties(addon, &addonTag, &property_array, &size)}; if (error == PVR_ERROR_NO_ERROR) - WriteStreamProperties(properties.get(), iPropertyCount, props); + WriteStreamProperties(property_array, size, props); + addon->toAddon->FreeProperties(addon, property_array, size); return error; }); } @@ -1426,15 +1426,14 @@ PVR_ERROR CPVRClient::GetChannelStreamProperties(const std::shared_ptr properties(new PVR_NAMED_VALUE[iPropertyCount]); - memset(properties.get(), 0, iPropertyCount * sizeof(PVR_NAMED_VALUE)); - - const PVR_ERROR error{addon->toAddon->GetChannelStreamProperties( - addon, &addonChannel, properties.get(), &iPropertyCount)}; + PVR_NAMED_VALUE** property_array{nullptr}; + unsigned int size{0}; + const PVR_ERROR error{ + addon->toAddon->GetChannelStreamProperties(addon, &addonChannel, &property_array, &size)}; if (error == PVR_ERROR_NO_ERROR) - WriteStreamProperties(properties.get(), iPropertyCount, props); + WriteStreamProperties(property_array, size, props); + addon->toAddon->FreeProperties(addon, property_array, size); return error; }); } @@ -1446,17 +1445,16 @@ PVR_ERROR CPVRClient::GetRecordingStreamProperties( if (!m_clientCapabilities.SupportsRecordings()) return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon - const CAddonRecording tag(*recording); - - unsigned int iPropertyCount = STREAM_MAX_PROPERTY_COUNT; - std::unique_ptr properties(new PVR_NAMED_VALUE[iPropertyCount]); - memset(properties.get(), 0, iPropertyCount * sizeof(PVR_NAMED_VALUE)); + const CAddonRecording addonRecording(*recording); - PVR_ERROR error = addon->toAddon->GetRecordingStreamProperties(addon, &tag, properties.get(), - &iPropertyCount); + PVR_NAMED_VALUE** property_array{nullptr}; + unsigned int size{0}; + const PVR_ERROR error{addon->toAddon->GetRecordingStreamProperties(addon, &addonRecording, + &property_array, &size)}; if (error == PVR_ERROR_NO_ERROR) - WriteStreamProperties(properties.get(), iPropertyCount, props); + WriteStreamProperties(property_array, size, props); + addon->toAddon->FreeProperties(addon, property_array, size); return error; }); } diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h index 99b77c4458213..b33aed93109ce 100644 --- a/xbmc/pvr/addons/PVRClient.h +++ b/xbmc/pvr/addons/PVRClient.h @@ -831,11 +831,11 @@ class CPVRClient : public ADDON::IAddonInstanceHandler /*! * @brief Write the given addon properties to the given properties container. - * @param properties Pointer to an array of addon properties. + * @param properties Pointer to an array of addon properties pointers. * @param iPropertyCount The number of properties contained in the addon properties array. * @param props The container the addon properties shall be written to. */ - static void WriteStreamProperties(const PVR_NAMED_VALUE* properties, + static void WriteStreamProperties(PVR_NAMED_VALUE** properties, unsigned int iPropertyCount, CPVRStreamProperties& props); From 16b39e06010453577793e45a6ee975804dd34b65 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 19 Jul 2024 22:38:17 +0200 Subject: [PATCH 326/651] [addons][PVR] PVR add-on API: PVR_ADDON_CAPABILITIES: Replace fixed size PVR_ATTRIBUTE_INT_VALUE array with dynamic array. --- .../include/kodi/addon-instance/pvr/General.h | 138 ++++++++++-------- .../c-api/addon-instance/pvr/pvr_defines.h | 1 - .../c-api/addon-instance/pvr/pvr_general.h | 2 +- xbmc/pvr/addons/PVRClientCapabilities.cpp | 5 +- 4 files changed, 82 insertions(+), 64 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h index cbc31762999c0..d12c0f1326f7a 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h @@ -151,13 +151,14 @@ class PVRTypeIntValue : public DynamicCStructHdl { friend class CInstancePVRClient; public: /*! \cond PRIVATE */ - explicit PVRCapabilities() = delete; + PVRCapabilities() { memset(m_cStructure, 0, sizeof(PVR_ADDON_CAPABILITIES)); } + PVRCapabilities(const PVRCapabilities& capabilities) : DynamicCStructHdl(capabilities) {} /*! \endcond */ /// @defgroup cpp_kodi_addon_pvr_Defs_PVRCapabilities_Help Value Help @@ -198,56 +199,56 @@ class PVRCapabilities ///@{ /// @brief Set **true** if the add-on provides EPG information. - void SetSupportsEPG(bool supportsEPG) { m_capabilities->bSupportsEPG = supportsEPG; } + void SetSupportsEPG(bool supportsEPG) { m_cStructure->bSupportsEPG = supportsEPG; } /// @brief To get with @ref SetSupportsEPG changed values. - bool GetSupportsEPG() const { return m_capabilities->bSupportsEPG; } + bool GetSupportsEPG() const { return m_cStructure->bSupportsEPG; } /// @brief Set **true** if the backend supports retrieving an edit decision /// list for an EPG tag. - void SetSupportsEPGEdl(bool supportsEPGEdl) { m_capabilities->bSupportsEPGEdl = supportsEPGEdl; } + void SetSupportsEPGEdl(bool supportsEPGEdl) { m_cStructure->bSupportsEPGEdl = supportsEPGEdl; } /// @brief To get with @ref SetSupportsEPGEdl changed values. - bool GetSupportsEPGEdl() const { return m_capabilities->bSupportsEPGEdl; } + bool GetSupportsEPGEdl() const { return m_cStructure->bSupportsEPGEdl; } /// @brief Set **true** if this add-on provides TV channels. - void SetSupportsTV(bool supportsTV) { m_capabilities->bSupportsTV = supportsTV; } + void SetSupportsTV(bool supportsTV) { m_cStructure->bSupportsTV = supportsTV; } /// @brief To get with @ref SetSupportsTV changed values. - bool GetSupportsTV() const { return m_capabilities->bSupportsTV; } + bool GetSupportsTV() const { return m_cStructure->bSupportsTV; } /// @brief Set **true** if this add-on provides TV channels. - void SetSupportsRadio(bool supportsRadio) { m_capabilities->bSupportsRadio = supportsRadio; } + void SetSupportsRadio(bool supportsRadio) { m_cStructure->bSupportsRadio = supportsRadio; } /// @brief To get with @ref SetSupportsRadio changed values. - bool GetSupportsRadio() const { return m_capabilities->bSupportsRadio; } + bool GetSupportsRadio() const { return m_cStructure->bSupportsRadio; } /// @brief **true** if this add-on supports playback of recordings stored on /// the backend. void SetSupportsRecordings(bool supportsRecordings) { - m_capabilities->bSupportsRecordings = supportsRecordings; + m_cStructure->bSupportsRecordings = supportsRecordings; } /// @brief To get with @ref SetSupportsRecordings changed values. - bool GetSupportsRecordings() const { return m_capabilities->bSupportsRecordings; } + bool GetSupportsRecordings() const { return m_cStructure->bSupportsRecordings; } /// @brief Set **true** if this add-on supports undelete of recordings stored /// on the backend. void SetSupportsRecordingsUndelete(bool supportsRecordingsUndelete) { - m_capabilities->bSupportsRecordingsUndelete = supportsRecordingsUndelete; + m_cStructure->bSupportsRecordingsUndelete = supportsRecordingsUndelete; } /// @brief To get with @ref SetSupportsRecordings changed values. - bool GetSupportsRecordingsUndelete() const { return m_capabilities->bSupportsRecordingsUndelete; } + bool GetSupportsRecordingsUndelete() const { return m_cStructure->bSupportsRecordingsUndelete; } /// @brief Set **true** if this add-on supports the creation and editing of /// timers. - void SetSupportsTimers(bool supportsTimers) { m_capabilities->bSupportsTimers = supportsTimers; } + void SetSupportsTimers(bool supportsTimers) { m_cStructure->bSupportsTimers = supportsTimers; } /// @brief To get with @ref SetSupportsTimers changed values. - bool GetSupportsTimers() const { return m_capabilities->bSupportsTimers; } + bool GetSupportsTimers() const { return m_cStructure->bSupportsTimers; } /// @brief Set **true** if this add-on supports providers. /// @@ -256,11 +257,11 @@ class PVRCapabilities /// - @ref kodi::addon::CInstancePVRClient::GetProviders() void SetSupportsProviders(bool supportsProviders) { - m_capabilities->bSupportsProviders = supportsProviders; + m_cStructure->bSupportsProviders = supportsProviders; } /// @brief To get with @ref SetSupportsProviders changed values. - bool GetSupportsProviders() const { return m_capabilities->bSupportsProviders; } + bool GetSupportsProviders() const { return m_cStructure->bSupportsProviders; } /// @brief Set **true** if this add-on supports channel groups. /// @@ -270,11 +271,11 @@ class PVRCapabilities /// - @ref kodi::addon::CInstancePVRClient::GetChannelGroupMembers() void SetSupportsChannelGroups(bool supportsChannelGroups) { - m_capabilities->bSupportsChannelGroups = supportsChannelGroups; + m_cStructure->bSupportsChannelGroups = supportsChannelGroups; } /// @brief To get with @ref SetSupportsChannelGroups changed values. - bool GetSupportsChannelGroups() const { return m_capabilities->bSupportsChannelGroups; } + bool GetSupportsChannelGroups() const { return m_cStructure->bSupportsChannelGroups; } /// @brief Set **true** if this add-on support scanning for new channels on /// the backend. @@ -283,11 +284,11 @@ class PVRCapabilities /// - @ref kodi::addon::CInstancePVRClient::OpenDialogChannelScan() void SetSupportsChannelScan(bool supportsChannelScan) { - m_capabilities->bSupportsChannelScan = supportsChannelScan; + m_cStructure->bSupportsChannelScan = supportsChannelScan; } /// @brief To get with @ref SetSupportsChannelScan changed values. - bool GetSupportsChannelScan() const { return m_capabilities->bSupportsChannelScan; } + bool GetSupportsChannelScan() const { return m_cStructure->bSupportsChannelScan; } /// @brief Set **true** if this add-on supports channel edit. /// @@ -298,122 +299,122 @@ class PVRCapabilities /// - @ref kodi::addon::CInstancePVRClient::OpenDialogChannelAdd() void SetSupportsChannelSettings(bool supportsChannelSettings) { - m_capabilities->bSupportsChannelSettings = supportsChannelSettings; + m_cStructure->bSupportsChannelSettings = supportsChannelSettings; } /// @brief To get with @ref SetSupportsChannelSettings changed values. - bool GetSupportsChannelSettings() const { return m_capabilities->bSupportsChannelSettings; } + bool GetSupportsChannelSettings() const { return m_cStructure->bSupportsChannelSettings; } /// @brief Set **true** if this add-on provides an input stream. false if Kodi /// handles the stream. void SetHandlesInputStream(bool handlesInputStream) { - m_capabilities->bHandlesInputStream = handlesInputStream; + m_cStructure->bHandlesInputStream = handlesInputStream; } /// @brief To get with @ref SetHandlesInputStream changed values. - bool GetHandlesInputStream() const { return m_capabilities->bHandlesInputStream; } + bool GetHandlesInputStream() const { return m_cStructure->bHandlesInputStream; } /// @brief Set **true** if this add-on demultiplexes packets. void SetHandlesDemuxing(bool handlesDemuxing) { - m_capabilities->bHandlesDemuxing = handlesDemuxing; + m_cStructure->bHandlesDemuxing = handlesDemuxing; } /// @brief To get with @ref SetHandlesDemuxing changed values. - bool GetHandlesDemuxing() const { return m_capabilities->bHandlesDemuxing; } + bool GetHandlesDemuxing() const { return m_cStructure->bHandlesDemuxing; } /// @brief Set **true** if the backend supports play count for recordings. void SetSupportsRecordingPlayCount(bool supportsRecordingPlayCount) { - m_capabilities->bSupportsRecordingPlayCount = supportsRecordingPlayCount; + m_cStructure->bSupportsRecordingPlayCount = supportsRecordingPlayCount; } /// @brief To get with @ref SetSupportsRecordingPlayCount changed values. - bool GetSupportsRecordingPlayCount() const { return m_capabilities->bSupportsRecordingPlayCount; } + bool GetSupportsRecordingPlayCount() const { return m_cStructure->bSupportsRecordingPlayCount; } /// @brief Set **true** if the backend supports store/retrieve of last played /// position for recordings. void SetSupportsLastPlayedPosition(bool supportsLastPlayedPosition) { - m_capabilities->bSupportsLastPlayedPosition = supportsLastPlayedPosition; + m_cStructure->bSupportsLastPlayedPosition = supportsLastPlayedPosition; } /// @brief To get with @ref SetSupportsLastPlayedPosition changed values. - bool GetSupportsLastPlayedPosition() const { return m_capabilities->bSupportsLastPlayedPosition; } + bool GetSupportsLastPlayedPosition() const { return m_cStructure->bSupportsLastPlayedPosition; } /// @brief Set **true** if the backend supports retrieving an edit decision /// list for recordings. void SetSupportsRecordingEdl(bool supportsRecordingEdl) { - m_capabilities->bSupportsRecordingEdl = supportsRecordingEdl; + m_cStructure->bSupportsRecordingEdl = supportsRecordingEdl; } /// @brief To get with @ref SetSupportsRecordingEdl changed values. - bool GetSupportsRecordingEdl() const { return m_capabilities->bSupportsRecordingEdl; } + bool GetSupportsRecordingEdl() const { return m_cStructure->bSupportsRecordingEdl; } /// @brief Set **true** if the backend supports renaming recordings. void SetSupportsRecordingsRename(bool supportsRecordingsRename) { - m_capabilities->bSupportsRecordingsRename = supportsRecordingsRename; + m_cStructure->bSupportsRecordingsRename = supportsRecordingsRename; } /// @brief To get with @ref SetSupportsRecordingsRename changed values. - bool GetSupportsRecordingsRename() const { return m_capabilities->bSupportsRecordingsRename; } + bool GetSupportsRecordingsRename() const { return m_cStructure->bSupportsRecordingsRename; } /// @brief Set **true** if the backend supports changing lifetime for /// recordings. void SetSupportsRecordingsLifetimeChange(bool supportsRecordingsLifetimeChange) { - m_capabilities->bSupportsRecordingsLifetimeChange = supportsRecordingsLifetimeChange; + m_cStructure->bSupportsRecordingsLifetimeChange = supportsRecordingsLifetimeChange; } /// @brief To get with @ref SetSupportsRecordingsLifetimeChange changed /// values. bool GetSupportsRecordingsLifetimeChange() const { - return m_capabilities->bSupportsRecordingsLifetimeChange; + return m_cStructure->bSupportsRecordingsLifetimeChange; } /// @brief Set **true** if the backend supports descramble information for /// playing channels. void SetSupportsDescrambleInfo(bool supportsDescrambleInfo) { - m_capabilities->bSupportsDescrambleInfo = supportsDescrambleInfo; + m_cStructure->bSupportsDescrambleInfo = supportsDescrambleInfo; } /// @brief To get with @ref SetSupportsDescrambleInfo changed values. - bool GetSupportsDescrambleInfo() const { return m_capabilities->bSupportsDescrambleInfo; } + bool GetSupportsDescrambleInfo() const { return m_cStructure->bSupportsDescrambleInfo; } /// @brief Set **true** if this addon-on supports asynchronous transfer of epg /// events to Kodi using the callback function /// @ref kodi::addon::CInstancePVRClient::EpgEventStateChange(). void SetSupportsAsyncEPGTransfer(bool supportsAsyncEPGTransfer) { - m_capabilities->bSupportsAsyncEPGTransfer = supportsAsyncEPGTransfer; + m_cStructure->bSupportsAsyncEPGTransfer = supportsAsyncEPGTransfer; } /// @brief To get with @ref SetSupportsAsyncEPGTransfer changed values. - bool GetSupportsAsyncEPGTransfer() const { return m_capabilities->bSupportsAsyncEPGTransfer; } + bool GetSupportsAsyncEPGTransfer() const { return m_cStructure->bSupportsAsyncEPGTransfer; } /// @brief Set **true** if this addon-on supports retrieving size of recordings. void SetSupportsRecordingSize(bool supportsRecordingSize) { - m_capabilities->bSupportsRecordingSize = supportsRecordingSize; + m_cStructure->bSupportsRecordingSize = supportsRecordingSize; } /// @brief To get with @ref SetSupportsRecordingSize changed values. - bool GetSupportsRecordingSize() const { return m_capabilities->bSupportsRecordingSize; } + bool GetSupportsRecordingSize() const { return m_cStructure->bSupportsRecordingSize; } /// @brief Set **true** if this add-on supports delete of recordings stored /// on the backend. void SetSupportsRecordingsDelete(bool supportsRecordingsDelete) { - m_capabilities->bSupportsRecordingsDelete = supportsRecordingsDelete; + m_cStructure->bSupportsRecordingsDelete = supportsRecordingsDelete; } /// @brief To get with @ref SetSupportsRecordingsDelete changed values. - bool GetSupportsRecordingsDelete() const { return m_capabilities->bSupportsRecordingsDelete; } + bool GetSupportsRecordingsDelete() const { return m_cStructure->bSupportsRecordingsDelete; } /// @brief **optional**\n /// Set array containing the possible values for @ref PVRRecording::SetLifetime(). @@ -423,36 +424,53 @@ class PVRCapabilities /// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeIntValue_Help void SetRecordingsLifetimeValues(const std::vector& recordingsLifetimeValues) { - PVRTypeIntValue::FreeResources(m_capabilities->recordingsLifetimeValues, - m_capabilities->iRecordingsLifetimesSize); - m_capabilities->iRecordingsLifetimesSize = + PVRTypeIntValue::FreeResources(m_cStructure->recordingsLifetimeValues, + m_cStructure->iRecordingsLifetimesSize); + delete[] m_cStructure->recordingsLifetimeValues; + m_cStructure->recordingsLifetimeValues = nullptr; + + m_cStructure->iRecordingsLifetimesSize = static_cast(recordingsLifetimeValues.size()); - PVRTypeIntValue::CopyData(recordingsLifetimeValues, m_capabilities->recordingsLifetimeValues, - m_capabilities->iRecordingsLifetimesSize); + if (m_cStructure->iRecordingsLifetimesSize) + { + m_cStructure->recordingsLifetimeValues = + new PVR_ATTRIBUTE_INT_VALUE[m_cStructure->iRecordingsLifetimesSize]{}; + PVRTypeIntValue::CopyData(recordingsLifetimeValues, m_cStructure->recordingsLifetimeValues, + m_cStructure->iRecordingsLifetimesSize); + } } /// @brief To get with @ref SetRecordingsLifetimeValues changed values. std::vector GetRecordingsLifetimeValues() const { std::vector recordingsLifetimeValues; - for (unsigned int i = 0; i < m_capabilities->iRecordingsLifetimesSize; ++i) + for (unsigned int i = 0; i < m_cStructure->iRecordingsLifetimesSize; ++i) recordingsLifetimeValues.emplace_back( - m_capabilities->recordingsLifetimeValues[i].iValue, - m_capabilities->recordingsLifetimeValues[i].strDescription); + m_cStructure->recordingsLifetimeValues[i].iValue, + m_cStructure->recordingsLifetimeValues[i].strDescription); return recordingsLifetimeValues; } ///@} - static void FreeResources(PVR_ADDON_CAPABILITIES* capabilities) + static void AllocResources(const PVR_ADDON_CAPABILITIES* source, PVR_ADDON_CAPABILITIES* target) { - PVRTypeIntValue::FreeResources(capabilities->recordingsLifetimeValues, - capabilities->iRecordingsLifetimesSize); + PVRTypeIntValue::AllocResources(source->recordingsLifetimeValues, + target->recordingsLifetimeValues, + target->iRecordingsLifetimesSize); } -private: - PVRCapabilities(PVR_ADDON_CAPABILITIES* capabilities) : m_capabilities(capabilities) {} + static void FreeResources(PVR_ADDON_CAPABILITIES* target) + { + PVRTypeIntValue::FreeResources(target->recordingsLifetimeValues, + target->iRecordingsLifetimesSize); + delete[] target->recordingsLifetimeValues; + target->recordingsLifetimeValues = nullptr; + target->iRecordingsLifetimesSize = 0; + } - PVR_ADDON_CAPABILITIES* m_capabilities; +private: + PVRCapabilities(const PVR_ADDON_CAPABILITIES* capabilities) : DynamicCStructHdl(capabilities) {} + PVRCapabilities(PVR_ADDON_CAPABILITIES* capabilities) : DynamicCStructHdl(capabilities) {} }; ///@} //------------------------------------------------------------------------------ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h index 5e7c7ecee0f5d..e33bec5b991ad 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h @@ -30,7 +30,6 @@ extern "C" #define PVR_ADDON_TIMERTYPE_VALUES_ARRAY_SIZE 512 #define PVR_ADDON_TIMERTYPE_VALUES_ARRAY_SIZE_SMALL 128 #define PVR_ADDON_TIMERTYPE_STRING_LENGTH 128 -#define PVR_ADDON_ATTRIBUTE_VALUES_ARRAY_SIZE 512 #define PVR_ADDON_DESCRAMBLE_INFO_STRING_LENGTH 64 ///@} diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h index 03a1d0b72192b..a0dff4242836a 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h @@ -290,7 +290,7 @@ extern "C" bool bSupportsRecordingsDelete; unsigned int iRecordingsLifetimesSize; - struct PVR_ATTRIBUTE_INT_VALUE recordingsLifetimeValues[PVR_ADDON_ATTRIBUTE_VALUES_ARRAY_SIZE]; + struct PVR_ATTRIBUTE_INT_VALUE* recordingsLifetimeValues; } PVR_ADDON_CAPABILITIES; #ifdef __cplusplus diff --git a/xbmc/pvr/addons/PVRClientCapabilities.cpp b/xbmc/pvr/addons/PVRClientCapabilities.cpp index 7c09fd55ff792..a0b1f6cfca4e2 100644 --- a/xbmc/pvr/addons/PVRClientCapabilities.cpp +++ b/xbmc/pvr/addons/PVRClientCapabilities.cpp @@ -53,8 +53,9 @@ void CPVRClientCapabilities::InitRecordingsLifetimeValues() { for (unsigned int i = 0; i < m_addonCapabilities->iRecordingsLifetimesSize; ++i) { - int iValue = m_addonCapabilities->recordingsLifetimeValues[i].iValue; - std::string strDescr(m_addonCapabilities->recordingsLifetimeValues[i].strDescription); + const auto& lifetime{m_addonCapabilities->recordingsLifetimeValues[i]}; + const int iValue{lifetime.iValue}; + std::string strDescr{lifetime.strDescription ? lifetime.strDescription : ""}; if (strDescr.empty()) { // No description given by addon. Create one from value. From dbbd12684999d80a398a3af3b4e4ea3de4b8d2c5 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 20 Jul 2024 00:25:53 +0200 Subject: [PATCH 327/651] [addons][PVR] PVR add-on API: PVR_TIMER_TYPE: Replace fixed size PVR_ATTRIBUTE_INT_VALUE and char arrays with dynamic arrays. --- .../include/kodi/addon-instance/pvr/General.h | 68 +++++++------- .../include/kodi/addon-instance/pvr/Timers.h | 92 ++++++++++++------- .../c-api/addon-instance/pvr/pvr_defines.h | 3 - .../c-api/addon-instance/pvr/pvr_timers.h | 12 +-- 4 files changed, 102 insertions(+), 73 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h index d12c0f1326f7a..dfb7cfa1accf3 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h @@ -85,15 +85,27 @@ class PVRTypeIntValue : public DynamicCStructHdlstrDescription; } ///@} - static void CopyData(const std::vector& source, - PVR_ATTRIBUTE_INT_VALUE* values, - unsigned int size) + static PVR_ATTRIBUTE_INT_VALUE* AllocAndCopyData(const std::vector& source) { - for (unsigned int i = 0; i < size; ++i) + PVR_ATTRIBUTE_INT_VALUE* values = new PVR_ATTRIBUTE_INT_VALUE[source.size()]{}; + for (unsigned int i = 0; i < source.size(); ++i) { values[i].iValue = source[i].GetCStructure()->iValue; - values[i].strDescription = AllocAndCopyString(source[i].GetCStructure()->strDescription); + AllocResources(source[i].GetCStructure(), &values[i]); } + return values; + } + + static PVR_ATTRIBUTE_INT_VALUE* AllocAndCopyData(const PVR_ATTRIBUTE_INT_VALUE* source, + unsigned int size) + { + PVR_ATTRIBUTE_INT_VALUE* values = new PVR_ATTRIBUTE_INT_VALUE[size]{}; + for (unsigned int i = 0; i < size; ++i) + { + values[i].iValue = source[i].iValue; + AllocResources(&source[i], &values[i]); + } + return values; } static void AllocResources(const PVR_ATTRIBUTE_INT_VALUE* source, PVR_ATTRIBUTE_INT_VALUE* target) @@ -107,22 +119,24 @@ class PVRTypeIntValue : public DynamicCStructHdlstrDescription = nullptr; } - static void AllocResources(const PVR_ATTRIBUTE_INT_VALUE* source, - PVR_ATTRIBUTE_INT_VALUE* values, - unsigned int size) + static void FreeResources(PVR_ATTRIBUTE_INT_VALUE* values, unsigned int size) { for (unsigned int i = 0; i < size; ++i) { - AllocResources(&source[i], &values[i]); + FreeResources(&values[i]); } + delete[] values; } - static void FreeResources(PVR_ATTRIBUTE_INT_VALUE* values, unsigned int size) + static void ReallocAndCopyData(PVR_ATTRIBUTE_INT_VALUE** source, + unsigned int* size, + const std::vector& values) { - for (unsigned int i = 0; i < size; ++i) - { - FreeResources(&values[i]); - } + FreeResources(*source, *size); + *source = nullptr; + *size = values.size(); + if (*size) + *source = AllocAndCopyData(values); } private: @@ -424,20 +438,9 @@ class PVRCapabilities : public DynamicCStructHdl& recordingsLifetimeValues) { - PVRTypeIntValue::FreeResources(m_cStructure->recordingsLifetimeValues, - m_cStructure->iRecordingsLifetimesSize); - delete[] m_cStructure->recordingsLifetimeValues; - m_cStructure->recordingsLifetimeValues = nullptr; - - m_cStructure->iRecordingsLifetimesSize = - static_cast(recordingsLifetimeValues.size()); - if (m_cStructure->iRecordingsLifetimesSize) - { - m_cStructure->recordingsLifetimeValues = - new PVR_ATTRIBUTE_INT_VALUE[m_cStructure->iRecordingsLifetimesSize]{}; - PVRTypeIntValue::CopyData(recordingsLifetimeValues, m_cStructure->recordingsLifetimeValues, - m_cStructure->iRecordingsLifetimesSize); - } + PVRTypeIntValue::ReallocAndCopyData(&m_cStructure->recordingsLifetimeValues, + &m_cStructure->iRecordingsLifetimesSize, + recordingsLifetimeValues); } /// @brief To get with @ref SetRecordingsLifetimeValues changed values. @@ -454,16 +457,17 @@ class PVRCapabilities : public DynamicCStructHdlrecordingsLifetimeValues, - target->recordingsLifetimeValues, - target->iRecordingsLifetimesSize); + if (target->iRecordingsLifetimesSize) + { + target->recordingsLifetimeValues = PVRTypeIntValue::AllocAndCopyData( + source->recordingsLifetimeValues, source->iRecordingsLifetimesSize); + } } static void FreeResources(PVR_ADDON_CAPABILITIES* target) { PVRTypeIntValue::FreeResources(target->recordingsLifetimeValues, target->iRecordingsLifetimesSize); - delete[] target->recordingsLifetimeValues; target->recordingsLifetimeValues = nullptr; target->iRecordingsLifetimesSize = 0; } diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h index f0f47bb2c83e5..2dcf8029fadfc 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h @@ -624,8 +624,7 @@ class PVRTimerType : public DynamicCStructHdl /// REPEATING and MANUAL. (e.g. "Repeating EPG-based.") void SetDescription(const std::string& description) { - strncpy(m_cStructure->strDescription, description.c_str(), - sizeof(m_cStructure->strDescription) - 1); + ReallocAndCopyString(&m_cStructure->strDescription, description.c_str()); } /// @brief To get with @ref SetDescription changed values. @@ -647,9 +646,8 @@ class PVRTimerType : public DynamicCStructHdl /// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeIntValue_Help void SetPriorities(const std::vector& priorities, int prioritiesDefault = -1) { - PVRTypeIntValue::FreeResources(m_cStructure->priorities, m_cStructure->iPrioritiesSize); - m_cStructure->iPrioritiesSize = static_cast(priorities.size()); - PVRTypeIntValue::CopyData(priorities, m_cStructure->priorities, m_cStructure->iPrioritiesSize); + PVRTypeIntValue::ReallocAndCopyData(&m_cStructure->priorities, &m_cStructure->iPrioritiesSize, + priorities); if (prioritiesDefault != -1) m_cStructure->iPrioritiesDefault = prioritiesDefault; } @@ -693,9 +691,8 @@ class PVRTimerType : public DynamicCStructHdl /// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeIntValue_Help void SetLifetimes(const std::vector& lifetimes, int lifetimesDefault = -1) { - PVRTypeIntValue::FreeResources(m_cStructure->lifetimes, m_cStructure->iLifetimesSize); - m_cStructure->iLifetimesSize = static_cast(lifetimes.size()); - PVRTypeIntValue::CopyData(lifetimes, m_cStructure->lifetimes, m_cStructure->iLifetimesSize); + PVRTypeIntValue::ReallocAndCopyData(&m_cStructure->lifetimes, &m_cStructure->iLifetimesSize, + lifetimes); if (lifetimesDefault != -1) m_cStructure->iLifetimesDefault = lifetimesDefault; } @@ -742,12 +739,9 @@ class PVRTimerType : public DynamicCStructHdl void SetPreventDuplicateEpisodes(const std::vector& preventDuplicateEpisodes, int preventDuplicateEpisodesDefault = -1) { - PVRTypeIntValue::FreeResources(m_cStructure->preventDuplicateEpisodes, - m_cStructure->iPreventDuplicateEpisodesSize); - m_cStructure->iPreventDuplicateEpisodesSize = - static_cast(preventDuplicateEpisodes.size()); - PVRTypeIntValue::CopyData(preventDuplicateEpisodes, m_cStructure->preventDuplicateEpisodes, - m_cStructure->iPreventDuplicateEpisodesSize); + PVRTypeIntValue::ReallocAndCopyData(&m_cStructure->preventDuplicateEpisodes, + &m_cStructure->iPreventDuplicateEpisodesSize, + preventDuplicateEpisodes); if (preventDuplicateEpisodesDefault != -1) m_cStructure->iPreventDuplicateEpisodesDefault = preventDuplicateEpisodesDefault; } @@ -793,10 +787,8 @@ class PVRTimerType : public DynamicCStructHdl void SetRecordingGroups(const std::vector& recordingGroup, int recordingGroupDefault = -1) { - PVRTypeIntValue::FreeResources(m_cStructure->recordingGroup, m_cStructure->iRecordingGroupSize); - m_cStructure->iRecordingGroupSize = static_cast(recordingGroup.size()); - PVRTypeIntValue::CopyData(recordingGroup, m_cStructure->recordingGroup, - m_cStructure->iRecordingGroupSize); + PVRTypeIntValue::ReallocAndCopyData(&m_cStructure->recordingGroup, + &m_cStructure->iRecordingGroupSize, recordingGroup); if (recordingGroupDefault != -1) m_cStructure->iRecordingGroupDefault = recordingGroupDefault; } @@ -839,10 +831,8 @@ class PVRTimerType : public DynamicCStructHdl void SetMaxRecordings(const std::vector& maxRecordings, int maxRecordingsDefault = -1) { - PVRTypeIntValue::FreeResources(m_cStructure->maxRecordings, m_cStructure->iMaxRecordingsSize); - m_cStructure->iMaxRecordingsSize = static_cast(maxRecordings.size()); - PVRTypeIntValue::CopyData(maxRecordings, m_cStructure->maxRecordings, - m_cStructure->iMaxRecordingsSize); + PVRTypeIntValue::ReallocAndCopyData(&m_cStructure->maxRecordings, + &m_cStructure->iMaxRecordingsSize, maxRecordings); if (maxRecordingsDefault != -1) m_cStructure->iMaxRecordingsDefault = maxRecordingsDefault; } @@ -872,26 +862,64 @@ class PVRTimerType : public DynamicCStructHdl static void AllocResources(const PVR_TIMER_TYPE* source, PVR_TIMER_TYPE* target) { - PVRTypeIntValue::AllocResources(source->priorities, target->priorities, - target->iPrioritiesSize); - PVRTypeIntValue::AllocResources(source->lifetimes, target->lifetimes, target->iLifetimesSize); - PVRTypeIntValue::AllocResources(source->preventDuplicateEpisodes, - target->preventDuplicateEpisodes, - target->iPreventDuplicateEpisodesSize); - PVRTypeIntValue::AllocResources(source->recordingGroup, target->recordingGroup, - target->iRecordingGroupSize); - PVRTypeIntValue::AllocResources(source->maxRecordings, target->maxRecordings, - target->iMaxRecordingsSize); + target->strDescription = AllocAndCopyString(source->strDescription); + + if (target->iPrioritiesSize) + { + target->priorities = + PVRTypeIntValue::AllocAndCopyData(source->priorities, source->iPrioritiesSize); + } + + if (target->iLifetimesSize) + { + target->lifetimes = + PVRTypeIntValue::AllocAndCopyData(source->lifetimes, source->iLifetimesSize); + } + + if (target->iPreventDuplicateEpisodesSize) + { + target->preventDuplicateEpisodes = PVRTypeIntValue::AllocAndCopyData( + source->preventDuplicateEpisodes, source->iPreventDuplicateEpisodesSize); + } + + if (target->iRecordingGroupSize) + { + target->recordingGroup = + PVRTypeIntValue::AllocAndCopyData(source->recordingGroup, source->iRecordingGroupSize); + } + + if (target->iMaxRecordingsSize) + { + target->maxRecordings = + PVRTypeIntValue::AllocAndCopyData(source->maxRecordings, source->iMaxRecordingsSize); + } } static void FreeResources(PVR_TIMER_TYPE* target) { + FreeString(target->strDescription); + target->strDescription = nullptr; + PVRTypeIntValue::FreeResources(target->priorities, target->iPrioritiesSize); + target->priorities = nullptr; + target->iPrioritiesSize = 0; + PVRTypeIntValue::FreeResources(target->lifetimes, target->iLifetimesSize); + target->lifetimes = nullptr; + target->iLifetimesSize = 0; + PVRTypeIntValue::FreeResources(target->preventDuplicateEpisodes, target->iPreventDuplicateEpisodesSize); + target->preventDuplicateEpisodes = nullptr; + target->iPreventDuplicateEpisodesSize = 0; + PVRTypeIntValue::FreeResources(target->recordingGroup, target->iRecordingGroupSize); + target->recordingGroup = nullptr; + target->iRecordingGroupSize = 0; + PVRTypeIntValue::FreeResources(target->maxRecordings, target->iMaxRecordingsSize); + target->maxRecordings = nullptr; + target->iMaxRecordingsSize = 0; } private: diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h index e33bec5b991ad..b6ca80ece4413 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h @@ -27,9 +27,6 @@ extern "C" #define PVR_ADDON_NAME_STRING_LENGTH 1024 #define PVR_ADDON_EDL_LENGTH 64 #define PVR_ADDON_TIMERTYPE_ARRAY_SIZE 32 -#define PVR_ADDON_TIMERTYPE_VALUES_ARRAY_SIZE 512 -#define PVR_ADDON_TIMERTYPE_VALUES_ARRAY_SIZE_SMALL 128 -#define PVR_ADDON_TIMERTYPE_STRING_LENGTH 128 #define PVR_ADDON_DESCRAMBLE_INFO_STRING_LENGTH 64 ///@} diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h index 7d333be1ad434..8af03939ff4d4 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h @@ -379,26 +379,26 @@ extern "C" { unsigned int iId; uint64_t iAttributes; - char strDescription[PVR_ADDON_TIMERTYPE_STRING_LENGTH]; + const char* strDescription; unsigned int iPrioritiesSize; - struct PVR_ATTRIBUTE_INT_VALUE priorities[PVR_ADDON_TIMERTYPE_VALUES_ARRAY_SIZE]; + struct PVR_ATTRIBUTE_INT_VALUE* priorities; int iPrioritiesDefault; unsigned int iLifetimesSize; - struct PVR_ATTRIBUTE_INT_VALUE lifetimes[PVR_ADDON_TIMERTYPE_VALUES_ARRAY_SIZE]; + struct PVR_ATTRIBUTE_INT_VALUE* lifetimes; int iLifetimesDefault; unsigned int iPreventDuplicateEpisodesSize; - struct PVR_ATTRIBUTE_INT_VALUE preventDuplicateEpisodes[PVR_ADDON_TIMERTYPE_VALUES_ARRAY_SIZE]; + struct PVR_ATTRIBUTE_INT_VALUE* preventDuplicateEpisodes; unsigned int iPreventDuplicateEpisodesDefault; unsigned int iRecordingGroupSize; - struct PVR_ATTRIBUTE_INT_VALUE recordingGroup[PVR_ADDON_TIMERTYPE_VALUES_ARRAY_SIZE]; + struct PVR_ATTRIBUTE_INT_VALUE* recordingGroup; unsigned int iRecordingGroupDefault; unsigned int iMaxRecordingsSize; - struct PVR_ATTRIBUTE_INT_VALUE maxRecordings[PVR_ADDON_TIMERTYPE_VALUES_ARRAY_SIZE_SMALL]; + struct PVR_ATTRIBUTE_INT_VALUE* maxRecordings; int iMaxRecordingsDefault; } PVR_TIMER_TYPE; From a334da6a89654afbcec743c20b097d21ec75fb95 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 21 Jul 2024 16:56:25 +0200 Subject: [PATCH 328/651] [addons][PVR] PVR add-on API: PVR_DESCRAMBLE_INFO: Replace fixed size char arrays with dynamic arrays. --- .../include/kodi/addon-instance/PVR.h | 8 +++ .../kodi/addon-instance/pvr/Channels.h | 33 ++++++++---- .../include/kodi/c-api/addon-instance/pvr.h | 2 + .../c-api/addon-instance/pvr/pvr_channels.h | 8 +-- .../c-api/addon-instance/pvr/pvr_defines.h | 1 - xbmc/pvr/CMakeLists.txt | 1 + xbmc/pvr/PVRDescrambleInfo.h | 52 +++++++++++++++++++ xbmc/pvr/addons/PVRClient.cpp | 14 +++-- xbmc/pvr/addons/PVRClient.h | 3 +- xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 8 +-- xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h | 5 +- 11 files changed, 111 insertions(+), 24 deletions(-) create mode 100644 xbmc/pvr/PVRDescrambleInfo.h diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h index d51be6b336bc8..de8ec5e2e8861 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h @@ -2829,6 +2829,7 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance instance->pvr->toAddon->FreeCapabilities = ADDON_FreeCapabilities; instance->pvr->toAddon->FreeTimerTypes = ADDON_FreeTimerTypes; instance->pvr->toAddon->FreeProperties = ADDON_FreeProperties; + instance->pvr->toAddon->FreeDescrambleInfo = ADDON_FreeDescrambleInfo; m_instanceData = instance->pvr; m_instanceData->toAddon->addonInstance = this; @@ -2964,6 +2965,13 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance ->GetDescrambleInfo(channelUid, cppDescrambleInfo); } + inline static PVR_ERROR ADDON_FreeDescrambleInfo(const AddonInstance_PVR* instance, + PVR_DESCRAMBLE_INFO* descrambleInfo) + { + PVRDescrambleInfo::FreeResources(descrambleInfo); + return PVR_ERROR_NO_ERROR; + } + //--==----==----==----==----==----==----==----==----==----==----==----==----== inline static PVR_ERROR ADDON_GetProvidersAmount(const AddonInstance_PVR* instance, int* amount) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h index dcc0650b6b57f..35327c50d0d81 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h @@ -401,7 +401,7 @@ class PVRSignalStatus : public CStructHdl /// @copydetails cpp_kodi_addon_pvr_Defs_Channel_PVRDescrambleInfo_Help /// ///@{ -class PVRDescrambleInfo : public CStructHdl +class PVRDescrambleInfo : public DynamicCStructHdl { friend class CInstancePVRClient; @@ -415,7 +415,7 @@ class PVRDescrambleInfo : public CStructHdliEcmTime = PVR_DESCRAMBLE_INFO_NOT_AVAILABLE; m_cStructure->iHops = PVR_DESCRAMBLE_INFO_NOT_AVAILABLE; } - PVRDescrambleInfo(const PVRDescrambleInfo& type) : CStructHdl(type) {} + PVRDescrambleInfo(const PVRDescrambleInfo& info) : DynamicCStructHdl(info) {} /*! \endcond */ /// @defgroup cpp_kodi_addon_pvr_Defs_Channel_PVRDescrambleInfo_Help Value Help @@ -496,8 +496,7 @@ class PVRDescrambleInfo : public CStructHdlstrCardSystem, cardSystem.c_str(), - sizeof(m_cStructure->strCardSystem) - 1); + ReallocAndCopyString(&m_cStructure->strCardSystem, cardSystem.c_str()); } /// @brief To get with @ref SetCardSystem changed values. @@ -507,7 +506,7 @@ class PVRDescrambleInfo : public CStructHdlstrReader, reader.c_str(), sizeof(m_cStructure->strReader) - 1); + ReallocAndCopyString(&m_cStructure->strReader, reader.c_str()); } /// @brief To get with @ref SetReader changed values. @@ -517,7 +516,7 @@ class PVRDescrambleInfo : public CStructHdlstrFrom, from.c_str(), sizeof(m_cStructure->strFrom) - 1); + ReallocAndCopyString(&m_cStructure->strFrom, from.c_str()); } /// @brief To get with @ref SetFrom changed values. @@ -527,16 +526,32 @@ class PVRDescrambleInfo : public CStructHdlstrProtocol, protocol.c_str(), sizeof(m_cStructure->strProtocol) - 1); + ReallocAndCopyString(&m_cStructure->strProtocol, protocol.c_str()); } /// @brief To get with @ref SetProtocol changed values. std::string GetProtocol() const { return m_cStructure->strProtocol; } ///@} + static void AllocResources(const PVR_DESCRAMBLE_INFO* source, PVR_DESCRAMBLE_INFO* target) + { + target->strCardSystem = AllocAndCopyString(source->strCardSystem); + target->strReader = AllocAndCopyString(source->strReader); + target->strFrom = AllocAndCopyString(source->strFrom); + target->strProtocol = AllocAndCopyString(source->strProtocol); + } + + static void FreeResources(PVR_DESCRAMBLE_INFO* target) + { + FreeString(target->strCardSystem); + FreeString(target->strReader); + FreeString(target->strFrom); + FreeString(target->strProtocol); + } + private: - PVRDescrambleInfo(const PVR_DESCRAMBLE_INFO* type) : CStructHdl(type) {} - PVRDescrambleInfo(PVR_DESCRAMBLE_INFO* type) : CStructHdl(type) {} + PVRDescrambleInfo(const PVR_DESCRAMBLE_INFO* info) : DynamicCStructHdl(info) {} + PVRDescrambleInfo(PVR_DESCRAMBLE_INFO* info) : DynamicCStructHdl(info) {} }; ///@} //------------------------------------------------------------------------------ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h index 6c21f0b543f3a..c97167bfcd8a9 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h @@ -333,6 +333,8 @@ extern "C" enum PVR_ERROR(__cdecl* FreeProperties)(const struct AddonInstance_PVR*, struct PVR_NAMED_VALUE**, unsigned int); + enum PVR_ERROR(__cdecl* FreeDescrambleInfo)(const struct AddonInstance_PVR*, + struct PVR_DESCRAMBLE_INFO*); //--==----==----==----==----==----==----==----==----==----==----==----==----== // New functions becomes added below and can be on another API change (where diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h index b7b02c6ae159d..b6b8131a92150 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h @@ -95,10 +95,10 @@ extern "C" int iProvid; int iEcmTime; int iHops; - char strCardSystem[PVR_ADDON_DESCRAMBLE_INFO_STRING_LENGTH]; - char strReader[PVR_ADDON_DESCRAMBLE_INFO_STRING_LENGTH]; - char strFrom[PVR_ADDON_DESCRAMBLE_INFO_STRING_LENGTH]; - char strProtocol[PVR_ADDON_DESCRAMBLE_INFO_STRING_LENGTH]; + const char* strCardSystem; + const char* strReader; + const char* strFrom; + const char* strProtocol; } PVR_DESCRAMBLE_INFO; #ifdef __cplusplus diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h index b6ca80ece4413..7b9d63cc34f7b 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h @@ -27,7 +27,6 @@ extern "C" #define PVR_ADDON_NAME_STRING_LENGTH 1024 #define PVR_ADDON_EDL_LENGTH 64 #define PVR_ADDON_TIMERTYPE_ARRAY_SIZE 32 -#define PVR_ADDON_DESCRAMBLE_INFO_STRING_LENGTH 64 ///@} /*! diff --git a/xbmc/pvr/CMakeLists.txt b/xbmc/pvr/CMakeLists.txt index 5bf1b80697daf..c5309a9a1963f 100644 --- a/xbmc/pvr/CMakeLists.txt +++ b/xbmc/pvr/CMakeLists.txt @@ -21,6 +21,7 @@ set(HEADERS IPVRComponent.h PVRComponentRegistration.h PVRContextMenus.h PVRDatabase.h + PVRDescrambleInfo.h PVREdl.h PVREventLogJob.h PVRItem.h diff --git a/xbmc/pvr/PVRDescrambleInfo.h b/xbmc/pvr/PVRDescrambleInfo.h new file mode 100644 index 0000000000000..83046909c047b --- /dev/null +++ b/xbmc/pvr/PVRDescrambleInfo.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 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 "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" + +#include + +namespace PVR +{ + +class CPVRDescrambleInfo +{ +public: + CPVRDescrambleInfo() + { + m_info.iPid = PVR_DESCRAMBLE_INFO_NOT_AVAILABLE; + m_info.iCaid = PVR_DESCRAMBLE_INFO_NOT_AVAILABLE; + m_info.iProvid = PVR_DESCRAMBLE_INFO_NOT_AVAILABLE; + m_info.iEcmTime = PVR_DESCRAMBLE_INFO_NOT_AVAILABLE; + m_info.iHops = PVR_DESCRAMBLE_INFO_NOT_AVAILABLE; + } + + CPVRDescrambleInfo(const PVR_DESCRAMBLE_INFO& info) + : m_info(info), + m_cardSystem(info.strCardSystem ? info.strCardSystem : ""), + m_reader(info.strReader ? info.strReader : ""), + m_from(info.strFrom ? info.strFrom : ""), + m_protocol(info.strProtocol ? info.strProtocol : "") + { + } + + virtual ~CPVRDescrambleInfo() = default; + + //! @todo add other getters (currently not used). + int Caid() const { return m_info.iCaid; } + +private: + PVR_DESCRAMBLE_INFO m_info{}; + std::string m_cardSystem; + std::string m_reader; + std::string m_from; + std::string m_protocol; +}; + +} // namespace PVR diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index a3f022356dd04..38e5517499623 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -18,6 +18,7 @@ #include "filesystem/SpecialProtocol.h" #include "guilib/LocalizeStrings.h" #include "pvr/PVRDatabase.h" +#include "pvr/PVRDescrambleInfo.h" #include "pvr/PVRManager.h" #include "pvr/PVRStreamProperties.h" #include "pvr/addons/PVRClientMenuHooks.h" @@ -1407,12 +1408,19 @@ PVR_ERROR CPVRClient::SignalQuality(int channelUid, PVR_SIGNAL_STATUS& qualityin }); } -PVR_ERROR CPVRClient::GetDescrambleInfo(int channelUid, PVR_DESCRAMBLE_INFO& descrambleinfo) const +PVR_ERROR CPVRClient::GetDescrambleInfo(int channelUid, CPVRDescrambleInfo& descrambleinfo) const { return DoAddonCall( __func__, - [channelUid, &descrambleinfo](const AddonInstance* addon) { - return addon->toAddon->GetDescrambleInfo(addon, channelUid, &descrambleinfo); + [channelUid, &descrambleinfo](const AddonInstance* addon) + { + PVR_DESCRAMBLE_INFO info{}; + const PVR_ERROR error{addon->toAddon->GetDescrambleInfo(addon, channelUid, &info)}; + if (error == PVR_ERROR_NO_ERROR) + descrambleinfo = info; + + addon->toAddon->FreeDescrambleInfo(addon, &info); + return error; }, m_clientCapabilities.SupportsDescrambleInfo()); } diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h index b33aed93109ce..92835a588b18d 100644 --- a/xbmc/pvr/addons/PVRClient.h +++ b/xbmc/pvr/addons/PVRClient.h @@ -33,6 +33,7 @@ class CPVRProvider; class CPVRProvidersContainer; class CPVRClientMenuHook; class CPVRClientMenuHooks; +class CPVRDescrambleInfo; class CPVREpg; class CPVREpgInfoTag; class CPVRRecording; @@ -579,7 +580,7 @@ class CPVRClient : public ADDON::IAddonInstanceHandler * @param descrambleinfo The descramble information. * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. */ - PVR_ERROR GetDescrambleInfo(int channelUid, PVR_DESCRAMBLE_INFO& descrambleinfo) const; + PVR_ERROR GetDescrambleInfo(int channelUid, CPVRDescrambleInfo& descrambleinfo) const; /*! * @brief Fill the given container with the properties required for playback of the given channel. Values are obtained from the PVR backend. diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index 9df61c25a69e4..0a1ac5036c5bf 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -114,7 +114,7 @@ void CPVRGUIInfo::ClearQualityInfo(PVR_SIGNAL_STATUS& qualityInfo) PVR_ADDON_NAME_STRING_LENGTH - 1); } -void CPVRGUIInfo::ClearDescrambleInfo(PVR_DESCRAMBLE_INFO& descrambleInfo) +void CPVRGUIInfo::ClearDescrambleInfo(CPVRDescrambleInfo& descrambleInfo) { descrambleInfo = {}; } @@ -264,7 +264,7 @@ void CPVRGUIInfo::UpdateDescrambleData() if (!playbackState) return; - PVR_DESCRAMBLE_INFO descrambleInfo; + CPVRDescrambleInfo descrambleInfo; ClearDescrambleInfo(descrambleInfo); const int channelUid = playbackState->GetPlayingChannelUniqueID(); @@ -1954,10 +1954,10 @@ void CPVRGUIInfo::CharInfoPlayingClientName(std::string& strValue) const void CPVRGUIInfo::CharInfoEncryption(std::string& strValue) const { - if (m_descrambleInfo.iCaid != PVR_DESCRAMBLE_INFO_NOT_AVAILABLE) + if (m_descrambleInfo.Caid() != PVR_DESCRAMBLE_INFO_NOT_AVAILABLE) { // prefer dynamically updated info, if available - strValue = CPVRChannel::GetEncryptionName(m_descrambleInfo.iCaid); + strValue = CPVRChannel::GetEncryptionName(m_descrambleInfo.Caid()); return; } else diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h index 55913534e30f6..6cea9ff476d14 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h @@ -10,6 +10,7 @@ #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" #include "guilib/guiinfo/GUIInfoProvider.h" +#include "pvr/PVRDescrambleInfo.h" #include "pvr/addons/PVRClients.h" #include "pvr/guilib/guiinfo/PVRGUITimerInfo.h" #include "pvr/guilib/guiinfo/PVRGUITimesInfo.h" @@ -90,7 +91,7 @@ class CPVRGUIInfo : public KODI::GUILIB::GUIINFO::CGUIInfoProvider, private CThr private: void ResetProperties(); void ClearQualityInfo(PVR_SIGNAL_STATUS& qualityInfo); - void ClearDescrambleInfo(PVR_DESCRAMBLE_INFO& descrambleInfo); + void ClearDescrambleInfo(CPVRDescrambleInfo& descrambleInfo); void Process() override; @@ -196,7 +197,7 @@ class CPVRGUIInfo : public KODI::GUILIB::GUIINFO::CGUIInfoProvider, private CThr //@} PVR_SIGNAL_STATUS m_qualityInfo; /*!< stream quality information */ - PVR_DESCRAMBLE_INFO m_descrambleInfo; /*!< stream descramble information */ + CPVRDescrambleInfo m_descrambleInfo; /*!< stream descramble information */ std::vector m_backendProperties; std::string m_channelNumberInput; From 92ddcb17d0998f466fc0da59d9dcf199800175bb Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 21 Jul 2024 17:57:04 +0200 Subject: [PATCH 329/651] [addons][PVR] PVR add-on API: PVR_SIGNAL_STATUS: Replace fixed size char arrays with dynamic arrays. --- .../include/kodi/addon-instance/PVR.h | 8 +++ .../kodi/addon-instance/pvr/Channels.h | 40 +++++++++---- .../include/kodi/c-api/addon-instance/pvr.h | 2 + .../c-api/addon-instance/pvr/pvr_channels.h | 10 ++-- xbmc/pvr/CMakeLists.txt | 1 + xbmc/pvr/PVRSignalStatus.h | 59 +++++++++++++++++++ xbmc/pvr/addons/PVRClient.cpp | 18 ++++-- xbmc/pvr/addons/PVRClient.h | 3 +- xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 47 ++++++--------- xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h | 6 +- 10 files changed, 140 insertions(+), 54 deletions(-) create mode 100644 xbmc/pvr/PVRSignalStatus.h diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h index de8ec5e2e8861..09e668490387d 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h @@ -2830,6 +2830,7 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance instance->pvr->toAddon->FreeTimerTypes = ADDON_FreeTimerTypes; instance->pvr->toAddon->FreeProperties = ADDON_FreeProperties; instance->pvr->toAddon->FreeDescrambleInfo = ADDON_FreeDescrambleInfo; + instance->pvr->toAddon->FreeSignalStatus = ADDON_FreeSignalStatus; m_instanceData = instance->pvr; m_instanceData->toAddon->addonInstance = this; @@ -2956,6 +2957,13 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance ->GetSignalStatus(channelUid, cppSignalStatus); } + inline static PVR_ERROR ADDON_FreeSignalStatus(const AddonInstance_PVR* instance, + PVR_SIGNAL_STATUS* signalStatus) + { + PVRSignalStatus::FreeResources(signalStatus); + return PVR_ERROR_NO_ERROR; + } + inline static PVR_ERROR ADDON_GetDescrambleInfo(const AddonInstance_PVR* instance, int channelUid, PVR_DESCRAMBLE_INFO* descrambleInfo) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h index 35327c50d0d81..cbbb4c3abdcca 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h @@ -262,14 +262,14 @@ class PVRChannelsResultSet /// @copydetails cpp_kodi_addon_pvr_Defs_Channel_PVRSignalStatus_Help /// ///@{ -class PVRSignalStatus : public CStructHdl +class PVRSignalStatus : public DynamicCStructHdl { friend class CInstancePVRClient; public: /*! \cond PRIVATE */ PVRSignalStatus() = default; - PVRSignalStatus(const PVRSignalStatus& type) : CStructHdl(type) {} + PVRSignalStatus(const PVRSignalStatus& status) : DynamicCStructHdl(status) {} /*! \endcond */ /// @defgroup cpp_kodi_addon_pvr_Defs_Channel_PVRSignalStatus_Help Value Help @@ -296,8 +296,7 @@ class PVRSignalStatus : public CStructHdl /// Name of the adapter that's being used. void SetAdapterName(const std::string& adapterName) { - strncpy(m_cStructure->strAdapterName, adapterName.c_str(), - sizeof(m_cStructure->strAdapterName) - 1); + ReallocAndCopyString(&m_cStructure->strAdapterName, adapterName.c_str()); } /// @brief To get with @ref SetAdapterName changed values. @@ -307,8 +306,7 @@ class PVRSignalStatus : public CStructHdl /// Status of the adapter that's being used. void SetAdapterStatus(const std::string& adapterStatus) { - strncpy(m_cStructure->strAdapterStatus, adapterStatus.c_str(), - sizeof(m_cStructure->strAdapterStatus) - 1); + ReallocAndCopyString(&m_cStructure->strAdapterStatus, adapterStatus.c_str()); } /// @brief To get with @ref SetAdapterStatus changed values. @@ -318,8 +316,7 @@ class PVRSignalStatus : public CStructHdl /// Name of the current service. void SetServiceName(const std::string& serviceName) { - strncpy(m_cStructure->strServiceName, serviceName.c_str(), - sizeof(m_cStructure->strServiceName) - 1); + ReallocAndCopyString(&m_cStructure->strServiceName, serviceName.c_str()); } /// @brief To get with @ref SetServiceName changed values. @@ -329,8 +326,7 @@ class PVRSignalStatus : public CStructHdl /// Name of the current service's provider. void SetProviderName(const std::string& providerName) { - strncpy(m_cStructure->strProviderName, providerName.c_str(), - sizeof(m_cStructure->strProviderName) - 1); + ReallocAndCopyString(&m_cStructure->strProviderName, providerName.c_str()); } /// @brief To get with @ref SetProviderName changed values. @@ -340,7 +336,7 @@ class PVRSignalStatus : public CStructHdl /// Name of the current mux. void SetMuxName(const std::string& muxName) { - strncpy(m_cStructure->strMuxName, muxName.c_str(), sizeof(m_cStructure->strMuxName) - 1); + ReallocAndCopyString(&m_cStructure->strMuxName, muxName.c_str()); } /// @brief To get with @ref SetMuxName changed values. @@ -379,9 +375,27 @@ class PVRSignalStatus : public CStructHdl long GetUNC() const { return m_cStructure->iUNC; } ///@} + static void AllocResources(const PVR_SIGNAL_STATUS* source, PVR_SIGNAL_STATUS* target) + { + target->strAdapterName = AllocAndCopyString(source->strAdapterName); + target->strAdapterStatus = AllocAndCopyString(source->strAdapterStatus); + target->strServiceName = AllocAndCopyString(source->strServiceName); + target->strProviderName = AllocAndCopyString(source->strProviderName); + target->strMuxName = AllocAndCopyString(source->strMuxName); + } + + static void FreeResources(PVR_SIGNAL_STATUS* target) + { + FreeString(target->strAdapterName); + FreeString(target->strAdapterStatus); + FreeString(target->strServiceName); + FreeString(target->strProviderName); + FreeString(target->strMuxName); + } + private: - PVRSignalStatus(const PVR_SIGNAL_STATUS* type) : CStructHdl(type) {} - PVRSignalStatus(PVR_SIGNAL_STATUS* type) : CStructHdl(type) {} + PVRSignalStatus(const PVR_SIGNAL_STATUS* status) : DynamicCStructHdl(status) {} + PVRSignalStatus(PVR_SIGNAL_STATUS* status) : DynamicCStructHdl(status) {} }; ///@} //------------------------------------------------------------------------------ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h index c97167bfcd8a9..c090e53d8b563 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h @@ -335,6 +335,8 @@ extern "C" unsigned int); enum PVR_ERROR(__cdecl* FreeDescrambleInfo)(const struct AddonInstance_PVR*, struct PVR_DESCRAMBLE_INFO*); + enum PVR_ERROR(__cdecl* FreeSignalStatus)(const struct AddonInstance_PVR*, + struct PVR_SIGNAL_STATUS*); //--==----==----==----==----==----==----==----==----==----==----==----==----== // New functions becomes added below and can be on another API change (where diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h index b6b8131a92150..67b17a5faae5d 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h @@ -62,11 +62,11 @@ extern "C" */ typedef struct PVR_SIGNAL_STATUS { - char strAdapterName[PVR_ADDON_NAME_STRING_LENGTH]; - char strAdapterStatus[PVR_ADDON_NAME_STRING_LENGTH]; - char strServiceName[PVR_ADDON_NAME_STRING_LENGTH]; - char strProviderName[PVR_ADDON_NAME_STRING_LENGTH]; - char strMuxName[PVR_ADDON_NAME_STRING_LENGTH]; + const char* strAdapterName; + const char* strAdapterStatus; + const char* strServiceName; + const char* strProviderName; + const char* strMuxName; int iSNR; int iSignal; long iBER; diff --git a/xbmc/pvr/CMakeLists.txt b/xbmc/pvr/CMakeLists.txt index c5309a9a1963f..b7cbc7d11e750 100644 --- a/xbmc/pvr/CMakeLists.txt +++ b/xbmc/pvr/CMakeLists.txt @@ -27,6 +27,7 @@ set(HEADERS IPVRComponent.h PVRItem.h PVRManager.h PVRPlaybackState.h + PVRSignalStatus.h PVRStreamProperties.h PVRThumbLoader.h) diff --git a/xbmc/pvr/PVRSignalStatus.h b/xbmc/pvr/PVRSignalStatus.h new file mode 100644 index 0000000000000..bd17d33757f46 --- /dev/null +++ b/xbmc/pvr/PVRSignalStatus.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 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 "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" + +#include + +namespace PVR +{ + +class CPVRSignalStatus +{ +public: + CPVRSignalStatus() = default; + + CPVRSignalStatus(const std::string& adapterName, const std::string& adapterStatus) + : m_adapterName(adapterName), m_adapterStatus(adapterStatus) + { + } + + CPVRSignalStatus(const PVR_SIGNAL_STATUS& status) + : m_status(status), + m_adapterName(status.strAdapterName ? status.strAdapterName : ""), + m_adapterStatus(status.strAdapterStatus ? status.strAdapterStatus : ""), + m_serviceName(status.strServiceName ? status.strServiceName : ""), + m_providerName(status.strProviderName ? status.strProviderName : ""), + m_muxName(status.strMuxName ? status.strMuxName : "") + { + } + + virtual ~CPVRSignalStatus() = default; + + int Signal() const { return m_status.iSignal; } + int SNR() const { return m_status.iSNR; } + long UNC() const { return m_status.iUNC; } + long BER() const { return m_status.iBER; } + const std::string& AdapterName() const { return m_adapterName; } + const std::string& AdapterStatus() const { return m_adapterStatus; } + const std::string& ServiceName() const { return m_serviceName; } + const std::string& ProviderName() const { return m_providerName; } + const std::string& MuxName() const { return m_muxName; } + +private: + PVR_SIGNAL_STATUS m_status{}; + std::string m_adapterName; + std::string m_adapterStatus; + std::string m_serviceName; + std::string m_providerName; + std::string m_muxName; +}; + +} // namespace PVR diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 38e5517499623..d9d33af36c499 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -20,6 +20,7 @@ #include "pvr/PVRDatabase.h" #include "pvr/PVRDescrambleInfo.h" #include "pvr/PVRManager.h" +#include "pvr/PVRSignalStatus.h" #include "pvr/PVRStreamProperties.h" #include "pvr/addons/PVRClientMenuHooks.h" #include "pvr/addons/PVRClients.h" @@ -1401,11 +1402,20 @@ PVR_ERROR CPVRClient::GetRecordedStreamLength(int64_t& iLength) const }); } -PVR_ERROR CPVRClient::SignalQuality(int channelUid, PVR_SIGNAL_STATUS& qualityinfo) const +PVR_ERROR CPVRClient::SignalQuality(int channelUid, CPVRSignalStatus& qualityinfo) const { - return DoAddonCall(__func__, [channelUid, &qualityinfo](const AddonInstance* addon) { - return addon->toAddon->GetSignalStatus(addon, channelUid, &qualityinfo); - }); + return DoAddonCall(__func__, + [channelUid, &qualityinfo](const AddonInstance* addon) + { + PVR_SIGNAL_STATUS info{}; + const PVR_ERROR error{ + addon->toAddon->GetSignalStatus(addon, channelUid, &info)}; + if (error == PVR_ERROR_NO_ERROR) + qualityinfo = info; + + addon->toAddon->FreeSignalStatus(addon, &info); + return error; + }); } PVR_ERROR CPVRClient::GetDescrambleInfo(int channelUid, CPVRDescrambleInfo& descrambleinfo) const diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h index 92835a588b18d..b40406c87b07e 100644 --- a/xbmc/pvr/addons/PVRClient.h +++ b/xbmc/pvr/addons/PVRClient.h @@ -38,6 +38,7 @@ class CPVREpg; class CPVREpgInfoTag; class CPVRRecording; class CPVRRecordings; +class CPVRSignalStatus; class CPVRStreamProperties; class CPVRTimerInfoTag; class CPVRTimerType; @@ -572,7 +573,7 @@ class CPVRClient : public ADDON::IAddonInstanceHandler * @param qualityinfo The signal quality. * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. */ - PVR_ERROR SignalQuality(int channelUid, PVR_SIGNAL_STATUS& qualityinfo) const; + PVR_ERROR SignalQuality(int channelUid, CPVRSignalStatus& qualityinfo) const; /*! * @brief Get the descramble information of the stream that's currently open. diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index 0a1ac5036c5bf..9049cf64c3162 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -105,13 +105,9 @@ void CPVRGUIInfo::ResetProperties() m_bRegistered = false; } -void CPVRGUIInfo::ClearQualityInfo(PVR_SIGNAL_STATUS& qualityInfo) +void CPVRGUIInfo::ClearQualityInfo(CPVRSignalStatus& qualityInfo) { - memset(&qualityInfo, 0, sizeof(qualityInfo)); - strncpy(qualityInfo.strAdapterName, g_localizeStrings.Get(13106).c_str(), - PVR_ADDON_NAME_STRING_LENGTH - 1); - strncpy(qualityInfo.strAdapterStatus, g_localizeStrings.Get(13106).c_str(), - PVR_ADDON_NAME_STRING_LENGTH - 1); + qualityInfo = {g_localizeStrings.Get(13106).c_str(), g_localizeStrings.Get(13106).c_str()}; } void CPVRGUIInfo::ClearDescrambleInfo(CPVRDescrambleInfo& descrambleInfo) @@ -240,7 +236,7 @@ void CPVRGUIInfo::UpdateQualityData() if (!playbackState) return; - PVR_SIGNAL_STATUS qualityInfo; + CPVRSignalStatus qualityInfo; ClearQualityInfo(qualityInfo); const int channelUid = playbackState->GetPlayingChannelUniqueID(); @@ -1374,10 +1370,10 @@ bool CPVRGUIInfo::GetPVRInt(const CFileItem* item, const CGUIInfo& info, int& iV iValue = GetTimeShiftSeekPercent(); return true; case PVR_ACTUAL_STREAM_SIG_PROGR: - iValue = std::lrintf(static_cast(m_qualityInfo.iSignal) / 0xFFFF * 100); + iValue = std::lrintf(static_cast(m_qualityInfo.Signal()) / 0xFFFF * 100); return true; case PVR_ACTUAL_STREAM_SNR_PROGR: - iValue = std::lrintf(static_cast(m_qualityInfo.iSNR) / 0xFFFF * 100); + iValue = std::lrintf(static_cast(m_qualityInfo.SNR()) / 0xFFFF * 100); return true; case PVR_BACKEND_DISKSPACE_PROGR: if (m_iBackendDiskTotal > 0) @@ -1839,38 +1835,36 @@ void CPVRGUIInfo::CharInfoTotalDiskSpace(std::string& strValue) const void CPVRGUIInfo::CharInfoSignal(std::string& strValue) const { - strValue = StringUtils::Format("{} %", m_qualityInfo.iSignal / 655); + strValue = StringUtils::Format("{} %", m_qualityInfo.Signal() / 655); } void CPVRGUIInfo::CharInfoSNR(std::string& strValue) const { - strValue = StringUtils::Format("{} %", m_qualityInfo.iSNR / 655); + strValue = StringUtils::Format("{} %", m_qualityInfo.SNR() / 655); } void CPVRGUIInfo::CharInfoBER(std::string& strValue) const { - strValue = StringUtils::Format("{:08X}", m_qualityInfo.iBER); + strValue = StringUtils::Format("{:08X}", m_qualityInfo.BER()); } void CPVRGUIInfo::CharInfoUNC(std::string& strValue) const { - strValue = StringUtils::Format("{:08X}", m_qualityInfo.iUNC); + strValue = StringUtils::Format("{:08X}", m_qualityInfo.UNC()); } void CPVRGUIInfo::CharInfoFrontendName(std::string& strValue) const { - if (!strlen(m_qualityInfo.strAdapterName)) + strValue = m_qualityInfo.AdapterName(); + if (strValue.empty()) strValue = g_localizeStrings.Get(13205); - else - strValue = m_qualityInfo.strAdapterName; } void CPVRGUIInfo::CharInfoFrontendStatus(std::string& strValue) const { - if (!strlen(m_qualityInfo.strAdapterStatus)) + strValue = m_qualityInfo.AdapterStatus(); + if (strValue.empty()) strValue = g_localizeStrings.Get(13205); - else - strValue = m_qualityInfo.strAdapterStatus; } void CPVRGUIInfo::CharInfoBackendName(std::string& strValue) const @@ -1976,26 +1970,23 @@ void CPVRGUIInfo::CharInfoEncryption(std::string& strValue) const void CPVRGUIInfo::CharInfoService(std::string& strValue) const { - if (!strlen(m_qualityInfo.strServiceName)) + strValue = m_qualityInfo.ServiceName(); + if (strValue.empty()) strValue = g_localizeStrings.Get(13205); - else - strValue = m_qualityInfo.strServiceName; } void CPVRGUIInfo::CharInfoMux(std::string& strValue) const { - if (!strlen(m_qualityInfo.strMuxName)) + strValue = m_qualityInfo.MuxName(); + if (strValue.empty()) strValue = g_localizeStrings.Get(13205); - else - strValue = m_qualityInfo.strMuxName; } void CPVRGUIInfo::CharInfoProvider(std::string& strValue) const { - if (!strlen(m_qualityInfo.strProviderName)) + strValue = m_qualityInfo.ProviderName(); + if (strValue.empty()) strValue = g_localizeStrings.Get(13205); - else - strValue = m_qualityInfo.strProviderName; } void CPVRGUIInfo::UpdateBackendCache() diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h index 6cea9ff476d14..50f01d6ec3062 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h @@ -8,9 +8,9 @@ #pragma once -#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" #include "guilib/guiinfo/GUIInfoProvider.h" #include "pvr/PVRDescrambleInfo.h" +#include "pvr/PVRSignalStatus.h" #include "pvr/addons/PVRClients.h" #include "pvr/guilib/guiinfo/PVRGUITimerInfo.h" #include "pvr/guilib/guiinfo/PVRGUITimesInfo.h" @@ -90,7 +90,7 @@ class CPVRGUIInfo : public KODI::GUILIB::GUIINFO::CGUIInfoProvider, private CThr private: void ResetProperties(); - void ClearQualityInfo(PVR_SIGNAL_STATUS& qualityInfo); + void ClearQualityInfo(CPVRSignalStatus& qualityInfo); void ClearDescrambleInfo(CPVRDescrambleInfo& descrambleInfo); void Process() override; @@ -196,7 +196,7 @@ class CPVRGUIInfo : public KODI::GUILIB::GUIINFO::CGUIInfoProvider, private CThr //@} - PVR_SIGNAL_STATUS m_qualityInfo; /*!< stream quality information */ + CPVRSignalStatus m_qualityInfo; /*!< stream quality information */ CPVRDescrambleInfo m_descrambleInfo; /*!< stream descramble information */ std::vector m_backendProperties; From 2f9139c0b7cdc1262375b8a683db986a8873bb90 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 20 Jul 2024 14:47:39 +0200 Subject: [PATCH 330/651] [addons][PVR] PVR add-on API: GetTimerTypes: Replace fixed size PVR_TIMER_TYPE array function parameter with dynamic array. --- .../include/kodi/addon-instance/PVR.h | 21 ++----- .../include/kodi/c-api/addon-instance/pvr.h | 4 +- .../c-api/addon-instance/pvr/pvr_defines.h | 1 - xbmc/pvr/addons/PVRClient.cpp | 55 +++++++++++-------- 4 files changed, 39 insertions(+), 42 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h index 09e668490387d..4eee0499e8be2 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h @@ -3309,36 +3309,25 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance //--==----==----==----==----==----==----==----==----==----==----==----==----== inline static PVR_ERROR ADDON_GetTimerTypes(const AddonInstance_PVR* instance, - PVR_TIMER_TYPE* types, + PVR_TIMER_TYPE*** types, unsigned int* typesCount) { *typesCount = 0; std::vector timerTypes; PVR_ERROR error = static_cast(instance->toAddon->addonInstance) ->GetTimerTypes(timerTypes); - if (error == PVR_ERROR_NO_ERROR) + if (error == PVR_ERROR_NO_ERROR && !timerTypes.empty()) { - for (const auto& timerType : timerTypes) - { - types[*typesCount] = *timerType; - PVRTimerType::AllocResources(timerType, &types[*typesCount]); - ++*typesCount; - if (*typesCount >= PVR_ADDON_TIMERTYPE_ARRAY_SIZE) - break; - } + *types = AllocAndCopyPointerArray(timerTypes, *typesCount); } return error; } inline static PVR_ERROR ADDON_FreeTimerTypes(const AddonInstance_PVR* instance, - PVR_TIMER_TYPE* types, + PVR_TIMER_TYPE** types, unsigned int typesCount) { - for (unsigned int i = 0; i < typesCount; ++i) - { - PVR_TIMER_TYPE* type = &types[i]; - PVRTimerType::FreeResources(type); - } + FreeDynamicPointerArray(types, typesCount); return PVR_ERROR_NO_ERROR; } diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h index c090e53d8b563..c8aa39761d039 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h @@ -265,7 +265,7 @@ extern "C" //--==----==----==----==----==----==----==----==----==----==----==----==----== // Timer interface functions enum PVR_ERROR(__cdecl* GetTimerTypes)(const struct AddonInstance_PVR*, - struct PVR_TIMER_TYPE[], + struct PVR_TIMER_TYPE***, unsigned int*); enum PVR_ERROR(__cdecl* GetTimersAmount)(const struct AddonInstance_PVR*, int*); enum PVR_ERROR(__cdecl* GetTimers)(const struct AddonInstance_PVR*, PVR_HANDLE); @@ -328,7 +328,7 @@ extern "C" enum PVR_ERROR(__cdecl* FreeCapabilities)(const struct AddonInstance_PVR*, struct PVR_ADDON_CAPABILITIES*); enum PVR_ERROR(__cdecl* FreeTimerTypes)(const struct AddonInstance_PVR*, - struct PVR_TIMER_TYPE*, + struct PVR_TIMER_TYPE**, unsigned int); enum PVR_ERROR(__cdecl* FreeProperties)(const struct AddonInstance_PVR*, struct PVR_NAMED_VALUE**, diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h index 7b9d63cc34f7b..5075238b588db 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h @@ -26,7 +26,6 @@ extern "C" ///@{ #define PVR_ADDON_NAME_STRING_LENGTH 1024 #define PVR_ADDON_EDL_LENGTH 64 -#define PVR_ADDON_TIMERTYPE_ARRAY_SIZE 32 ///@} /*! diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index d9d33af36c499..459fc22438ee0 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -1216,14 +1216,14 @@ PVR_ERROR CPVRClient::UpdateTimerTypes() PVR_ERROR retVal = DoAddonCall( __func__, - [this, &timerTypes](const AddonInstance* addon) { - std::unique_ptr types_array( - new PVR_TIMER_TYPE[PVR_ADDON_TIMERTYPE_ARRAY_SIZE]); - unsigned int size = PVR_ADDON_TIMERTYPE_ARRAY_SIZE; - - PVR_ERROR retval = addon->toAddon->GetTimerTypes(addon, types_array.get(), &size); + [this, &timerTypes](const AddonInstance* addon) + { + PVR_TIMER_TYPE** types_array{nullptr}; + unsigned int size{0}; + PVR_ERROR retval{addon->toAddon->GetTimerTypes(addon, &types_array, &size)}; - if (retval == PVR_ERROR_NOT_IMPLEMENTED) + const bool array_owner{retval == PVR_ERROR_NOT_IMPLEMENTED}; + if (array_owner) { // begin compat section CLog::LogF(LOGWARNING, @@ -1238,40 +1238,39 @@ PVR_ERROR CPVRClient::UpdateTimerTypes() // Isengard. Also, new features (like epg search) are not available to addons automatically. // This code can be removed once all addons actually support the respective PVR Addon API version. - size = 0; + size = 2; + if (m_clientCapabilities.SupportsEPG()) + size++; + + types_array = new PVR_TIMER_TYPE*[size]; + // manual one time - memset(&types_array[size], 0, sizeof(types_array[size])); - types_array[size].iId = size + 1; - types_array[size].iAttributes = + (*types_array)[0].iId = 1; + (*types_array)[0].iAttributes = PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | PVR_TIMER_TYPE_SUPPORTS_END_TIME | PVR_TIMER_TYPE_SUPPORTS_PRIORITY | PVR_TIMER_TYPE_SUPPORTS_LIFETIME | PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS; - ++size; // manual timer rule - memset(&types_array[size], 0, sizeof(types_array[size])); - types_array[size].iId = size + 1; - types_array[size].iAttributes = + (*types_array)[1].iId = 2; + (*types_array)[1].iAttributes = PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_IS_REPEATING | PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | PVR_TIMER_TYPE_SUPPORTS_END_TIME | PVR_TIMER_TYPE_SUPPORTS_PRIORITY | PVR_TIMER_TYPE_SUPPORTS_LIFETIME | PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY | PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS | PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS; - ++size; if (m_clientCapabilities.SupportsEPG()) { // One-shot epg-based - memset(&types_array[size], 0, sizeof(types_array[size])); - types_array[size].iId = size + 1; - types_array[size].iAttributes = + (*types_array)[2].iId = 3; + (*types_array)[2].iAttributes = PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE | PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | PVR_TIMER_TYPE_SUPPORTS_END_TIME | PVR_TIMER_TYPE_SUPPORTS_PRIORITY | PVR_TIMER_TYPE_SUPPORTS_LIFETIME | PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS; - ++size; } retval = PVR_ERROR_NO_ERROR; @@ -1283,17 +1282,27 @@ PVR_ERROR CPVRClient::UpdateTimerTypes() timerTypes.reserve(size); for (unsigned int i = 0; i < size; ++i) { - if (types_array[i].iId == PVR_TIMER_TYPE_NONE) + if (types_array[i]->iId == PVR_TIMER_TYPE_NONE) { CLog::LogF(LOGERROR, "Invalid timer type supplied by add-on {}.", GetID()); continue; } - timerTypes.emplace_back(std::make_shared(types_array[i], m_iClientId)); + timerTypes.emplace_back(std::make_shared(*types_array[i], m_iClientId)); } } /* free the resources of the timer types array */ - addon->toAddon->FreeTimerTypes(addon, types_array.get(), size); + if (array_owner) + { + // begin compat section + delete[] types_array; + // end compat section + } + else + { + addon->toAddon->FreeTimerTypes(addon, types_array, size); + } + types_array = nullptr; return retval; }, From 42f4a5a6c790d68040c25c5b389c7c118a682de5 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:03:46 +0200 Subject: [PATCH 331/651] [PVR] Rework encapsulation for add-on supplied EDL. --- xbmc/pvr/PVREdl.cpp | 40 +++------------------------- xbmc/pvr/addons/PVRClient.cpp | 38 +++++++++++++++++++++++--- xbmc/pvr/addons/PVRClient.h | 9 +++++-- xbmc/pvr/epg/EpgInfoTag.cpp | 5 ++-- xbmc/pvr/epg/EpgInfoTag.h | 8 ++++-- xbmc/pvr/recordings/PVRRecording.cpp | 5 ++-- xbmc/pvr/recordings/PVRRecording.h | 8 ++++-- 7 files changed, 62 insertions(+), 51 deletions(-) diff --git a/xbmc/pvr/PVREdl.cpp b/xbmc/pvr/PVREdl.cpp index aa47cbed27f6f..dad03d816424b 100644 --- a/xbmc/pvr/PVREdl.cpp +++ b/xbmc/pvr/PVREdl.cpp @@ -9,62 +9,28 @@ #include "PVREdl.h" #include "FileItem.h" -#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_edl.h" #include "cores/EdlEdit.h" #include "pvr/epg/EpgInfoTag.h" #include "pvr/recordings/PVRRecording.h" #include "utils/log.h" -#include - namespace PVR { std::vector CPVREdl::GetEdits(const CFileItem& item) { - std::vector edl; - if (item.HasPVRRecordingInfoTag()) { CLog::LogFC(LOGDEBUG, LOGPVR, "Reading EDL for recording: {}", item.GetPVRRecordingInfoTag()->m_strTitle); - edl = item.GetPVRRecordingInfoTag()->GetEdl(); + return item.GetPVRRecordingInfoTag()->GetEdl(); } else if (item.HasEPGInfoTag()) { CLog::LogFC(LOGDEBUG, LOGPVR, "Reading EDL for EPG tag: {}", item.GetEPGInfoTag()->Title()); - edl = item.GetEPGInfoTag()->GetEdl(); - } - - std::vector editlist; - for (const auto& entry : edl) - { - EDL::Edit edit; - edit.start = std::chrono::milliseconds(entry.start); - edit.end = std::chrono::milliseconds(entry.end); - - switch (entry.type) - { - case PVR_EDL_TYPE_CUT: - edit.action = EDL::Action::CUT; - break; - case PVR_EDL_TYPE_MUTE: - edit.action = EDL::Action::MUTE; - break; - case PVR_EDL_TYPE_SCENE: - edit.action = EDL::Action::SCENE; - break; - case PVR_EDL_TYPE_COMBREAK: - edit.action = EDL::Action::COMM_BREAK; - break; - default: - CLog::LogF(LOGWARNING, "Ignoring entry of unknown EDL type: {}", entry.type); - continue; - } - - editlist.emplace_back(edit); + return item.GetEPGInfoTag()->GetEdl(); } - return editlist; + return {}; } } // namespace PVR diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 459fc22438ee0..1652f2b50bf1f 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -11,6 +11,7 @@ #include "ServiceBroker.h" #include "addons/AddonManager.h" #include "addons/binary-addons/AddonDll.h" +#include "cores/EdlEdit.h" #include "cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.h" #include "dialogs/GUIDialogKaiToast.h" //! @todo get rid of GUI in core #include "events/EventLog.h" @@ -45,6 +46,7 @@ #include "utils/StringUtils.h" #include "utils/log.h" +#include #include #include #include @@ -236,6 +238,34 @@ class CAddonTimer : public PVR_TIMER const std::string m_summary; const std::string m_seriesLink; }; + +EDL::Edit ConvertAddonEdl(const PVR_EDL_ENTRY& entry) +{ + EDL::Edit edit; + edit.start = std::chrono::milliseconds(entry.start); + edit.end = std::chrono::milliseconds(entry.end); + + switch (entry.type) + { + case PVR_EDL_TYPE_CUT: + edit.action = EDL::Action::CUT; + break; + case PVR_EDL_TYPE_MUTE: + edit.action = EDL::Action::MUTE; + break; + case PVR_EDL_TYPE_SCENE: + edit.action = EDL::Action::SCENE; + break; + case PVR_EDL_TYPE_COMBREAK: + edit.action = EDL::Action::COMM_BREAK; + break; + default: + CLog::LogF(LOGWARNING, "Ignoring entry of unknown EDL type: {}", entry.type); + break; + } + + return edit; +} } // unnamed namespace namespace PVR @@ -874,7 +904,7 @@ PVR_ERROR CPVRClient::GetEpgTagStreamProperties(const std::shared_ptr& epgTag, - std::vector& edls) const + std::vector& edls) const { edls.clear(); return DoAddonCall( @@ -889,7 +919,7 @@ PVR_ERROR CPVRClient::GetEpgTagEdl(const std::shared_ptr& { edls.reserve(size); for (int i = 0; i < size; ++i) - edls.emplace_back(edl_array[i]); + edls.emplace_back(ConvertAddonEdl(edl_array[i])); } return error; }, @@ -1110,7 +1140,7 @@ PVR_ERROR CPVRClient::GetRecordingLastPlayedPosition(const CPVRRecording& record } PVR_ERROR CPVRClient::GetRecordingEdl(const CPVRRecording& recording, - std::vector& edls) const + std::vector& edls) const { edls.clear(); return DoAddonCall( @@ -1126,7 +1156,7 @@ PVR_ERROR CPVRClient::GetRecordingEdl(const CPVRRecording& recording, { edls.reserve(size); for (int i = 0; i < size; ++i) - edls.emplace_back(edl_array[i]); + edls.emplace_back(ConvertAddonEdl(edl_array[i])); } return error; }, diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h index b40406c87b07e..30417dfffbf9b 100644 --- a/xbmc/pvr/addons/PVRClient.h +++ b/xbmc/pvr/addons/PVRClient.h @@ -23,6 +23,11 @@ struct DemuxPacket; +namespace EDL +{ +struct Edit; +} + namespace PVR { class CPVRChannel; @@ -447,7 +452,7 @@ class CPVRClient : public ADDON::IAddonInstanceHandler * @param edls The edit decision list (empty on error). * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. */ - PVR_ERROR GetRecordingEdl(const CPVRRecording& recording, std::vector& edls) const; + PVR_ERROR GetRecordingEdl(const CPVRRecording& recording, std::vector& edls) const; /*! * @brief Retrieve the size of a recording on the backend. @@ -464,7 +469,7 @@ class CPVRClient : public ADDON::IAddonInstanceHandler * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. */ PVR_ERROR GetEpgTagEdl(const std::shared_ptr& epgTag, - std::vector& edls) const; + std::vector& edls) const; //@} /** @name PVR timer methods */ diff --git a/xbmc/pvr/epg/EpgInfoTag.cpp b/xbmc/pvr/epg/EpgInfoTag.cpp index 0b9cef0d01d77..0b84e88c2e5df 100644 --- a/xbmc/pvr/epg/EpgInfoTag.cpp +++ b/xbmc/pvr/epg/EpgInfoTag.cpp @@ -9,6 +9,7 @@ #include "EpgInfoTag.h" #include "ServiceBroker.h" +#include "cores/EdlEdit.h" #include "pvr/PVRManager.h" #include "pvr/PVRPlaybackState.h" #include "pvr/addons/PVRClient.h" @@ -570,9 +571,9 @@ bool CPVREpgInfoTag::QueuePersistQuery(const std::shared_ptr& d return database->QueuePersistQuery(*this); } -std::vector CPVREpgInfoTag::GetEdl() const +std::vector CPVREpgInfoTag::GetEdl() const { - std::vector edls; + std::vector edls; std::unique_lock lock(m_critSection); const std::shared_ptr client = diff --git a/xbmc/pvr/epg/EpgInfoTag.h b/xbmc/pvr/epg/EpgInfoTag.h index 89f2e0c0cbb30..f08f44821b35b 100644 --- a/xbmc/pvr/epg/EpgInfoTag.h +++ b/xbmc/pvr/epg/EpgInfoTag.h @@ -17,8 +17,12 @@ #include #include +namespace EDL +{ +struct Edit; +} + struct EPG_TAG; -struct PVR_EDL_ENTRY; namespace PVR { @@ -391,7 +395,7 @@ class CPVREpgInfoTag final : public ISerializable, * @brief Retrieve the edit decision list (EDL) of an EPG tag. * @return The edit decision list (empty on error) */ - std::vector GetEdl() const; + std::vector GetEdl() const; /*! * @brief Check whether this tag has any series attributes. diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp index 38c288d5f956d..2f1b09ddb7db8 100644 --- a/xbmc/pvr/recordings/PVRRecording.cpp +++ b/xbmc/pvr/recordings/PVRRecording.cpp @@ -10,6 +10,7 @@ #include "ServiceBroker.h" #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h" +#include "cores/EdlEdit.h" #include "guilib/LocalizeStrings.h" #include "pvr/PVRManager.h" #include "pvr/addons/PVRClient.h" @@ -395,9 +396,9 @@ void CPVRRecording::UpdateMetadata(CVideoDatabase& db, const CPVRClient& client) m_bGotMetaData = true; } -std::vector CPVRRecording::GetEdl() const +std::vector CPVRRecording::GetEdl() const { - std::vector edls; + std::vector edls; const std::shared_ptr client = CServiceBroker::GetPVRManager().GetClient(m_iClientId); diff --git a/xbmc/pvr/recordings/PVRRecording.h b/xbmc/pvr/recordings/PVRRecording.h index 1a7e438111628..bf8ca07188491 100644 --- a/xbmc/pvr/recordings/PVRRecording.h +++ b/xbmc/pvr/recordings/PVRRecording.h @@ -22,7 +22,11 @@ class CVideoDatabase; -struct PVR_EDL_ENTRY; +namespace EDL +{ +struct Edit; +} + struct PVR_RECORDING; namespace PVR @@ -155,7 +159,7 @@ class CPVRRecording final : public CVideoInfoTag * @brief Retrieve the edit decision list (EDL) of a recording on the backend. * @return The edit decision list (empty on error) */ - std::vector GetEdl() const; + std::vector GetEdl() const; /*! * @brief Get the resume point and play count from the database if the From 30b8391266c8455bb627dc2b44152421c6cb0f26 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:55:02 +0200 Subject: [PATCH 332/651] [addons][PVR] PVR add-on API: Get(Recording|EpgTag)Edl: Replace fixed size PVR_EDL_ENTRY array function parameter with dynamic array. --- .../kodi-dev-kit/include/kodi/AddonBase.h | 20 +++++++ .../include/kodi/addon-instance/PVR.h | 53 +++++++------------ .../include/kodi/c-api/addon-instance/pvr.h | 11 ++-- .../c-api/addon-instance/pvr/pvr_defines.h | 1 - xbmc/pvr/addons/PVRClient.cpp | 22 ++++---- 5 files changed, 58 insertions(+), 49 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h index 25bbcd8a4bc46..00567e7b8e24b 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h @@ -148,6 +148,20 @@ inline static void FreeDynamicPointerArray(C_STRUCT** targetArray, unsigned int delete[] targetArray; } +/*! + * @brief Internally used helper to free an array of of c-struct pointers. + */ +template +inline static void FreeStaticPointerArray(C_STRUCT** targetArray, unsigned int targetArraySize) +{ + for (unsigned int i = 0; i < targetArraySize; ++i) + { + C_STRUCT** arrayElem{&targetArray[i]}; + delete *arrayElem; + } + delete[] targetArray; +} + /* * Internally used helper class to manage processing of a "C" structure in "CPP" * class. @@ -254,6 +268,12 @@ class CStructHdl const C_STRUCT* GetCStructure() const { return m_cStructure; } + C_STRUCT* release() + { + m_owner = false; + return m_cStructure; + } + protected: C_STRUCT* m_cStructure = nullptr; diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h index 4eee0499e8be2..29f4cc7d6a989 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h @@ -2831,6 +2831,7 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance instance->pvr->toAddon->FreeProperties = ADDON_FreeProperties; instance->pvr->toAddon->FreeDescrambleInfo = ADDON_FreeDescrambleInfo; instance->pvr->toAddon->FreeSignalStatus = ADDON_FreeSignalStatus; + instance->pvr->toAddon->FreeEdlEntries = ADDON_FreeEdlEntries; m_instanceData = instance->pvr; m_instanceData->toAddon->addonInstance = this; @@ -3096,28 +3097,16 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance inline static PVR_ERROR ADDON_GetEPGTagEdl(const AddonInstance_PVR* instance, const EPG_TAG* tag, - PVR_EDL_ENTRY* edl, - int* size) + PVR_EDL_ENTRY*** edls, + unsigned int* size) { + *size = 0; std::vector edlList; PVR_ERROR error = static_cast(instance->toAddon->addonInstance) ->GetEPGTagEdl(tag, edlList); - if (static_cast(edlList.size()) > *size) - { - kodi::Log( - ADDON_LOG_WARNING, - "CInstancePVRClient::%s: Truncating %d EDL entries from client to permitted size %d", - __func__, static_cast(edlList.size()), *size); - edlList.resize(*size); - } - *size = 0; - if (error == PVR_ERROR_NO_ERROR) + if (error == PVR_ERROR_NO_ERROR && !edlList.empty()) { - for (const auto& edlEntry : edlList) - { - edl[*size] = *edlEntry; - ++*size; - } + *edls = AllocAndCopyPointerArray(edlList, *size); } return error; } @@ -3239,32 +3228,28 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance inline static PVR_ERROR ADDON_GetRecordingEdl(const AddonInstance_PVR* instance, const PVR_RECORDING* recording, - PVR_EDL_ENTRY* edl, - int* size) + PVR_EDL_ENTRY*** edls, + unsigned int* size) { + *size = 0; std::vector edlList; PVR_ERROR error = static_cast(instance->toAddon->addonInstance) ->GetRecordingEdl(recording, edlList); - if (static_cast(edlList.size()) > *size) - { - kodi::Log( - ADDON_LOG_WARNING, - "CInstancePVRClient::%s: Truncating %d EDL entries from client to permitted size %d", - __func__, static_cast(edlList.size()), *size); - edlList.resize(*size); - } - *size = 0; - if (error == PVR_ERROR_NO_ERROR) + if (error == PVR_ERROR_NO_ERROR && !edlList.empty()) { - for (const auto& edlEntry : edlList) - { - edl[*size] = *edlEntry; - ++*size; - } + *edls = AllocAndCopyPointerArray(edlList, *size); } return error; } + inline static PVR_ERROR ADDON_FreeEdlEntries(const AddonInstance_PVR* instance, + PVR_EDL_ENTRY** edls, + unsigned int size) + { + FreeStaticPointerArray(edls, size); + return PVR_ERROR_NO_ERROR; + } + inline static PVR_ERROR ADDON_GetRecordingSize(const AddonInstance_PVR* instance, const PVR_RECORDING* recording, int64_t* size) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h index c8aa39761d039..09030727648f9 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h @@ -213,8 +213,8 @@ extern "C" bool*); enum PVR_ERROR(__cdecl* GetEPGTagEdl)(const struct AddonInstance_PVR*, const struct EPG_TAG*, - struct PVR_EDL_ENTRY[], - int*); + struct PVR_EDL_ENTRY***, + unsigned int*); enum PVR_ERROR(__cdecl* GetEPGTagStreamProperties)(const struct AddonInstance_PVR*, const struct EPG_TAG*, struct PVR_NAMED_VALUE***, @@ -249,8 +249,8 @@ extern "C" int*); enum PVR_ERROR(__cdecl* GetRecordingEdl)(const struct AddonInstance_PVR*, const struct PVR_RECORDING*, - struct PVR_EDL_ENTRY[], - int*); + struct PVR_EDL_ENTRY***, + unsigned int*); enum PVR_ERROR(__cdecl* GetRecordingSize)(const struct AddonInstance_PVR*, const PVR_RECORDING*, int64_t*); @@ -337,6 +337,9 @@ extern "C" struct PVR_DESCRAMBLE_INFO*); enum PVR_ERROR(__cdecl* FreeSignalStatus)(const struct AddonInstance_PVR*, struct PVR_SIGNAL_STATUS*); + enum PVR_ERROR(__cdecl* FreeEdlEntries)(const struct AddonInstance_PVR*, + struct PVR_EDL_ENTRY**, + unsigned int); //--==----==----==----==----==----==----==----==----==----==----==----==----== // New functions becomes added below and can be on another API change (where diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h index 5075238b588db..5f4f070b0e3a4 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h @@ -25,7 +25,6 @@ extern "C" */ ///@{ #define PVR_ADDON_NAME_STRING_LENGTH 1024 -#define PVR_ADDON_EDL_LENGTH 64 ///@} /*! diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 1652f2b50bf1f..184c544945274 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -912,15 +912,16 @@ PVR_ERROR CPVRClient::GetEpgTagEdl(const std::shared_ptr& [&epgTag, &edls](const AddonInstance* addon) { CAddonEpgTag addonTag(epgTag); - PVR_EDL_ENTRY edl_array[PVR_ADDON_EDL_LENGTH]; - int size = PVR_ADDON_EDL_LENGTH; - PVR_ERROR error = addon->toAddon->GetEPGTagEdl(addon, &addonTag, edl_array, &size); + PVR_EDL_ENTRY** edl_array{nullptr}; + unsigned int size{0}; + const PVR_ERROR error{addon->toAddon->GetEPGTagEdl(addon, &addonTag, &edl_array, &size)}; if (error == PVR_ERROR_NO_ERROR) { edls.reserve(size); - for (int i = 0; i < size; ++i) - edls.emplace_back(ConvertAddonEdl(edl_array[i])); + for (unsigned int i = 0; i < size; ++i) + edls.emplace_back(ConvertAddonEdl(*edl_array[i])); } + addon->toAddon->FreeEdlEntries(addon, edl_array, size); return error; }, m_clientCapabilities.SupportsEpgTagEdl()); @@ -1149,15 +1150,16 @@ PVR_ERROR CPVRClient::GetRecordingEdl(const CPVRRecording& recording, { const CAddonRecording tag{recording}; - PVR_EDL_ENTRY edl_array[PVR_ADDON_EDL_LENGTH]; - int size = PVR_ADDON_EDL_LENGTH; - PVR_ERROR error = addon->toAddon->GetRecordingEdl(addon, &tag, edl_array, &size); + PVR_EDL_ENTRY** edl_array{nullptr}; + unsigned int size{0}; + const PVR_ERROR error{addon->toAddon->GetRecordingEdl(addon, &tag, &edl_array, &size)}; if (error == PVR_ERROR_NO_ERROR) { edls.reserve(size); - for (int i = 0; i < size; ++i) - edls.emplace_back(ConvertAddonEdl(edl_array[i])); + for (unsigned int i = 0; i < size; ++i) + edls.emplace_back(ConvertAddonEdl(*edl_array[i])); } + addon->toAddon->FreeEdlEntries(addon, edl_array, size); return error; }, m_clientCapabilities.SupportsRecordingsEdl()); From 403e0c0f2ccc74e4af6cfe2f6167eb22a8f7e6b9 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 22 Jul 2024 23:06:59 +0200 Subject: [PATCH 333/651] [addons][PVR] PVR add-on API: GetBackend(Name|Version|Hostname), GetConnectionString: Replace fixed size char array function parameter with dynamic array. --- .../include/kodi/addon-instance/PVR.h | 31 +++++----- .../include/kodi/c-api/addon-instance/pvr.h | 9 +-- .../c-api/addon-instance/pvr/pvr_defines.h | 8 --- xbmc/pvr/addons/PVRClient.cpp | 59 +++++++++++++------ 4 files changed, 60 insertions(+), 47 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h index 29f4cc7d6a989..2f0a1bea3bc97 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h @@ -2832,6 +2832,7 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance instance->pvr->toAddon->FreeDescrambleInfo = ADDON_FreeDescrambleInfo; instance->pvr->toAddon->FreeSignalStatus = ADDON_FreeSignalStatus; instance->pvr->toAddon->FreeEdlEntries = ADDON_FreeEdlEntries; + instance->pvr->toAddon->FreeString = ADDON_FreeString; m_instanceData = instance->pvr; m_instanceData->toAddon->addonInstance = this; @@ -2852,51 +2853,43 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance return PVR_ERROR_NO_ERROR; } - inline static PVR_ERROR ADDON_GetBackendName(const AddonInstance_PVR* instance, - char* str, - int memSize) + inline static PVR_ERROR ADDON_GetBackendName(const AddonInstance_PVR* instance, char** str) { std::string backendName; PVR_ERROR err = static_cast(instance->toAddon->addonInstance) ->GetBackendName(backendName); if (err == PVR_ERROR_NO_ERROR) - strncpy(str, backendName.c_str(), memSize); + *str = AllocAndCopyString(backendName.c_str()); return err; } - inline static PVR_ERROR ADDON_GetBackendVersion(const AddonInstance_PVR* instance, - char* str, - int memSize) + inline static PVR_ERROR ADDON_GetBackendVersion(const AddonInstance_PVR* instance, char** str) { std::string backendVersion; PVR_ERROR err = static_cast(instance->toAddon->addonInstance) ->GetBackendVersion(backendVersion); if (err == PVR_ERROR_NO_ERROR) - strncpy(str, backendVersion.c_str(), memSize); + *str = AllocAndCopyString(backendVersion.c_str()); return err; } - inline static PVR_ERROR ADDON_GetBackendHostname(const AddonInstance_PVR* instance, - char* str, - int memSize) + inline static PVR_ERROR ADDON_GetBackendHostname(const AddonInstance_PVR* instance, char** str) { std::string backendHostname; PVR_ERROR err = static_cast(instance->toAddon->addonInstance) ->GetBackendHostname(backendHostname); if (err == PVR_ERROR_NO_ERROR) - strncpy(str, backendHostname.c_str(), memSize); + *str = AllocAndCopyString(backendHostname.c_str()); return err; } - inline static PVR_ERROR ADDON_GetConnectionString(const AddonInstance_PVR* instance, - char* str, - int memSize) + inline static PVR_ERROR ADDON_GetConnectionString(const AddonInstance_PVR* instance, char** str) { std::string connectionString; PVR_ERROR err = static_cast(instance->toAddon->addonInstance) ->GetConnectionString(connectionString); if (err == PVR_ERROR_NO_ERROR) - strncpy(str, connectionString.c_str(), memSize); + *str = AllocAndCopyString(connectionString.c_str()); return err; } @@ -3552,6 +3545,12 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance return static_cast(instance->toAddon->addonInstance) ->GetStreamTimes(cppTimes); } + + inline static PVR_ERROR ADDON_FreeString(const AddonInstance_PVR* instance, char* str) + { + FreeString(str); + return PVR_ERROR_NO_ERROR; + } ///@} AddonInstance_PVR* m_instanceData = nullptr; diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h index 09030727648f9..d32df12c197c0 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h @@ -149,10 +149,10 @@ extern "C" // General interface functions enum PVR_ERROR(__cdecl* GetCapabilities)(const struct AddonInstance_PVR*, struct PVR_ADDON_CAPABILITIES*); - enum PVR_ERROR(__cdecl* GetBackendName)(const struct AddonInstance_PVR*, char*, int); - enum PVR_ERROR(__cdecl* GetBackendVersion)(const struct AddonInstance_PVR*, char*, int); - enum PVR_ERROR(__cdecl* GetBackendHostname)(const struct AddonInstance_PVR*, char*, int); - enum PVR_ERROR(__cdecl* GetConnectionString)(const struct AddonInstance_PVR*, char*, int); + enum PVR_ERROR(__cdecl* GetBackendName)(const struct AddonInstance_PVR*, char**); + enum PVR_ERROR(__cdecl* GetBackendVersion)(const struct AddonInstance_PVR*, char**); + enum PVR_ERROR(__cdecl* GetBackendHostname)(const struct AddonInstance_PVR*, char**); + enum PVR_ERROR(__cdecl* GetConnectionString)(const struct AddonInstance_PVR*, char**); enum PVR_ERROR(__cdecl* GetDriveSpace)(const struct AddonInstance_PVR*, uint64_t*, uint64_t*); enum PVR_ERROR(__cdecl* CallSettingsMenuHook)(const struct AddonInstance_PVR*, const struct PVR_MENUHOOK*); @@ -340,6 +340,7 @@ extern "C" enum PVR_ERROR(__cdecl* FreeEdlEntries)(const struct AddonInstance_PVR*, struct PVR_EDL_ENTRY**, unsigned int); + enum PVR_ERROR(__cdecl* FreeString)(const struct AddonInstance_PVR*, char*); //--==----==----==----==----==----==----==----==----==----==----==----==----== // New functions becomes added below and can be on another API change (where diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h index 5f4f070b0e3a4..3c64e9dd7ff5c 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h @@ -19,14 +19,6 @@ extern "C" { #endif /* __cplusplus */ - /*! - * @brief API array sizes which are used for data exchange between - * Kodi and addon. - */ - ///@{ -#define PVR_ADDON_NAME_STRING_LENGTH 1024 - ///@} - /*! * @brief "C" Representation of a general attribute integer value. */ diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 184c544945274..abdad03aca196 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -544,16 +544,22 @@ bool CPVRClient::GetAddonProperties() bool CPVRClient::GetAddonNameStringProperties() { - char strBackendName[PVR_ADDON_NAME_STRING_LENGTH] = {}; - char strConnectionString[PVR_ADDON_NAME_STRING_LENGTH] = {}; - char strBackendVersion[PVR_ADDON_NAME_STRING_LENGTH] = {}; - char strBackendHostname[PVR_ADDON_NAME_STRING_LENGTH] = {}; + std::string backendName; + std::string connectionString; + std::string backendVersion; + std::string backendHostname; /* get the name of the backend */ PVR_ERROR retVal = DoAddonCall( __func__, - [&strBackendName](const AddonInstance* addon) { - return addon->toAddon->GetBackendName(addon, strBackendName, sizeof(strBackendName)); + [&backendName](const AddonInstance* addon) + { + char* strBackendName{nullptr}; + const PVR_ERROR error{addon->toAddon->GetBackendName(addon, &strBackendName)}; + if (error == PVR_ERROR_NO_ERROR && strBackendName != nullptr) + backendName = strBackendName; + addon->toAddon->FreeString(addon, strBackendName); + return error; }, true, false); @@ -563,9 +569,14 @@ bool CPVRClient::GetAddonNameStringProperties() /* get the connection string */ retVal = DoAddonCall( __func__, - [&strConnectionString](const AddonInstance* addon) { - return addon->toAddon->GetConnectionString(addon, strConnectionString, - sizeof(strConnectionString)); + [&connectionString](const AddonInstance* addon) + { + char* strConnectionString{nullptr}; + const PVR_ERROR error{addon->toAddon->GetConnectionString(addon, &strConnectionString)}; + if (error == PVR_ERROR_NO_ERROR && strConnectionString != nullptr) + connectionString = strConnectionString; + addon->toAddon->FreeString(addon, strConnectionString); + return error; }, true, false); @@ -575,9 +586,14 @@ bool CPVRClient::GetAddonNameStringProperties() /* backend version number */ retVal = DoAddonCall( __func__, - [&strBackendVersion](const AddonInstance* addon) { - return addon->toAddon->GetBackendVersion(addon, strBackendVersion, - sizeof(strBackendVersion)); + [&backendVersion](const AddonInstance* addon) + { + char* strBackendVersion{nullptr}; + const PVR_ERROR error{addon->toAddon->GetBackendVersion(addon, &strBackendVersion)}; + if (error == PVR_ERROR_NO_ERROR && strBackendVersion != nullptr) + backendVersion = strBackendVersion; + addon->toAddon->FreeString(addon, strBackendVersion); + return error; }, true, false); @@ -587,9 +603,14 @@ bool CPVRClient::GetAddonNameStringProperties() /* backend hostname */ retVal = DoAddonCall( __func__, - [&strBackendHostname](const AddonInstance* addon) { - return addon->toAddon->GetBackendHostname(addon, strBackendHostname, - sizeof(strBackendHostname)); + [&backendHostname](const AddonInstance* addon) + { + char* strBackendHostname{nullptr}; + const PVR_ERROR error{addon->toAddon->GetBackendHostname(addon, &strBackendHostname)}; + if (error == PVR_ERROR_NO_ERROR && strBackendHostname != nullptr) + backendHostname = strBackendHostname; + addon->toAddon->FreeString(addon, strBackendHostname); + return error; }, true, false); @@ -598,10 +619,10 @@ bool CPVRClient::GetAddonNameStringProperties() /* update the members */ std::unique_lock lock(m_critSection); - m_strBackendName = strBackendName; - m_strConnectionString = strConnectionString; - m_strBackendVersion = strBackendVersion; - m_strBackendHostname = strBackendHostname; + m_strBackendName = backendName; + m_strConnectionString = connectionString; + m_strBackendVersion = backendVersion; + m_strBackendHostname = backendHostname; return true; } From 0c2849ca4c4e1a42f8c2d85d1a7ec1c80606742d Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 27 Jul 2024 19:20:11 +0200 Subject: [PATCH 334/651] [addons] Bump ADDON_INSTANCE_VERSION_PVR and ADDON_INSTANCE_VERSION_PVR_MIN to 8.4.0 --- xbmc/addons/kodi-dev-kit/include/kodi/versions.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h index 1eae8133fbb6a..4b8a49e9b7476 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h @@ -130,11 +130,10 @@ #define ADDON_INSTANCE_VERSION_PERIPHERAL_DEPENDS "addon-instance/Peripheral.h" \ "addon-instance/PeripheralUtils.h" -#define ADDON_INSTANCE_VERSION_PVR "8.3.0" -#define ADDON_INSTANCE_VERSION_PVR_MIN "8.2.0" +#define ADDON_INSTANCE_VERSION_PVR "8.4.0" +#define ADDON_INSTANCE_VERSION_PVR_MIN "8.4.0" #define ADDON_INSTANCE_VERSION_PVR_XML_ID "kodi.binary.instance.pvr" #define ADDON_INSTANCE_VERSION_PVR_DEPENDS "c-api/addon-instance/pvr.h" \ - "c-api/addon-instance/pvr/pvr_providers.h" \ "c-api/addon-instance/pvr/pvr_channel_groups.h" \ "c-api/addon-instance/pvr/pvr_channels.h" \ "c-api/addon-instance/pvr/pvr_defines.h" \ @@ -142,6 +141,7 @@ "c-api/addon-instance/pvr/pvr_epg.h" \ "c-api/addon-instance/pvr/pvr_general.h" \ "c-api/addon-instance/pvr/pvr_menu_hook.h" \ + "c-api/addon-instance/pvr/pvr_providers.h" \ "c-api/addon-instance/pvr/pvr_recordings.h" \ "c-api/addon-instance/pvr/pvr_stream.h" \ "c-api/addon-instance/pvr/pvr_timers.h" \ @@ -152,6 +152,7 @@ "addon-instance/pvr/EPG.h" \ "addon-instance/pvr/General.h" \ "addon-instance/pvr/MenuHook.h" \ + "addon-instance/pvr/Providers.h" \ "addon-instance/pvr/Recordings.h" \ "addon-instance/pvr/Stream.h" \ "addon-instance/pvr/Timers.h" From 7dd3509d14b8f59ee16ee9fc75041ded4fa226a2 Mon Sep 17 00:00:00 2001 From: MikeSiLVO Date: Thu, 1 Aug 2024 07:01:22 -0400 Subject: [PATCH 335/651] [Estuary] Add widget "editor" (#25355) * Combine settings dialogs * Add widget "editor" * Move Edit categories buttons to DialogSettings * Use SettingsDialog to not confuse with DialogSettings.xml * Use _no_ for skin settings * Consistent button ids --- .../resource.language.en_gb/strings.po | 5 +- .../xml/Custom_1101_SettingsDialog.xml | 45 ++ .../xml/Custom_1101_SettingsList.xml | 191 -------- .../xml/Custom_1105_MusicOSDSettings.xml | 79 --- addons/skin.estuary/xml/GameOSD.xml | 4 +- addons/skin.estuary/xml/Home.xml | 100 ++-- addons/skin.estuary/xml/Includes.xml | 1 + addons/skin.estuary/xml/Includes_Buttons.xml | 2 + .../xml/Includes_SettingsDialog.xml | 452 ++++++++++++++++++ addons/skin.estuary/xml/MusicOSD.xml | 4 +- addons/skin.estuary/xml/SkinSettings.xml | 67 ++- addons/skin.estuary/xml/Timers.xml | 4 +- addons/skin.estuary/xml/VideoOSD.xml | 8 +- 13 files changed, 611 insertions(+), 351 deletions(-) create mode 100644 addons/skin.estuary/xml/Custom_1101_SettingsDialog.xml delete mode 100644 addons/skin.estuary/xml/Custom_1101_SettingsList.xml delete mode 100644 addons/skin.estuary/xml/Custom_1105_MusicOSDSettings.xml create mode 100644 addons/skin.estuary/xml/Includes_SettingsDialog.xml diff --git a/addons/skin.estuary/language/resource.language.en_gb/strings.po b/addons/skin.estuary/language/resource.language.en_gb/strings.po index 3bdeed72f60d7..229f0b95e51b8 100644 --- a/addons/skin.estuary/language/resource.language.en_gb/strings.po +++ b/addons/skin.estuary/language/resource.language.en_gb/strings.po @@ -232,7 +232,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" -#empty string with id 31040 +#: /xml/SkinSettings.xml +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" #: /xml/Variables.xml /xml/MyPics.xml msgctxt "#31041" diff --git a/addons/skin.estuary/xml/Custom_1101_SettingsDialog.xml b/addons/skin.estuary/xml/Custom_1101_SettingsDialog.xml new file mode 100644 index 0000000000000..87ca34616d36c --- /dev/null +++ b/addons/skin.estuary/xml/Custom_1101_SettingsDialog.xml @@ -0,0 +1,45 @@ + + + 11000 + Animation_DialogPopupOpenClose + ClearProperty(settingsdialog_header,Home) + ClearProperty(settingsdialog_content,Home) + + + 50% + 700 + 50% + 700 + SettingsDialogOSDVisible + VisibleChange + + + + + + + + 0 + 80 + + 700 + 700 + 0 + 11100 + 11100 + vertical + SettingsDialogOSDContent + SettingsDialog3DContent + SettingsDialogGameContent + SettingsDialogMusicOSDContent + SettingsDialogMovieWidgetsContent + SettingsDialogTVShowWidgetsContent + SettingsDialogMusicWidgetsContent + SettingsDialogMusicVideoWidgetsContent + SettingsDialogTVWidgetsContent + SettingsDialogRadioWidgetsContent + + + + + diff --git a/addons/skin.estuary/xml/Custom_1101_SettingsList.xml b/addons/skin.estuary/xml/Custom_1101_SettingsList.xml deleted file mode 100644 index e15a42e39de92..0000000000000 --- a/addons/skin.estuary/xml/Custom_1101_SettingsList.xml +++ /dev/null @@ -1,191 +0,0 @@ - - - 11000 - Animation_DialogPopupOpenClose - ClearProperty(settingslist_header,Home) - ClearProperty(settingslist_content,Home) - - - 50% - 670 - 50% - 700 - !Window.IsActive(DialogSettings.xml) + !Window.IsActive(DialogSlider.xml) + !Window.IsActive(GameVideoFilter) + !Window.IsActive(GameStretchMode) + !Window.IsActive(GameControllers) + !Window.IsActive(GameVideoRotation) - VisibleChange - - - - - - - - 0 - 80 - - String.IsEqual(window(home).Property(settingslist_content),osd) - 700 - 630 - 0 - 11100 - 11100 - vertical - - 700 - DialogSettingButton - - ActivateWindow(osdaudiosettings) - - - 700 - DialogSettingButton - - ActivateWindow(osdsubtitlesettings) - - - 700 - DialogSettingButton - - ActivateWindow(osdvideosettings) - - - 700 - DialogSettingButton - - ActivateWindow(osdcmssettings) - System.HasCMS - - - 700 - DialogSettingButton - - [B]$INFO[VideoPlayer.AudioLanguage][/B] - AudioNextLanguage - Integer.IsGreater(VideoPlayer.AudioStreamCount,1) - - - 700 - DialogSettingButton - - $VAR[ActiveVideoPlayerSubtitleLanguage] - NextSubtitle - VideoPlayer.HasSubtitles - - - 700 - DialogSettingButton - - PlayerProgramSelect - Player.HasPrograms - - - 700 - DialogSettingButton - - PlayerResolutionSelect - Player.HasResolutions - - - 700 - DialogSettingButton - - Dialog.Close(1101) - ActivateWindow(1110) - Player.TempoEnabled - - - - String.IsEqual(window(home).Property(settingslist_content),3d) - 700 - 360 - 0 - 13100 - 13100 - vertical - - 700 - DialogSettingButton - 590 - - ToggleStereoMode - Integer.IsGreater(System.StereoscopicMode,0) - - - 700 - DialogSettingButton - - [B]$INFO[VideoPlayer.StereoscopicMode][/B] - StereoMode - - - 700 - DialogSettingButton - 590 - - StereoModeToMono - Integer.IsEqual(System.StereoscopicMode,9) - - - - 14101 - String.IsEqual(window(home).Property(settingslist_content),games) - 700 - 500 - 0 - 14100 - 14100 - vertical - - Video filter button - 700 - DialogSettingButton - - ActivateWindow(GameVideoFilter) - - - Stretch mode button - 700 - DialogSettingButton - - ActivateWindow(GameStretchMode) - - - Video rotation button - 700 - DialogSettingButton - - ActivateWindow(GameVideoRotation) - - - Volume button - 700 - DialogSettingButton - - [COLOR grey]$FEATURE[select,game.controller.snes] + $FEATURE[rightstick,game.controller.default][/COLOR] - ActivateWindow(GameVolume) - - - Controller settings button - 700 - DialogSettingButton - - ActivateWindow(GameControllers) - - - Controller port configuration - 700 - DialogSettingButton - - ActivateWindow(GamePorts) - - - Advanced settings - 700 - DialogSettingButton - - ActivateWindow(GameAdvancedSettings) - - - - - - diff --git a/addons/skin.estuary/xml/Custom_1105_MusicOSDSettings.xml b/addons/skin.estuary/xml/Custom_1105_MusicOSDSettings.xml deleted file mode 100644 index a86d24f929e59..0000000000000 --- a/addons/skin.estuary/xml/Custom_1105_MusicOSDSettings.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - 5000 - Animation_DialogPopupOpenClose - - - 50% - 600 - 50% - 700 - - - - - - - - 0 - 80 - - 600 - 700 - 0 - 5000 - 5000 - vertical - - 600 - DialogSettingButton - - Skin.ToggleSetting(hide_background_fanart) - !Skin.HasSetting(hide_background_fanart) - - - - DialogSettingButton - Skin.ToggleSetting(animate_background_fanart) - Skin.HasSetting(animate_background_fanart) - - - 600 - DialogSettingButton - - [B]$INFO[Visualisation.Name][/B] - Dialog.Close(all) - SendClick(500) - - - 600 - DialogSettingButton - - Addon.Default.OpenSettings(xbmc.player.musicviz) - - - 600 - DialogSettingButton - - ActivateWindow(visualisationpresetlist) - Visualisation.HasPresets - - - 600 - DialogSettingButton - - [B]$INFO[Skin.String(LyricScript_Path)][/B] - Skin.SetAddon(LyricScript_Path,xbmc.python.lyrics) - - - 600 - DialogSettingButton - - Addon.OpenSettings($INFO[Skin.String(LyricScript_Path)]) - !String.IsEmpty(Skin.String(LyricScript_Path)) - - - - - - diff --git a/addons/skin.estuary/xml/GameOSD.xml b/addons/skin.estuary/xml/GameOSD.xml index 0c00a592d48a4..b2f5fd429e6d7 100644 --- a/addons/skin.estuary/xml/GameOSD.xml +++ b/addons/skin.estuary/xml/GameOSD.xml @@ -237,8 +237,8 @@ Settings button osd/fullscreen/buttons/settings.png - SetProperty(settingslist_content,games,home) - SetProperty(settingslist_header,$LOCALIZE[5],home) + SetProperty(settingsdialog_content,games,home) + SetProperty(settingsdialog_header,$LOCALIZE[5],home) ActivateWindow(1101) diff --git a/addons/skin.estuary/xml/Home.xml b/addons/skin.estuary/xml/Home.xml index 0f2d3a86a8791..24d032df238ee 100644 --- a/addons/skin.estuary/xml/Home.xml +++ b/addons/skin.estuary/xml/Home.xml @@ -51,39 +51,39 @@ WidgetGroupListCommon 5010 - + - + - + - + - + - + @@ -91,7 +91,7 @@ - + @@ -120,14 +120,14 @@ WidgetGroupListCommon 6010 - + - + @@ -137,13 +137,13 @@ - + - + @@ -151,7 +151,7 @@ - + @@ -159,7 +159,7 @@ - + @@ -187,13 +187,13 @@ WidgetGroupListCommon 7010 - + - + @@ -202,7 +202,7 @@ - + @@ -211,7 +211,7 @@ - + @@ -221,7 +221,7 @@ - + @@ -229,7 +229,7 @@ - + @@ -238,7 +238,7 @@ - + @@ -269,7 +269,7 @@ WidgetGroupListCommon 8010 - + @@ -391,12 +391,12 @@ WidgetGroupListCommon 12010 - + - + @@ -406,7 +406,7 @@ - + @@ -417,7 +417,7 @@ - + @@ -428,14 +428,14 @@ - + - + @@ -443,7 +443,7 @@ - + @@ -474,12 +474,12 @@ WidgetGroupListCommon 13010 - + - + @@ -489,7 +489,7 @@ - + @@ -500,7 +500,7 @@ - + @@ -511,14 +511,14 @@ - + - + @@ -526,7 +526,7 @@ - + @@ -720,13 +720,13 @@ WidgetGroupListCommon 16010 - + - + @@ -736,14 +736,14 @@ - + - + @@ -753,14 +753,14 @@ - + - + @@ -768,7 +768,7 @@ - + @@ -778,7 +778,7 @@ - + @@ -786,7 +786,7 @@ - + @@ -957,9 +957,9 @@ - ActivateWindow(Videos,videodb://movies/,return) - ActivateWindow(Videos,videodb://movies/titles/,return) - ActivateWindow(Videos,videodb://movies/titles/,return) + ActivateWindow(Videos,videodb://movies/,return) + ActivateWindow(Videos,videodb://movies/titles/,return) + ActivateWindow(Videos,videodb://movies/titles/,return) ActivateWindow(Videos,sources://video/,return) $NUMBER[5000] icons/sidemenu/movies.png @@ -968,9 +968,9 @@ - ActivateWindow(Videos,videodb://tvshows/,return) - ActivateWindow(Videos,videodb://tvshows/titles/,return) - ActivateWindow(Videos,videodb://tvshows/titles/,return) + ActivateWindow(Videos,videodb://tvshows/,return) + ActivateWindow(Videos,videodb://tvshows/titles/,return) + ActivateWindow(Videos,videodb://tvshows/titles/,return) ActivateWindow(Videos,sources://video/,return) $NUMBER[6000] icons/sidemenu/tv.png diff --git a/addons/skin.estuary/xml/Includes.xml b/addons/skin.estuary/xml/Includes.xml index e410c699c44c8..8dd226ec45710 100644 --- a/addons/skin.estuary/xml/Includes.xml +++ b/addons/skin.estuary/xml/Includes.xml @@ -9,6 +9,7 @@ + diff --git a/addons/skin.estuary/xml/Includes_Buttons.xml b/addons/skin.estuary/xml/Includes_Buttons.xml index 18939417cb722..e7deab53c4ff0 100644 --- a/addons/skin.estuary/xml/Includes_Buttons.xml +++ b/addons/skin.estuary/xml/Includes_Buttons.xml @@ -60,11 +60,13 @@ + 700 70 40 $PARAM[textoffsetx] 0 + $PARAM[width] $PARAM[height] center invalid diff --git a/addons/skin.estuary/xml/Includes_SettingsDialog.xml b/addons/skin.estuary/xml/Includes_SettingsDialog.xml new file mode 100644 index 0000000000000..7d8aa3cab9ffc --- /dev/null +++ b/addons/skin.estuary/xml/Includes_SettingsDialog.xml @@ -0,0 +1,452 @@ + + + + !Window.IsActive(DialogSettings.xml) + !Window.IsActive(DialogSlider.xml) + !Window.IsActive(GameVideoFilter) + !Window.IsActive(GameStretchMode) + !Window.IsActive(GameControllers) + !Window.IsActive(GameVideoRotation) + + + + DialogSettingButton + + ActivateWindow(osdaudiosettings) + + + DialogSettingButton + + ActivateWindow(osdsubtitlesettings) + + + DialogSettingButton + + ActivateWindow(osdvideosettings) + + + DialogSettingButton + + ActivateWindow(osdcmssettings) + System.HasCMS + + + DialogSettingButton + + [B]$INFO[VideoPlayer.AudioLanguage][/B] + AudioNextLanguage + Integer.IsGreater(VideoPlayer.AudioStreamCount,1) + + + DialogSettingButton + + $VAR[ActiveVideoPlayerSubtitleLanguage] + NextSubtitle + VideoPlayer.HasSubtitles + + + DialogSettingButton + + PlayerProgramSelect + Player.HasPrograms + + + DialogSettingButton + + PlayerResolutionSelect + Player.HasResolutions + + + DialogSettingButton + + Dialog.Close(1101) + ActivateWindow(1110) + Player.TempoEnabled + + + + + DialogSettingButton + 590 + + ToggleStereoMode + Integer.IsGreater(System.StereoscopicMode,0) + + + DialogSettingButton + + [B]$INFO[VideoPlayer.StereoscopicMode][/B] + StereoMode + + + DialogSettingButton + 590 + + StereoModeToMono + Integer.IsEqual(System.StereoscopicMode,9) + + + + + Video filter button + DialogSettingButton + + ActivateWindow(GameVideoFilter) + + + Stretch mode button + DialogSettingButton + + ActivateWindow(GameStretchMode) + + + Video rotation button + DialogSettingButton + + ActivateWindow(GameVideoRotation) + + + Volume button + DialogSettingButton + + [COLOR grey]$FEATURE[select,game.controller.snes] + $FEATURE[rightstick,game.controller.default][/COLOR] + ActivateWindow(GameVolume) + + + Controller settings button + DialogSettingButton + + ActivateWindow(GameControllers) + + + Controller port configuration + DialogSettingButton + + ActivateWindow(GamePorts) + + + Advanced settings + DialogSettingButton + + ActivateWindow(GameAdvancedSettings) + + + + + DialogSettingButton + + Skin.ToggleSetting(hide_background_fanart) + !Skin.HasSetting(hide_background_fanart) + + + + DialogSettingButton + Skin.ToggleSetting(animate_background_fanart) + Skin.HasSetting(animate_background_fanart) + + + DialogSettingButton + + [B]$INFO[Visualisation.Name][/B] + Dialog.Close(all) + SendClick(500) + + + DialogSettingButton + + Addon.Default.OpenSettings(xbmc.player.musicviz) + + + DialogSettingButton + + ActivateWindow(visualisationpresetlist) + Visualisation.HasPresets + + + DialogSettingButton + + [B]$INFO[Skin.String(LyricScript_Path)][/B] + Skin.SetAddon(LyricScript_Path,xbmc.python.lyrics) + + + DialogSettingButton + + Addon.OpenSettings($INFO[Skin.String(LyricScript_Path)]) + !String.IsEmpty(Skin.String(LyricScript_Path)) + + + + + DialogSettingButton + + Skin.ToggleSetting(home_no_movies_categories_widget) + !Skin.HasSetting(home_no_movies_categories_widget) + + + + DialogSettingButton + Dialog.Close(1101) + ActivateWindow(programs,plugin://plugin.library.node.editor/?ltype=video,return) + EnableAddon(plugin.library.node.editor) + InstallAddon(plugin.library.node.editor) + !Skin.HasSetting(home_no_movies_categories_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_movies_inprogress_widget) + !Skin.HasSetting(home_no_movies_inprogress_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_movies_recentlyadded_widget) + !Skin.HasSetting(home_no_movies_recentlyadded_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_movies_unwatched_widget) + !Skin.HasSetting(home_no_movies_unwatched_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_movies_random_widget) + !Skin.HasSetting(home_no_movies_random_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_movies_genres_widget) + !Skin.HasSetting(home_no_movies_genres_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_movies_sets_widget) + !Skin.HasSetting(home_no_movies_sets_widget) + + + + + DialogSettingButton + + Skin.ToggleSetting(home_no_tvshows_categories_widget) + !Skin.HasSetting(home_no_tvshows_categories_widget) + + + + DialogSettingButton + Dialog.Close(1101) + ActivateWindow(programs,plugin://plugin.library.node.editor/?ltype=video,return) + EnableAddon(plugin.library.node.editor) + InstallAddon(plugin.library.node.editor) + !Skin.HasSetting(home_no_tvshows_categories_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_tvshows_inprogress_widget) + !Skin.HasSetting(home_no_tvshows_inprogress_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_tvshows_recentlyaddedepisodes_widget) + !Skin.HasSetting(home_no_tvshows_recentlyaddedepisodes_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_tvshows_unwatched_widget) + !Skin.HasSetting(home_no_tvshows_unwatched_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_tvshows_genres_widget) + !Skin.HasSetting(home_no_tvshows_genres_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_tvshows_studios_widget) + !Skin.HasSetting(home_no_tvshows_studios_widget) + + + + + DialogSettingButton + + Skin.ToggleSetting(home_no_music_categories_widget) + !Skin.HasSetting(home_no_music_categories_widget) + + + + DialogSettingButton + Dialog.Close(1101) + ActivateWindow(programs,plugin://plugin.library.node.editor/?ltype=music,return) + EnableAddon(plugin.library.node.editor) + InstallAddon(plugin.library.node.editor) + !Skin.HasSetting(home_no_music_categories_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_music_recentlyplayedalbums_widget) + !Skin.HasSetting(home_no_music_recentlyplayedalbums_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_music_recentalbums_widget) + !Skin.HasSetting(home_no_music_recentalbums_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_music_randomalbums_widget) + !Skin.HasSetting(home_no_music_randomalbums_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_music_randomartists_widget) + !Skin.HasSetting(home_no_music_randomartists_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_music_unplayedalbums_widget) + !Skin.HasSetting(home_no_music_unplayedalbums_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_music_mostplayedalbums_widget) + !Skin.HasSetting(home_no_music_mostplayedalbums_widget) + + + + + DialogSettingButton + + Skin.ToggleSetting(home_no_musicvideos_categories_widget) + !Skin.HasSetting(home_no_musicvideos_categories_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_musicvideos_recentlyadded_widget) + !Skin.HasSetting(home_no_musicvideos_recentlyadded_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_musicvideos_unwatched_widget) + !Skin.HasSetting(home_no_musicvideos_unwatched_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_musicvideos_randomartists_widget) + !Skin.HasSetting(home_no_musicvideos_randomartists_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_musicvideos_random_widget) + !Skin.HasSetting(home_no_musicvideos_random_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_musicvideos_studios_widget) + !Skin.HasSetting(home_no_musicvideos_studios_widget) + + + + + DialogSettingButton + + Skin.ToggleSetting(home_no_tv_categories_widget) + !Skin.HasSetting(home_no_tv_categories_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_tv_recentlyplayed_widget) + !Skin.HasSetting(home_no_tv_recentlyplayed_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_tv_recentrecordings_widget) + !Skin.HasSetting(home_no_tv_recentrecordings_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_tv_timers_widget) + !Skin.HasSetting(home_no_tv_timers_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_tv_channelgroups_widget) + !Skin.HasSetting(home_no_tv_channelgroups_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_tv_savedsearches_widget) + !Skin.HasSetting(home_no_tv_savedsearches_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_tv_recentlyaddedchannels_widget) + !Skin.HasSetting(home_no_tv_recentlyaddedchannels_widget) + + + + + DialogSettingButton + + Skin.ToggleSetting(home_no_radio_categories_widget) + !Skin.HasSetting(home_no_radio_categories_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_radio_recentlyplayed_widget) + !Skin.HasSetting(home_no_radio_recentlyplayed_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_radio_recentrecordings_widget) + !Skin.HasSetting(home_no_radio_recentrecordings_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_radio_timers_widget) + !Skin.HasSetting(home_no_radio_timers_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_radio_channelgroups_widget) + !Skin.HasSetting(home_no_radio_channelgroups_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_radio_savedsearches_widget) + !Skin.HasSetting(home_no_radio_savedsearches_widget) + + + DialogSettingButton + + Skin.ToggleSetting(home_no_radio_recentlyaddedchannels_widget) + !Skin.HasSetting(home_no_radio_recentlyaddedchannels_widget) + + + diff --git a/addons/skin.estuary/xml/MusicOSD.xml b/addons/skin.estuary/xml/MusicOSD.xml index a97c0d3cd683f..9b97121a19d20 100644 --- a/addons/skin.estuary/xml/MusicOSD.xml +++ b/addons/skin.estuary/xml/MusicOSD.xml @@ -240,7 +240,9 @@ - ActivateWindow(1105) + SetProperty(settingsdialog_content,musicosd,home) + SetProperty(settingsdialog_header,$LOCALIZE[5],home) + ActivateWindow(1101) diff --git a/addons/skin.estuary/xml/SkinSettings.xml b/addons/skin.estuary/xml/SkinSettings.xml index 7f86e125e5d84..0e9b46268cf7d 100644 --- a/addons/skin.estuary/xml/SkinSettings.xml +++ b/addons/skin.estuary/xml/SkinSettings.xml @@ -147,24 +147,18 @@ 60 610 Container(9000).HasFocus(2) - - - DefaultSettingButton - !Skin.HasSetting(home_no_categories_widget) - Skin.ToggleSetting(home_no_categories_widget) - DefaultSettingButton !Skin.HasSetting(HomeMenuNoMovieButton) Skin.ToggleSetting(HomeMenuNoMovieButton) - - + + DefaultSettingButton - ActivateWindow(programs,plugin://plugin.library.node.editor/?ltype=video,return) - EnableAddon(plugin.library.node.editor) - InstallAddon(plugin.library.node.editor) + SetProperty(settingsdialog_header,$LOCALIZE[31040] - $LOCALIZE[342],home) + SetProperty(settingsdialog_content,moviewidgets,home) + ActivateWindow(1101) !Skin.HasSetting(HomeMenuNoMovieButton) @@ -180,12 +174,12 @@ !Skin.HasSetting(HomeMenuNoTVShowButton) Skin.ToggleSetting(HomeMenuNoTVShowButton) - - + + DefaultSettingButton - ActivateWindow(programs,plugin://plugin.library.node.editor/?ltype=video,return) - EnableAddon(plugin.library.node.editor) - InstallAddon(plugin.library.node.editor) + SetProperty(settingsdialog_header,$LOCALIZE[31040] - $LOCALIZE[20343],home) + SetProperty(settingsdialog_content,tvshowwidgets,home) + ActivateWindow(1101) !Skin.HasSetting(HomeMenuNoTVShowButton) @@ -201,12 +195,12 @@ !Skin.HasSetting(HomeMenuNoMusicButton) Skin.ToggleSetting(HomeMenuNoMusicButton) - - + + DefaultSettingButton - ActivateWindow(programs,plugin://plugin.library.node.editor/?ltype=music,return) - EnableAddon(plugin.library.node.editor) - InstallAddon(plugin.library.node.editor) + SetProperty(settingsdialog_header,$LOCALIZE[31040] - $LOCALIZE[2],home) + SetProperty(settingsdialog_content,musicwidgets,home) + ActivateWindow(1101) !Skin.HasSetting(HomeMenuNoMusicButton) @@ -222,24 +216,55 @@ !Skin.HasSetting(HomeMenuNoMusicVideoButton) Skin.ToggleSetting(HomeMenuNoMusicVideoButton) + + + DefaultSettingButton + SetProperty(settingsdialog_header,$LOCALIZE[31040] - $LOCALIZE[20389],home) + SetProperty(settingsdialog_content,musicvideowidgets,home) + ActivateWindow(1101) + !Skin.HasSetting(HomeMenuNoMusicVideoButton) + DefaultSettingButton !Skin.HasSetting(HomeMenuNoTVButton) Skin.ToggleSetting(HomeMenuNoTVButton) + + + DefaultSettingButton + SetProperty(settingsdialog_header,$LOCALIZE[31040] - $LOCALIZE[19020],home) + SetProperty(settingsdialog_content,tvwidgets,home) + ActivateWindow(1101) + !Skin.HasSetting(HomeMenuNoTVButton) + DefaultSettingButton !Skin.HasSetting(HomeMenuNoRadioButton) Skin.ToggleSetting(HomeMenuNoRadioButton) + + + DefaultSettingButton + SetProperty(settingsdialog_header,$LOCALIZE[31040] - $LOCALIZE[19021],home) + SetProperty(settingsdialog_content,radiowidgets,home) + ActivateWindow(1101) + !Skin.HasSetting(HomeMenuNoRadioButton) + DefaultSettingButton !Skin.HasSetting(HomeMenuNoProgramsButton) Skin.ToggleSetting(HomeMenuNoProgramsButton) + + + DefaultSettingButton + !Skin.HasSetting(home_no_addons_categories_widget) + Skin.ToggleSetting(home_no_addons_categories_widget) + !Skin.HasSetting(HomeMenuNoProgramsButton) + DefaultSettingButton diff --git a/addons/skin.estuary/xml/Timers.xml b/addons/skin.estuary/xml/Timers.xml index 3f6d5ff14f989..1a963b4e97275 100644 --- a/addons/skin.estuary/xml/Timers.xml +++ b/addons/skin.estuary/xml/Timers.xml @@ -3,8 +3,8 @@ autoclosevideoosd Timer to auto close the video OSD (if enabled in the skin settings) - Window.IsActive(videoosd) + Skin.HasSetting(OSDAutoClose) + !String.IsEqual(window(home).Property(settingslist_content),osd) + !Window.IsActive(osdsubtitlesettings) + !Window.IsActive(osdaudiosettings) + !Window.IsActive(osdvideosettings) + !Window.IsActive(OSDCMSSettings) - Window.IsActive(videoosd) + !System.IdleTime(1) + Integer.IsGreaterOrEqual(Skin.TimerElapsedSecs(autoclosevideoosd), 1) | String.IsEqual(window(home).Property(settingslist_content),osd) | Window.IsActive(osdsubtitlesettings) | Window.IsActive(osdaudiosettings) | Window.IsActive(osdvideosettings) | Window.IsActive(OSDCMSSettings) + Window.IsActive(videoosd) + Skin.HasSetting(OSDAutoClose) + !String.IsEqual(window(home).Property(settingsdialog_content),osd) + !Window.IsActive(osdsubtitlesettings) + !Window.IsActive(osdaudiosettings) + !Window.IsActive(osdvideosettings) + !Window.IsActive(OSDCMSSettings) + Window.IsActive(videoosd) + !System.IdleTime(1) + Integer.IsGreaterOrEqual(Skin.TimerElapsedSecs(autoclosevideoosd), 1) | String.IsEqual(window(home).Property(settingsdialog_content),osd) | Window.IsActive(osdsubtitlesettings) | Window.IsActive(osdaudiosettings) | Window.IsActive(osdvideosettings) | Window.IsActive(OSDCMSSettings) !Window.IsActive(videoosd) | String.IsEmpty(Skin.String(OSDAutoCloseTime)) + Integer.IsGreaterOrEqual(Skin.TimerElapsedSecs(autoclosevideoosd), 4) | !String.IsEmpty(Skin.String(OSDAutoCloseTime)) + Integer.IsGreaterOrEqual(Skin.TimerElapsedSecs(autoclosevideoosd),Skin.Numeric(OSDAutoCloseTime)) Dialog.Close(videoosd) diff --git a/addons/skin.estuary/xml/VideoOSD.xml b/addons/skin.estuary/xml/VideoOSD.xml index 74b220ec7a9bc..6c25ea3a86c9c 100644 --- a/addons/skin.estuary/xml/VideoOSD.xml +++ b/addons/skin.estuary/xml/VideoOSD.xml @@ -210,16 +210,16 @@ VideoPlayer.IsStereoscopic - SetProperty(settingslist_content,3d,home) - SetProperty(settingslist_header,$LOCALIZE[36501],home) + SetProperty(settingsdialog_content,3d,home) + SetProperty(settingsdialog_header,$LOCALIZE[36501],home) ActivateWindow(1101) - SetProperty(settingslist_content,osd,home) - SetProperty(settingslist_header,$LOCALIZE[5],home) + SetProperty(settingsdialog_content,osd,home) + SetProperty(settingsdialog_header,$LOCALIZE[5],home) ActivateWindow(1101) From a2f7d5d0b5cea7336c3f41068ab6f3f87de68c3c Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 1 Aug 2024 18:13:11 +0200 Subject: [PATCH 336/651] [cmake] Change binary addon repo branch to Piers. --- cmake/addons/bootstrap/repositories/binary-addons.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/addons/bootstrap/repositories/binary-addons.txt b/cmake/addons/bootstrap/repositories/binary-addons.txt index 54a5a5f6d10bd..0d3927de2afb3 100644 --- a/cmake/addons/bootstrap/repositories/binary-addons.txt +++ b/cmake/addons/bootstrap/repositories/binary-addons.txt @@ -1 +1 @@ -binary-addons https://github.com/xbmc/repo-binary-addons.git Omega +binary-addons https://github.com/xbmc/repo-binary-addons.git Piers From 1e31093cfcbdbff8d7d2c1d230dc85bff986cb50 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:59:31 +0200 Subject: [PATCH 337/651] [addons] Change addon repo to Piers. --- addons/repository.xbmc.org/addon.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/repository.xbmc.org/addon.xml b/addons/repository.xbmc.org/addon.xml index f4fe0ab99feb8..86ae1a25505a6 100644 --- a/addons/repository.xbmc.org/addon.xml +++ b/addons/repository.xbmc.org/addon.xml @@ -1,7 +1,7 @@ @@ -9,10 +9,10 @@

- https://mirrors.kodi.tv/addons/omega/addons.xml.gz - https://mirrors.kodi.tv/addons/omega/addons.xml.gz?sha256 - https://mirrors.kodi.tv/addons/omega - https://mirrors.kodi.tv/addons/omega + https://mirrors.kodi.tv/addons/piers/addons.xml.gz + https://mirrors.kodi.tv/addons/piers/addons.xml.gz?sha256 + https://mirrors.kodi.tv/addons/piers + https://mirrors.kodi.tv/addons/piers sha256 From 26a6e678d07e2af55aeb4adcb38858680a81aefa Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:25:40 +0200 Subject: [PATCH 338/651] [addons] PVR add-on API: Fix compiler warnings. --- xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h index 00567e7b8e24b..92bf52ffb80d8 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h @@ -78,7 +78,7 @@ struct ATTR_DLL_LOCAL CPrivateBase /*! * @brief Internally used helper to dynamically allocate and fill a char array. */ -inline char* ATTR_DLL_LOCAL AllocAndCopyString(const char* source) +inline char* AllocAndCopyString(const char* source) { if (source) { @@ -93,7 +93,7 @@ inline char* ATTR_DLL_LOCAL AllocAndCopyString(const char* source) /*! * @brief Internally used helper to delete a string that was allocated via AllocAndCopyString. */ -inline void ATTR_DLL_LOCAL FreeString(const char* str) +inline void FreeString(const char* str) { delete[] str; } @@ -101,8 +101,7 @@ inline void ATTR_DLL_LOCAL FreeString(const char* str) /*! * @brief Internally used helper to dynamically reallocate and fill a char array. */ -inline void ATTR_DLL_LOCAL ReallocAndCopyString(const char** stringToRealloc, - const char* stringToCopy) +inline void ReallocAndCopyString(const char** stringToRealloc, const char* stringToCopy) { FreeString(*stringToRealloc); *stringToRealloc = AllocAndCopyString(stringToCopy); @@ -112,8 +111,8 @@ inline void ATTR_DLL_LOCAL ReallocAndCopyString(const char** stringToRealloc, * @brief Internally used helper to convert c-struct data contained in a vector to an array of c-struct pointers. */ template -inline static ATTR_DLL_LOCAL C_STRUCT** AllocAndCopyPointerArray( - std::vector& sourceVector, unsigned int& targetArraySize) +inline static C_STRUCT** AllocAndCopyPointerArray(std::vector& sourceVector, + unsigned int& targetArraySize) { targetArraySize = static_cast(sourceVector.size()); if (targetArraySize > 0) From 50848739e4e07b13eaa594f660fb5bce3b420b61 Mon Sep 17 00:00:00 2001 From: Rechi Date: Fri, 2 Aug 2024 19:06:53 +0200 Subject: [PATCH 339/651] [cppcheck] remove rule-file - ignored checks are no longer required after 45427e386f - Debian disabled rules support as it depends on pcre --- cmake/scripts/common/StaticAnalysis.cmake | 1 - tools/static-analysis/cppcheck/cppcheck-rules.xml | 10 ---------- 2 files changed, 11 deletions(-) delete mode 100644 tools/static-analysis/cppcheck/cppcheck-rules.xml diff --git a/cmake/scripts/common/StaticAnalysis.cmake b/cmake/scripts/common/StaticAnalysis.cmake index f7ffa3957348e..d972e498ea73c 100644 --- a/cmake/scripts/common/StaticAnalysis.cmake +++ b/cmake/scripts/common/StaticAnalysis.cmake @@ -15,7 +15,6 @@ if(CPPCHECK_EXECUTABLE) --xml-version=2 --language=c++ --relative-paths=${CMAKE_SOURCE_DIR} - --rule-file=${CMAKE_SOURCE_DIR}/tools/static-analysis/cppcheck/cppcheck-rules.xml --suppress-xml=${CMAKE_SOURCE_DIR}/tools/static-analysis/cppcheck/cppcheck-suppressions.xml --output-file=${CMAKE_BINARY_DIR}/cppcheck-result.xml COMMENT "Static code analysis using cppcheck") diff --git a/tools/static-analysis/cppcheck/cppcheck-rules.xml b/tools/static-analysis/cppcheck/cppcheck-rules.xml deleted file mode 100644 index 35f1fd18c787d..0000000000000 --- a/tools/static-analysis/cppcheck/cppcheck-rules.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - CSingleLock - CSharedLock - CExclusiveLock - - - From c4d5e9cdc27aa6ec0f85782cb21592f6c65d6b92 Mon Sep 17 00:00:00 2001 From: Rechi Date: Fri, 2 Aug 2024 19:06:53 +0200 Subject: [PATCH 340/651] [cppcheck] enable exhaustive checking for cppcheck >= 2.14.0 --- cmake/modules/buildtools/FindCppcheck.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake/modules/buildtools/FindCppcheck.cmake b/cmake/modules/buildtools/FindCppcheck.cmake index 27e26752912c4..48cd89f920e35 100644 --- a/cmake/modules/buildtools/FindCppcheck.cmake +++ b/cmake/modules/buildtools/FindCppcheck.cmake @@ -21,7 +21,10 @@ if(CPPCHECK_FOUND) # but cppcheck doesn't support Objective-C and Objective-C++. # CMake >= 3.16 added support for Objective-C and Objective-C++ language, # but doesn't support OBJC and OBJCXX for _CLANG_TIDY. - file(WRITE "${CMAKE_BINARY_DIR}/cppcheck" "case \"$@\" in *.m|*.mm) exit 0; esac\nexec \"${CPPCHECK_EXECUTABLE}\" --enable=performance --quiet --relative-paths=\"${CMAKE_SOURCE_DIR}\" \"$@\"\n") + if(CPPCHECK_VERSION VERSION_GREATER_EQUAL 2.14) + set(check-level --check-level=exhaustive) + endif() + file(WRITE "${CMAKE_BINARY_DIR}/cppcheck" "#!/bin/sh\ncase \"$@\" in *.m|*.mm) exit 0; esac\nexec \"${CPPCHECK_EXECUTABLE}\" ${check-level} --enable=performance --quiet --relative-paths=\"${CMAKE_SOURCE_DIR}\" \"$@\"\n") execute_process(COMMAND chmod +x "${CMAKE_BINARY_DIR}/cppcheck") # Supports Unix Makefiles and Ninja From 4f7d286e912f4b07cf27aa1c5f0f51e2c6538dcf Mon Sep 17 00:00:00 2001 From: Rechi Date: Fri, 2 Aug 2024 19:06:53 +0200 Subject: [PATCH 341/651] [cppcheck] passedByValue --- xbmc/FileItemList.cpp | 2 +- xbmc/FileItemList.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/FileItemList.cpp b/xbmc/FileItemList.cpp index 95b2dc58ae067..cb8e918001476 100644 --- a/xbmc/FileItemList.cpp +++ b/xbmc/FileItemList.cpp @@ -1154,7 +1154,7 @@ void CFileItemList::AddSortMethod(SortBy sortBy, AddSortMethod(sorting, buttonLabel, labelMasks); } -void CFileItemList::AddSortMethod(SortDescription sortDescription, +void CFileItemList::AddSortMethod(const SortDescription& sortDescription, int buttonLabel, const LABEL_MASKS& labelMasks) { diff --git a/xbmc/FileItemList.h b/xbmc/FileItemList.h index 5f04f179d291a..5fd82e79d7d67 100644 --- a/xbmc/FileItemList.h +++ b/xbmc/FileItemList.h @@ -143,7 +143,7 @@ class CFileItemList : public CFileItem SortAttribute sortAttributes, int buttonLabel, const LABEL_MASKS& labelMasks); - void AddSortMethod(SortDescription sortDescription, + void AddSortMethod(const SortDescription& sortDescription, int buttonLabel, const LABEL_MASKS& labelMasks); bool HasSortDetails() const { return m_sortDetails.size() != 0; } From 58cd6be5d5b54963241118fe597b7bec9ca6c41c Mon Sep 17 00:00:00 2001 From: Rechi Date: Fri, 2 Aug 2024 19:06:53 +0200 Subject: [PATCH 342/651] [cppcheck] returnByReference --- xbmc/CueDocument.cpp | 5 -- xbmc/CueDocument.h | 2 +- xbmc/TextureCacheJob.cpp | 4 +- .../kodi-dev-kit/include/kodi/AddonBase.h | 2 +- .../kodi/addon-instance/AudioDecoder.h | 22 +++---- .../kodi/addon-instance/ImageDecoder.h | 10 ++-- xbmc/application/ApplicationPowerHandling.h | 2 +- .../Sinks/osx/AEDeviceEnumerationOSX.h | 2 +- .../Sinks/pipewire/PipewireGlobal.h | 6 +- .../rendering/RenderVideoSettings.h | 2 +- xbmc/favourites/FavouritesURL.h | 8 +-- xbmc/filesystem/CurlFile.cpp | 5 -- xbmc/filesystem/CurlFile.h | 2 +- xbmc/filesystem/FTPParse.cpp | 5 -- xbmc/filesystem/FTPParse.h | 2 +- xbmc/imagefiles/ImageFileURL.h | 4 +- xbmc/input/InputManager.cpp | 2 +- xbmc/interfaces/builtins/Builtins.cpp | 10 ++-- xbmc/interfaces/legacy/ModuleXbmc.cpp | 4 +- xbmc/music/Artist.h | 10 ++-- xbmc/music/Song.cpp | 5 -- xbmc/music/Song.h | 8 +-- xbmc/network/test/TestWebServer.cpp | 4 +- .../linux/storage/UDisks2Provider.cpp | 10 ---- xbmc/platform/linux/storage/UDisks2Provider.h | 4 +- .../platform/linux/storage/UDisksProvider.cpp | 10 ---- xbmc/platform/linux/storage/UDisksProvider.h | 4 +- xbmc/pvr/PVRContextMenus.h | 2 +- xbmc/pvr/addons/PVRClientMenuHooks.cpp | 5 -- xbmc/pvr/addons/PVRClientMenuHooks.h | 2 +- xbmc/pvr/epg/EpgInfoTag.cpp | 60 ------------------- xbmc/pvr/epg/EpgInfoTag.h | 24 ++++---- xbmc/pvr/recordings/PVRRecording.h | 10 ++-- xbmc/settings/SettingControl.h | 4 +- xbmc/settings/lib/Setting.h | 4 +- xbmc/storage/cdioSupport.h | 4 +- xbmc/test/TestUtils.cpp | 5 +- xbmc/utils/CPUInfo.h | 12 ++-- xbmc/utils/CharsetConverter.cpp | 4 +- xbmc/utils/ExecString.h | 6 +- xbmc/utils/HttpHeader.h | 3 +- xbmc/utils/XBMCTinyXML.h | 4 +- xbmc/video/ContextMenus.cpp | 2 +- xbmc/video/VideoDatabase.cpp | 2 +- xbmc/video/VideoGeneratedImageFileLoader.cpp | 2 +- xbmc/windowing/X11/GLContext.h | 2 +- 46 files changed, 101 insertions(+), 210 deletions(-) diff --git a/xbmc/CueDocument.cpp b/xbmc/CueDocument.cpp index 2094b02492eac..3a5c0fae074f3 100644 --- a/xbmc/CueDocument.cpp +++ b/xbmc/CueDocument.cpp @@ -233,11 +233,6 @@ void CCueDocument::GetMediaFiles(std::vector& mediaFiles) mediaFiles.push_back(*it); } -std::string CCueDocument::GetMediaTitle() -{ - return m_strAlbum; -} - bool CCueDocument::IsLoaded() const { return !m_tracks.empty(); diff --git a/xbmc/CueDocument.h b/xbmc/CueDocument.h index 22fa733de2c3b..2a7f9c7cadba0 100644 --- a/xbmc/CueDocument.h +++ b/xbmc/CueDocument.h @@ -39,7 +39,7 @@ class CCueDocument bool ParseTag(const std::string &strContent); void GetSongs(VECSONGS &songs); std::string GetMediaPath(); - std::string GetMediaTitle(); + const std::string& GetMediaTitle() const { return m_strAlbum; } void GetMediaFiles(std::vector& mediaFiles); void UpdateMediaFile(const std::string& oldMediaFile, const std::string& mediaFile); bool IsOneFilePerTrack() const; diff --git a/xbmc/TextureCacheJob.cpp b/xbmc/TextureCacheJob.cpp index ca1070bc31540..6daf8e2f60f3d 100644 --- a/xbmc/TextureCacheJob.cpp +++ b/xbmc/TextureCacheJob.cpp @@ -97,7 +97,7 @@ bool CTextureCacheJob::CacheTexture(std::unique_ptr* out_texture) { IMAGE_FILES::CImageFileURL imageURL{m_url}; - auto image = imageURL.GetTargetFile(); + const auto& image = imageURL.GetTargetFile(); m_details.updateable = ShouldCheckForChanges(imageURL.GetSpecialType(), image); if (m_details.updateable) @@ -151,7 +151,7 @@ bool CTextureCacheJob::ResizeTexture(const std::string& url, result_size = 0; const IMAGE_FILES::CImageFileURL imageURL{url}; - const auto image = imageURL.GetTargetFile(); + const auto& image = imageURL.GetTargetFile(); if (image.empty()) return false; diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h index 92bf52ffb80d8..c64ce05a1104d 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h @@ -448,7 +448,7 @@ class ATTR_DLL_LOCAL CSettingValue ///@{ /// @brief To get settings value as string. - std::string GetString() const { return str; } + const std::string& GetString() const { return str; } /// @brief To get settings value as integer. int GetInt() const { return std::atoi(str.c_str()); } diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/AudioDecoder.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/AudioDecoder.h index 475c885094020..9c16676cb2443 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/AudioDecoder.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/AudioDecoder.h @@ -79,25 +79,25 @@ class ATTR_DLL_LOCAL AudioDecoderInfoTag void SetTitle(const std::string& title) { m_title = title; } /// @brief Get title name - std::string GetTitle() const { return m_title; } + const std::string& GetTitle() const { return m_title; } /// @brief Set artist name void SetArtist(const std::string& artist) { m_artist = artist; } /// @brief Get artist name - std::string GetArtist() const { return m_artist; } + const std::string& GetArtist() const { return m_artist; } /// @brief Set album name void SetAlbum(const std::string& album) { m_album = album; } /// @brief Set album name - std::string GetAlbum() const { return m_album; } + const std::string& GetAlbum() const { return m_album; } /// @brief Set album artist name void SetAlbumArtist(const std::string& albumArtist) { m_album_artist = albumArtist; } /// @brief Get album artist name - std::string GetAlbumArtist() const { return m_album_artist; } + const std::string& GetAlbumArtist() const { return m_album_artist; } /// @brief Set the media type of the music item. /// @@ -112,13 +112,13 @@ class ATTR_DLL_LOCAL AudioDecoderInfoTag void SetMediaType(const std::string& mediaType) { m_media_type = mediaType; } /// @brief Get the media type of the music item. - std::string GetMediaType() const { return m_media_type; } + const std::string& GetMediaType() const { return m_media_type; } /// @brief Set genre name from music as string if present. void SetGenre(const std::string& genre) { m_genre = genre; } /// @brief Get genre name from music as string if present. - std::string GetGenre() const { return m_genre; } + const std::string& GetGenre() const { return m_genre; } /// @brief Set the duration of music as integer from info. void SetDuration(int duration) { m_duration = duration; } @@ -142,7 +142,7 @@ class ATTR_DLL_LOCAL AudioDecoderInfoTag void SetDiscSubtitle(const std::string& discSubtitle) { m_disc_subtitle = discSubtitle; } /// @brief Get disk subtitle name (if present) from music info. - std::string GetDiscSubtitle() const { return m_disc_subtitle; } + const std::string& GetDiscSubtitle() const { return m_disc_subtitle; } /// @brief Set disks amount quantity (if present) from music info as integer. void SetDiscTotal(int discTotal) { m_disc_total = discTotal; } @@ -155,13 +155,13 @@ class ATTR_DLL_LOCAL AudioDecoderInfoTag void SetReleaseDate(const std::string& releaseDate) { m_release_date = releaseDate; } /// @brief Get release date as string from music info (if present). - std::string GetReleaseDate() const { return m_release_date; } + const std::string& GetReleaseDate() const { return m_release_date; } /// @brief Set string from lyrics. void SetLyrics(const std::string& lyrics) { m_lyrics = lyrics; } /// @brief Get string from lyrics. - std::string GetLyrics() const { return m_lyrics; } + const std::string& GetLyrics() const { return m_lyrics; } /// @brief Set related stream samplerate. void SetSamplerate(int samplerate) { m_samplerate = samplerate; } @@ -185,7 +185,7 @@ class ATTR_DLL_LOCAL AudioDecoderInfoTag void SetComment(const std::string& comment) { m_comment = comment; } /// @brief Get additional information comment (if present). - std::string GetComment() const { return m_comment; } + const std::string& GetComment() const { return m_comment; } /// @brief Set cover art image by path. /// @@ -201,7 +201,7 @@ class ATTR_DLL_LOCAL AudioDecoderInfoTag /// @note Only be available if set before by @ref SetCoverArtByPath. /// Cannot be combined with @ref SetCoverArtByMem and @ref GetCoverArtByMem. /// - std::string GetCoverArtByPath() const { return m_cover_art_path; } + const std::string& GetCoverArtByPath() const { return m_cover_art_path; } /// @brief Set cover art image by memory. /// diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/ImageDecoder.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/ImageDecoder.h index eb98f900285d6..7a876ad5b1fcf 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/ImageDecoder.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/ImageDecoder.h @@ -289,31 +289,31 @@ class ATTR_DLL_LOCAL ImageDecoderInfoTag } /// @brief Get camera manufacturer - std::string GetCameraManufacturer() const { return m_camera_manufacturer; } + const std::string& GetCameraManufacturer() const { return m_camera_manufacturer; } /// @brief Set camera model void SetCameraModel(const std::string& camera_model) { m_camera_model = camera_model; } /// @brief Get camera model - std::string GetCameraModel() const { return m_camera_model; } + const std::string& GetCameraModel() const { return m_camera_model; } /// @brief Set author void SetAuthor(const std::string& author) { m_author = author; } /// @brief Get author - std::string GetAuthor() const { return m_author; } + const std::string& GetAuthor() const { return m_author; } /// @brief Set description void SetDescription(const std::string& description) { m_description = description; } /// @brief Get description - std::string GetDescription() const { return m_description; } + const std::string& GetDescription() const { return m_description; } /// @brief Set copyright void SetCopyright(const std::string& copyright) { m_copyright = copyright; } /// @brief Get copyright - std::string GetCopyright() const { return m_copyright; } + const std::string& GetCopyright() const { return m_copyright; } ///@} diff --git a/xbmc/application/ApplicationPowerHandling.h b/xbmc/application/ApplicationPowerHandling.h index 9d78c6e36d26e..97edd7c27f6bf 100644 --- a/xbmc/application/ApplicationPowerHandling.h +++ b/xbmc/application/ApplicationPowerHandling.h @@ -42,7 +42,7 @@ class CApplicationPowerHandling : public IApplicationComponent void SetScreenSaverLockFailed() { m_iScreenSaveLock = -1; } void SetScreenSaverUnlocked() { m_iScreenSaveLock = 1; } void StopScreenSaverTimer(); - std::string ScreensaverIdInUse() const { return m_screensaverIdInUse; } + const std::string& ScreensaverIdInUse() const { return m_screensaverIdInUse; } bool GetRenderGUI() const { return m_renderGUI; } void SetRenderGUI(bool renderGUI); diff --git a/xbmc/cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.h b/xbmc/cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.h index 7184f9be73cee..93f499f2de4b2 100644 --- a/xbmc/cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.h +++ b/xbmc/cores/AudioEngine/Sinks/osx/AEDeviceEnumerationOSX.h @@ -90,7 +90,7 @@ class AEDeviceEnumerationOSX * @brief Returns the device name which belongs to m_deviceID without any stream/source suffixes * @return the CA device name */ - std::string GetMasterDeviceName() const { return m_deviceName; } + const std::string& GetMasterDeviceName() const { return m_deviceName; } /*! * @brief Tries to return a proper channelmap from CA in a format AE understands diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireGlobal.h b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireGlobal.h index 67f40c72203fe..0a6ddd84bec3c 100644 --- a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireGlobal.h +++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireGlobal.h @@ -35,7 +35,7 @@ class CPipewireGlobal return *this; } - std::string GetName() const { return m_name; } + const std::string& GetName() const { return m_name; } CPipewireGlobal& SetDescription(const std::string& description) { @@ -43,7 +43,7 @@ class CPipewireGlobal return *this; } - std::string GetDescription() const { return m_description; } + const std::string& GetDescription() const { return m_description; } CPipewireGlobal& SetID(uint32_t id) { @@ -67,7 +67,7 @@ class CPipewireGlobal return *this; } - std::string GetType() const { return m_type; } + const std::string& GetType() const { return m_type; } CPipewireGlobal& SetVersion(uint32_t version) { diff --git a/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.h b/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.h index 1d142706eaf2d..7c2dcf64c862c 100644 --- a/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.h +++ b/xbmc/cores/RetroPlayer/rendering/RenderVideoSettings.h @@ -46,7 +46,7 @@ class CRenderVideoSettings unsigned int GetRenderRotation() const { return m_rotationDegCCW; } void SetRenderRotation(unsigned int rotationDegCCW) { m_rotationDegCCW = rotationDegCCW; } - std::string GetPixels() const { return m_pixelPath; } + const std::string& GetPixels() const { return m_pixelPath; } void SetPixels(const std::string& pixelPath) { m_pixelPath = pixelPath; } void ResetPixels(); diff --git a/xbmc/favourites/FavouritesURL.h b/xbmc/favourites/FavouritesURL.h index 7261484632a08..8ab19aab93593 100644 --- a/xbmc/favourites/FavouritesURL.h +++ b/xbmc/favourites/FavouritesURL.h @@ -36,7 +36,7 @@ class CFavouritesURL virtual ~CFavouritesURL() = default; - std::string GetURL() const { return m_path; } + const std::string& GetURL() const { return m_path; } bool IsValid() const { return m_valid && m_exec.IsValid(); } @@ -45,10 +45,10 @@ class CFavouritesURL std::string GetExecString() const { return m_exec.GetExecString(); } Action GetAction() const { return m_action; } std::vector GetParams() const { return m_exec.GetParams(); } - std::string GetTarget() const { return m_target; } + const std::string& GetTarget() const { return m_target; } int GetWindowID() const { return m_windowId; } - std::string GetActionLabel() const { return m_actionLabel; } - std::string GetProviderLabel() const { return m_providerLabel; } + const std::string& GetActionLabel() const { return m_actionLabel; } + const std::string& GetProviderLabel() const { return m_providerLabel; } private: bool Parse(CFavouritesURL::Action action, const std::vector& params); diff --git a/xbmc/filesystem/CurlFile.cpp b/xbmc/filesystem/CurlFile.cpp index b79ef62a120d2..0269dbfc48da8 100644 --- a/xbmc/filesystem/CurlFile.cpp +++ b/xbmc/filesystem/CurlFile.cpp @@ -1929,11 +1929,6 @@ void CCurlFile::SetRequestHeader(const std::string& header, long value) m_requestheaders[header] = std::to_string(value); } -std::string CCurlFile::GetURL(void) -{ - return m_url; -} - std::string CCurlFile::GetRedirectURL() { return GetInfoString(CURLINFO_REDIRECT_URL); diff --git a/xbmc/filesystem/CurlFile.h b/xbmc/filesystem/CurlFile.h index 88f29232d6bba..7343533d144d2 100644 --- a/xbmc/filesystem/CurlFile.h +++ b/xbmc/filesystem/CurlFile.h @@ -80,7 +80,7 @@ namespace XFILE void SetBufferSize(unsigned int size); const CHttpHeader& GetHttpHeader() const { return m_state->m_httpheader; } - std::string GetURL(void); + const std::string& GetURL() const { return m_url; } std::string GetRedirectURL(); /* static function that will get content type of a file */ diff --git a/xbmc/filesystem/FTPParse.cpp b/xbmc/filesystem/FTPParse.cpp index dbbc033a40763..563fb2a194c4e 100644 --- a/xbmc/filesystem/FTPParse.cpp +++ b/xbmc/filesystem/FTPParse.cpp @@ -17,11 +17,6 @@ CFTPParse::CFTPParse() m_size = 0; } -std::string CFTPParse::getName() -{ - return m_name; -} - int CFTPParse::getFlagtrycwd() { return m_flagtrycwd; diff --git a/xbmc/filesystem/FTPParse.h b/xbmc/filesystem/FTPParse.h index a853b0286931e..daabb72f3c613 100644 --- a/xbmc/filesystem/FTPParse.h +++ b/xbmc/filesystem/FTPParse.h @@ -17,7 +17,7 @@ class CFTPParse public: CFTPParse(); int FTPParse(const std::string& str); - std::string getName(); + const std::string& getName() const { return m_name; } int getFlagtrycwd(); int getFlagtryretr(); uint64_t getSize(); diff --git a/xbmc/imagefiles/ImageFileURL.h b/xbmc/imagefiles/ImageFileURL.h index e55d5b1d42256..d10f286e00de1 100644 --- a/xbmc/imagefiles/ImageFileURL.h +++ b/xbmc/imagefiles/ImageFileURL.h @@ -58,10 +58,10 @@ class CImageFileURL */ std::string ToCacheKey() const; - std::string GetTargetFile() const { return m_filePath; } + const std::string& GetTargetFile() const { return m_filePath; } bool IsSpecialImage() const { return !m_specialType.empty(); } - std::string GetSpecialType() const { return m_specialType; } + const std::string& GetSpecialType() const { return m_specialType; } bool flipped{false}; diff --git a/xbmc/input/InputManager.cpp b/xbmc/input/InputManager.cpp index 26641f64f2e5e..a07a1316ee694 100644 --- a/xbmc/input/InputManager.cpp +++ b/xbmc/input/InputManager.cpp @@ -704,7 +704,7 @@ bool CInputManager::AlwaysProcess(const CAction& action) const CExecString exec(action.GetName()); if (exec.IsValid()) { - const std::string builtInFunction = exec.GetFunction(); + const std::string& builtInFunction = exec.GetFunction(); // should this button be handled normally or just cancel the screensaver? if (builtInFunction == "powerdown" || builtInFunction == "reboot" || diff --git a/xbmc/interfaces/builtins/Builtins.cpp b/xbmc/interfaces/builtins/Builtins.cpp index 624d9d1850623..730978bd8401a 100644 --- a/xbmc/interfaces/builtins/Builtins.cpp +++ b/xbmc/interfaces/builtins/Builtins.cpp @@ -79,8 +79,8 @@ bool CBuiltins::HasCommand(const std::string& execString) if (!exec.IsValid()) return false; - const std::string function = exec.GetFunction(); - const std::vector parameters = exec.GetParams(); + const std::string& function = exec.GetFunction(); + const std::vector& parameters = exec.GetParams(); if (CServiceBroker::GetInputManager().HasBuiltin(function)) return true; @@ -101,7 +101,7 @@ bool CBuiltins::IsSystemPowerdownCommand(const std::string& execString) if (!exec.IsValid()) return false; - const std::string execute = exec.GetFunction(); + const std::string& execute = exec.GetFunction(); // Check if action is resulting in system powerdown. if (execute == "reboot" || @@ -148,8 +148,8 @@ int CBuiltins::Execute(const std::string& execString) if (!exec.IsValid()) return -1; - const std::string execute = exec.GetFunction(); - const std::vector params = exec.GetParams(); + const std::string& execute = exec.GetFunction(); + const std::vector& params = exec.GetParams(); const auto& it = m_command.find(execute); if (it != m_command.end()) diff --git a/xbmc/interfaces/legacy/ModuleXbmc.cpp b/xbmc/interfaces/legacy/ModuleXbmc.cpp index de752344d7b8d..493873ad62ae5 100644 --- a/xbmc/interfaces/legacy/ModuleXbmc.cpp +++ b/xbmc/interfaces/legacy/ModuleXbmc.cpp @@ -100,8 +100,8 @@ namespace XBMCAddon if (!exec.IsValid()) return; - const std::string execute = exec.GetFunction(); - const std::vector params = exec.GetParams(); + const std::string& execute = exec.GetFunction(); + const std::vector& params = exec.GetParams(); if (StringUtils::EqualsNoCase(execute, "activatewindow") || StringUtils::EqualsNoCase(execute, "closedialog")) diff --git a/xbmc/music/Artist.h b/xbmc/music/Artist.h index d24f00c8f719b..bd39ce78a6615 100644 --- a/xbmc/music/Artist.h +++ b/xbmc/music/Artist.h @@ -163,9 +163,9 @@ class CArtistCredit return false; } - std::string GetArtist() const { return m_strArtist; } - std::string GetSortName() const { return m_strSortName; } - std::string GetMusicBrainzArtistID() const { return m_strMusicBrainzArtistID; } + const std::string& GetArtist() const { return m_strArtist; } + const std::string& GetSortName() const { return m_strSortName; } + const std::string& GetMusicBrainzArtistID() const { return m_strMusicBrainzArtistID; } int GetArtistId() const { return idArtist; } bool HasScrapedMBID() const { return m_bScrapedMBID; } void SetArtist(const std::string &strArtist) { m_strArtist = strArtist; } @@ -207,8 +207,8 @@ class CMusicRole idArtist(ArtistId) { } - std::string GetArtist() const { return m_strArtist; } - std::string GetRoleDesc() const { return m_strRole; } + const std::string& GetArtist() const { return m_strArtist; } + const std::string& GetRoleDesc() const { return m_strRole; } int GetRoleId() const { return idRole; } int GetArtistId() const { return idArtist; } void SetArtistId(int iArtistId) { idArtist = iArtistId; } diff --git a/xbmc/music/Song.cpp b/xbmc/music/Song.cpp index 93b2156d7b3bb..ad68480da6fe7 100644 --- a/xbmc/music/Song.cpp +++ b/xbmc/music/Song.cpp @@ -369,8 +369,3 @@ bool CSong::ArtMatches(const CSong &right) const return (right.strThumb == strThumb && embeddedArt.Matches(right.embeddedArt)); } - -const std::string CSong::GetDiscSubtitle() const -{ - return strDiscSubtitle; -} diff --git a/xbmc/music/Song.h b/xbmc/music/Song.h index 737ba46eed7c8..cfd33fe56fe5e 100644 --- a/xbmc/music/Song.h +++ b/xbmc/music/Song.h @@ -93,22 +93,22 @@ class CSong final : public ISerializable and is stored in album artist credits \return album artist names as a vector of strings */ - const std::vector GetAlbumArtist() const { return m_albumArtist; } + const std::vector& GetAlbumArtist() const { return m_albumArtist; } /*! \brief Get album artist sort name string \return album artist sort name as a single string */ - const std::string GetAlbumArtistSort() const { return m_strAlbumArtistSort; } + const std::string& GetAlbumArtistSort() const { return m_strAlbumArtistSort; } /*! \brief Get disc subtitle string where one exists \return disc subtitle as a single string */ - const std::string GetDiscSubtitle() const; + const std::string& GetDiscSubtitle() const { return strDiscSubtitle; } /*! \brief Get composer sort name string \return composer sort name as a single string */ - const std::string GetComposerSort() const { return m_strComposerSort; } + const std::string& GetComposerSort() const { return m_strComposerSort; } /*! \brief Get the full list of artist names and the role each played for those that contributed to the recording. Given in music file tags other than ARTIST diff --git a/xbmc/network/test/TestWebServer.cpp b/xbmc/network/test/TestWebServer.cpp index 70c371b210fc6..0cfb624af3972 100644 --- a/xbmc/network/test/TestWebServer.cpp +++ b/xbmc/network/test/TestWebServer.cpp @@ -180,7 +180,7 @@ class TestWebServer : public testing::Test // check the protocol line for the expected HTTP status std::string httpStatusString = StringUtils::Format(" {} ", httpStatus); - std::string protocolLine = httpHeader.GetProtoLine(); + const std::string& protocolLine = httpHeader.GetProtoLine(); ASSERT_TRUE(protocolLine.find(httpStatusString) != std::string::npos); // Content-Type must be "text/html" @@ -215,7 +215,7 @@ class TestWebServer : public testing::Test // check the protocol line for the expected HTTP status std::string httpStatusString = StringUtils::Format(" {} ", MHD_HTTP_PARTIAL_CONTENT); - std::string protocolLine = httpHeader.GetProtoLine(); + const std::string& protocolLine = httpHeader.GetProtoLine(); ASSERT_TRUE(protocolLine.find(httpStatusString) != std::string::npos); // Accept-Ranges must be "bytes" diff --git a/xbmc/platform/linux/storage/UDisks2Provider.cpp b/xbmc/platform/linux/storage/UDisks2Provider.cpp index b3431c73256c2..ee2fc92ac9aa7 100644 --- a/xbmc/platform/linux/storage/UDisks2Provider.cpp +++ b/xbmc/platform/linux/storage/UDisks2Provider.cpp @@ -108,16 +108,6 @@ MEDIA_DETECT::STORAGE::Type CUDisks2Provider::Filesystem::GetStorageType() const return MEDIA_DETECT::STORAGE::Type::UNKNOWN; } -std::string CUDisks2Provider::Filesystem::GetMountPoint() const -{ - return m_mountPoint; -} - -std::string CUDisks2Provider::Filesystem::GetObject() const -{ - return m_object; -} - void CUDisks2Provider::Filesystem::ResetMountPoint() { m_mountPoint.clear(); diff --git a/xbmc/platform/linux/storage/UDisks2Provider.h b/xbmc/platform/linux/storage/UDisks2Provider.h index b4c85eeb1e433..7693dd31ab617 100644 --- a/xbmc/platform/linux/storage/UDisks2Provider.h +++ b/xbmc/platform/linux/storage/UDisks2Provider.h @@ -104,7 +104,7 @@ class CUDisks2Provider : public IStorageProvider /*! \brief Get the device mount point * @return the device mount point */ - std::string GetMountPoint() const; + const std::string& GetMountPoint() const { return m_mountPoint; } /*! \brief Reset the device mount point */ @@ -118,7 +118,7 @@ class CUDisks2Provider : public IStorageProvider /*! \brief Get the device dbus object * @return the device dbus object */ - std::string GetObject() const; + const std::string& GetObject() const { return m_object; } /*! \brief Get a representation of the device as a readable string * @return device as a string diff --git a/xbmc/platform/linux/storage/UDisksProvider.cpp b/xbmc/platform/linux/storage/UDisksProvider.cpp index 7a2a0347d70e8..083fb8b847fd6 100644 --- a/xbmc/platform/linux/storage/UDisksProvider.cpp +++ b/xbmc/platform/linux/storage/UDisksProvider.cpp @@ -168,16 +168,6 @@ bool CUDiskDevice::IsMounted() const return m_isMounted; } -std::string CUDiskDevice::GetDisplayName() const -{ - return m_Label; -} - -std::string CUDiskDevice::GetMountPoint() const -{ - return m_MountPath; -} - bool CUDiskDevice::IsSystemInternal() const { return m_isSystemInternal; diff --git a/xbmc/platform/linux/storage/UDisksProvider.h b/xbmc/platform/linux/storage/UDisksProvider.h index c5372f8a35445..2f0ab8484a49b 100644 --- a/xbmc/platform/linux/storage/UDisksProvider.h +++ b/xbmc/platform/linux/storage/UDisksProvider.h @@ -54,12 +54,12 @@ class CUDiskDevice /*! \brief Get the device display name/label * @return the device display name/label */ - std::string GetDisplayName() const; + const std::string& GetDisplayName() const { return m_Label; } /*! \brief Get the device mount point * @return the device mount point */ - std::string GetMountPoint() const; + const std::string& GetMountPoint() const { return m_MountPath; } /*! \brief Get a representation of the device as a readable string * @return device as a string diff --git a/xbmc/pvr/PVRContextMenus.h b/xbmc/pvr/PVRContextMenus.h index 357db17063f38..4e79c45a4f714 100644 --- a/xbmc/pvr/PVRContextMenus.h +++ b/xbmc/pvr/PVRContextMenus.h @@ -42,7 +42,7 @@ class CPVRContextMenuManager public: static CPVRContextMenuManager& GetInstance(); - std::vector> GetMenuItems() const { return m_items; } + const std::vector>& GetMenuItems() const { return m_items; } void AddMenuHook(const CPVRClientMenuHook& hook); void RemoveMenuHook(const CPVRClientMenuHook& hook); diff --git a/xbmc/pvr/addons/PVRClientMenuHooks.cpp b/xbmc/pvr/addons/PVRClientMenuHooks.cpp index 9d58f1a915dba..c5380e2456cef 100644 --- a/xbmc/pvr/addons/PVRClientMenuHooks.cpp +++ b/xbmc/pvr/addons/PVRClientMenuHooks.cpp @@ -79,11 +79,6 @@ bool CPVRClientMenuHook::IsSettingsHook() const return m_hook->category == PVR_MENUHOOK_SETTING; } -std::string CPVRClientMenuHook::GetAddonId() const -{ - return m_addonId; -} - unsigned int CPVRClientMenuHook::GetId() const { return m_hook->iHookId; diff --git a/xbmc/pvr/addons/PVRClientMenuHooks.h b/xbmc/pvr/addons/PVRClientMenuHooks.h index 69d7f79539da1..7b7ef02149ebe 100644 --- a/xbmc/pvr/addons/PVRClientMenuHooks.h +++ b/xbmc/pvr/addons/PVRClientMenuHooks.h @@ -35,7 +35,7 @@ namespace PVR bool IsDeletedRecordingHook() const; bool IsSettingsHook() const; - std::string GetAddonId() const; + const std::string& GetAddonId() const { return m_addonId; } unsigned int GetId() const; unsigned int GetLabelId() const; std::string GetLabel() const; diff --git a/xbmc/pvr/epg/EpgInfoTag.cpp b/xbmc/pvr/epg/EpgInfoTag.cpp index 0b84e88c2e5df..873fd27b0cd44 100644 --- a/xbmc/pvr/epg/EpgInfoTag.cpp +++ b/xbmc/pvr/epg/EpgInfoTag.cpp @@ -326,41 +326,6 @@ int CPVREpgInfoTag::GetDuration() const return end - start > 0 ? end - start : 3600; } -std::string CPVREpgInfoTag::Title() const -{ - return m_strTitle; -} - -std::string CPVREpgInfoTag::PlotOutline() const -{ - return m_strPlotOutline; -} - -std::string CPVREpgInfoTag::Plot() const -{ - return m_strPlot; -} - -std::string CPVREpgInfoTag::OriginalTitle() const -{ - return m_strOriginalTitle; -} - -const std::vector CPVREpgInfoTag::Cast() const -{ - return m_cast; -} - -const std::vector CPVREpgInfoTag::Directors() const -{ - return m_directors; -} - -const std::vector CPVREpgInfoTag::Writers() const -{ - return m_writers; -} - const std::string CPVREpgInfoTag::GetCastLabel() const { // Note: see CVideoInfoTag::GetCast for reference implementation. @@ -396,11 +361,6 @@ int CPVREpgInfoTag::Year() const return m_iYear; } -std::string CPVREpgInfoTag::IMDBNumber() const -{ - return m_strIMDBNumber; -} - int CPVREpgInfoTag::GenreType() const { return m_iGenreType; @@ -411,11 +371,6 @@ int CPVREpgInfoTag::GenreSubType() const return m_iGenreSubType; } -std::string CPVREpgInfoTag::GenreDescription() const -{ - return m_strGenreDescription; -} - const std::vector CPVREpgInfoTag::Genre() const { if (m_genre.empty()) @@ -448,11 +403,6 @@ int CPVREpgInfoTag::ParentalRating() const return m_iParentalRating; } -std::string CPVREpgInfoTag::ParentalRatingCode() const -{ - return m_strParentalRatingCode; -} - int CPVREpgInfoTag::StarRating() const { return m_iStarRating; @@ -463,11 +413,6 @@ int CPVREpgInfoTag::SeriesNumber() const return m_iSeriesNumber; } -std::string CPVREpgInfoTag::SeriesLink() const -{ - return m_strSeriesLink; -} - int CPVREpgInfoTag::EpisodeNumber() const { return m_iEpisodeNumber; @@ -478,11 +423,6 @@ int CPVREpgInfoTag::EpisodePart() const return m_iEpisodePart; } -std::string CPVREpgInfoTag::EpisodeName() const -{ - return m_strEpisodeName; -} - std::string CPVREpgInfoTag::IconPath() const { return m_iconPath.GetLocalImage(); diff --git a/xbmc/pvr/epg/EpgInfoTag.h b/xbmc/pvr/epg/EpgInfoTag.h index f08f44821b35b..4e3a5ee54085d 100644 --- a/xbmc/pvr/epg/EpgInfoTag.h +++ b/xbmc/pvr/epg/EpgInfoTag.h @@ -193,43 +193,43 @@ class CPVREpgInfoTag final : public ISerializable, * @brief Get the title of this event. * @return The title. */ - std::string Title() const; + const std::string& Title() const { return m_strTitle; } /*! * @brief Get the plot outline of this event. * @return The plot outline. */ - std::string PlotOutline() const; + const std::string& PlotOutline() const { return m_strPlotOutline; } /*! * @brief Get the plot of this event. * @return The plot. */ - std::string Plot() const; + const std::string& Plot() const { return m_strPlot; } /*! * @brief Get the original title of this event. * @return The original title. */ - std::string OriginalTitle() const; + const std::string& OriginalTitle() const { return m_strOriginalTitle; } /*! * @brief Get the cast of this event. * @return The cast. */ - const std::vector Cast() const; + const std::vector& Cast() const { return m_cast; } /*! * @brief Get the director(s) of this event. * @return The director(s). */ - const std::vector Directors() const; + const std::vector& Directors() const { return m_directors; } /*! * @brief Get the writer(s) of this event. * @return The writer(s). */ - const std::vector Writers() const; + const std::vector& Writers() const { return m_writers; } /*! * @brief Get the cast of this event as formatted string. @@ -265,7 +265,7 @@ class CPVREpgInfoTag final : public ISerializable, * @brief Get the imdbnumber of this event. * @return The imdbnumber. */ - std::string IMDBNumber() const; + const std::string& IMDBNumber() const { return m_strIMDBNumber; } /*! * @brief Get the genre type ID of this event. @@ -283,7 +283,7 @@ class CPVREpgInfoTag final : public ISerializable, * @brief Get the genre description of this event. * @return The genre. */ - std::string GenreDescription() const; + const std::string& GenreDescription() const { return m_strGenreDescription; } /*! * @brief Get the genre as human readable string. @@ -307,7 +307,7 @@ class CPVREpgInfoTag final : public ISerializable, * @brief Get the parental rating code of this event. * @return The parental rating code. */ - std::string ParentalRatingCode() const; + const std::string& ParentalRatingCode() const { return m_strParentalRatingCode; } /*! * @brief Get the star rating of this event. @@ -325,7 +325,7 @@ class CPVREpgInfoTag final : public ISerializable, * @brief The series link for this event. * @return The series link or empty string, if not available. */ - std::string SeriesLink() const; + const std::string& SeriesLink() const { return m_strSeriesLink; } /*! * @brief The episode number of this event. @@ -343,7 +343,7 @@ class CPVREpgInfoTag final : public ISerializable, * @brief The episode name of this event. * @return The episode name. */ - std::string EpisodeName() const; + const std::string& EpisodeName() const { return m_strEpisodeName; } /*! * @brief Get the path to the icon for this event used by Kodi. diff --git a/xbmc/pvr/recordings/PVRRecording.h b/xbmc/pvr/recordings/PVRRecording.h index bf8ca07188491..a9d43374ae160 100644 --- a/xbmc/pvr/recordings/PVRRecording.h +++ b/xbmc/pvr/recordings/PVRRecording.h @@ -264,7 +264,7 @@ class CPVRRecording final : public CVideoInfoTag * @brief Get the recording ID as upplied by the client * @return the recording identifier */ - std::string ClientRecordingID() const { return m_strRecordingId; } + const std::string& ClientRecordingID() const { return m_strRecordingId; } /*! * @brief Get the recording ID as upplied by the client @@ -282,7 +282,7 @@ class CPVRRecording final : public CVideoInfoTag * @brief Get the directory for this recording * @return the directory */ - std::string Directory() const { return m_strDirectory; } + const std::string& Directory() const { return m_strDirectory; } /*! * @brief Get the priority for this recording @@ -306,7 +306,7 @@ class CPVRRecording final : public CVideoInfoTag * @brief Get the channel name for this recording * @return the channel name */ - std::string ChannelName() const { return m_strChannelName; } + const std::string& ChannelName() const { return m_strChannelName; } /*! * @brief Return the icon path as given by the client. @@ -348,7 +348,7 @@ class CPVRRecording final : public CVideoInfoTag * @brief Retrieve the recording Episode Name * @note Returns an empty string if no Episode Name was provided by the PVR client */ - std::string EpisodeName() const { return m_strShowTitle; } + const std::string& EpisodeName() const { return m_strShowTitle; } /*! * @brief check whether this recording is currently in progress @@ -386,7 +386,7 @@ class CPVRRecording final : public CVideoInfoTag * @brief Get the genre as human readable string. * @return The genre. */ - const std::vector Genre() const { return m_genre; } + const std::vector& Genre() const { return m_genre; } /*! * @brief Get the genre(s) of this recording as formatted string. diff --git a/xbmc/settings/SettingControl.h b/xbmc/settings/SettingControl.h index 160fcec19fd1d..292e243370a1c 100644 --- a/xbmc/settings/SettingControl.h +++ b/xbmc/settings/SettingControl.h @@ -194,7 +194,7 @@ class CSettingControlList : public CSettingControlFormattedRange int GetAddButtonLabel() const { return m_addButtonLabel; } void SetAddButtonLabel(int label) { m_addButtonLabel = label; } - SettingControlListValueFormatter GetFormatter() const { return m_formatter; } + const SettingControlListValueFormatter& GetFormatter() const { return m_formatter; } void SetFormatter(SettingControlListValueFormatter formatter) { m_formatter = formatter; } bool UseDetails() const { return m_useDetails; } @@ -238,7 +238,7 @@ class CSettingControlSlider : public ISettingControl void SetFormatString(const std::string &formatString) { m_formatString = formatString; } std::string GetDefaultFormatString() const; - SettingControlSliderFormatter GetFormatter() const { return m_formatter; } + const SettingControlSliderFormatter& GetFormatter() const { return m_formatter; } void SetFormatter(SettingControlSliderFormatter formatter) { m_formatter = formatter; } protected: diff --git a/xbmc/settings/lib/Setting.h b/xbmc/settings/lib/Setting.h index 6236cfc478e58..bc4d2cb808f27 100644 --- a/xbmc/settings/lib/Setting.h +++ b/xbmc/settings/lib/Setting.h @@ -326,7 +326,7 @@ class CSettingInt : public CTraitedSetting m_optionsFiller = optionsFiller; m_optionsFillerData = data; } - IntegerSettingOptions GetDynamicOptions() const { return m_dynamicOptions; } + const IntegerSettingOptions& GetDynamicOptions() const { return m_dynamicOptions; } IntegerSettingOptions UpdateDynamicOptions(); SettingOptionsSort GetOptionsSort() const { return m_optionsSort; } void SetOptionsSort(SettingOptionsSort optionsSort) { m_optionsSort = optionsSort; } @@ -470,7 +470,7 @@ class CSettingString : public CTraitedSetting m_optionsFiller = optionsFiller; m_optionsFillerData = data; } - StringSettingOptions GetDynamicOptions() const { return m_dynamicOptions; } + const StringSettingOptions& GetDynamicOptions() const { return m_dynamicOptions; } StringSettingOptions UpdateDynamicOptions(); SettingOptionsSort GetOptionsSort() const { return m_optionsSort; } void SetOptionsSort(SettingOptionsSort optionsSort) { m_optionsSort = optionsSort; } diff --git a/xbmc/storage/cdioSupport.h b/xbmc/storage/cdioSupport.h index b786ff2d3f942..d1a6823a185c5 100644 --- a/xbmc/storage/cdioSupport.h +++ b/xbmc/storage/cdioSupport.h @@ -121,7 +121,7 @@ class CCdInfo } trackinfo GetTrackInformation( int nTrack ) { return m_ti[nTrack -1]; } - xbmc_cdtext_t GetDiscCDTextInformation() { return m_cdtext; } + const xbmc_cdtext_t& GetDiscCDTextInformation() const { return m_cdtext; } bool HasDataTracks() { return (m_nNumData > 0); } bool HasAudioTracks() { return (m_nNumAudio > 0); } @@ -133,7 +133,7 @@ class CCdInfo int GetAudioTrackCount() { return m_nNumAudio; } uint32_t GetCddbDiscId() { return m_ulCddbDiscId; } int GetDiscLength() { return m_nLength; } - std::string GetDiscLabel(){ return m_strDiscLabel; } + const std::string& GetDiscLabel() const { return m_strDiscLabel; } // CD-ROM with ISO 9660 filesystem bool IsIso9660( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_ISO_9660); } diff --git a/xbmc/test/TestUtils.cpp b/xbmc/test/TestUtils.cpp index 1289c89ca2d85..5face64555a4b 100644 --- a/xbmc/test/TestUtils.cpp +++ b/xbmc/test/TestUtils.cpp @@ -52,10 +52,7 @@ class CTempFile : public XFILE::CFile Close(); return CFile::Delete(m_ptempFilePath); }; - std::string getTempFilePath() const - { - return m_ptempFilePath; - } + const std::string& getTempFilePath() const { return m_ptempFilePath; } std::string getTempFileDirectory() const { return URIUtils::GetDirectory(m_ptempFilePath); diff --git a/xbmc/utils/CPUInfo.h b/xbmc/utils/CPUInfo.h index e11384e8a5f38..a53ec29c6eebb 100644 --- a/xbmc/utils/CPUInfo.h +++ b/xbmc/utils/CPUInfo.h @@ -88,12 +88,12 @@ class CCPUInfo unsigned int GetCPUFeatures() const { return m_cpuFeatures; } int GetCPUCount() const { return m_cpuCount; } - std::string GetCPUModel() { return m_cpuModel; } - std::string GetCPUBogoMips() { return m_cpuBogoMips; } - std::string GetCPUSoC() { return m_cpuSoC; } - std::string GetCPUHardware() { return m_cpuHardware; } - std::string GetCPURevision() { return m_cpuRevision; } - std::string GetCPUSerial() { return m_cpuSerial; } + const std::string& GetCPUModel() const { return m_cpuModel; } + const std::string& GetCPUBogoMips() const { return m_cpuBogoMips; } + const std::string& GetCPUSoC() const { return m_cpuSoC; } + const std::string& GetCPUHardware() const { return m_cpuHardware; } + const std::string& GetCPURevision() const { return m_cpuRevision; } + const std::string& GetCPUSerial() const { return m_cpuSerial; } protected: CCPUInfo() = default; diff --git a/xbmc/utils/CharsetConverter.cpp b/xbmc/utils/CharsetConverter.cpp index c8ec0529ccb93..7def229fcbfa6 100644 --- a/xbmc/utils/CharsetConverter.cpp +++ b/xbmc/utils/CharsetConverter.cpp @@ -93,8 +93,8 @@ class CConverterType : public CCriticalSection void Reset(void); void ReinitTo(const std::string& sourceCharset, const std::string& targetCharset, unsigned int targetSingleCharMaxLen = 1); - std::string GetSourceCharset(void) const { return m_sourceCharset; } - std::string GetTargetCharset(void) const { return m_targetCharset; } + const std::string& GetSourceCharset() const { return m_sourceCharset; } + const std::string& GetTargetCharset() const { return m_targetCharset; } unsigned int GetTargetSingleCharMaxLen(void) const { return m_targetSingleCharMaxLen; } private: diff --git a/xbmc/utils/ExecString.h b/xbmc/utils/ExecString.h index c66b6d2185033..67d91f74d3fb5 100644 --- a/xbmc/utils/ExecString.h +++ b/xbmc/utils/ExecString.h @@ -24,12 +24,12 @@ class CExecString virtual ~CExecString() = default; - std::string GetExecString() const { return m_execString; } + const std::string& GetExecString() const { return m_execString; } bool IsValid() const { return m_valid; } - std::string GetFunction() const { return m_function; } - std::vector GetParams() const { return m_params; } + const std::string& GetFunction() const { return m_function; } + const std::vector& GetParams() const { return m_params; } private: bool Parse(const std::string& execString); diff --git a/xbmc/utils/HttpHeader.h b/xbmc/utils/HttpHeader.h index 6d8b5433aaa29..b1e6a3ce19022 100644 --- a/xbmc/utils/HttpHeader.h +++ b/xbmc/utils/HttpHeader.h @@ -32,8 +32,7 @@ class CHttpHeader std::string GetMimeType(void) const; std::string GetCharset(void) const; - inline std::string GetProtoLine() const - { return m_protoLine; } + inline const std::string& GetProtoLine() const { return m_protoLine; } inline bool IsHeaderDone(void) const { return m_headerdone; } diff --git a/xbmc/utils/XBMCTinyXML.h b/xbmc/utils/XBMCTinyXML.h index 2f4e1888da196..296344ffdd867 100644 --- a/xbmc/utils/XBMCTinyXML.h +++ b/xbmc/utils/XBMCTinyXML.h @@ -46,8 +46,8 @@ class CXBMCTinyXML : public TiXmlDocument bool SaveFile(const std::string& filename) const; bool Parse(const std::string& data, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING); bool Parse(const std::string& data, const std::string& dataCharset); - inline std::string GetSuggestedCharset(void) const { return m_SuggestedCharset; } - inline std::string GetUsedCharset(void) const { return m_UsedCharset; } + inline const std::string& GetSuggestedCharset() const { return m_SuggestedCharset; } + inline const std::string& GetUsedCharset() const { return m_UsedCharset; } static bool Test(); protected: using TiXmlDocument::Parse; diff --git a/xbmc/video/ContextMenus.cpp b/xbmc/video/ContextMenus.cpp index af65bf1eb89d2..0d463b81050d7 100644 --- a/xbmc/video/ContextMenus.cpp +++ b/xbmc/video/ContextMenus.cpp @@ -177,7 +177,7 @@ namespace { bool ExecuteAction(const CExecString& execute) { - const std::string execStr{execute.GetExecString()}; + const std::string& execStr{execute.GetExecString()}; if (!execStr.empty()) { CGUIMessage message(GUI_MSG_EXECUTE, 0, 0); diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 7a837b6f11605..994cf34a68928 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -12852,7 +12852,7 @@ std::vector CVideoDatabase::GetUsedImages( auto imageFile = IMAGE_FILES::CImageFileURL(image); if (imageFile.GetSpecialType() == "video" && !imageFile.GetOption("chapter").empty()) { - auto target = imageFile.GetTargetFile(); + const auto& target = imageFile.GetTargetFile(); auto quickFind = std::find(foundVideoFiles.begin(), foundVideoFiles.end(), target); if (quickFind != foundVideoFiles.end()) { diff --git a/xbmc/video/VideoGeneratedImageFileLoader.cpp b/xbmc/video/VideoGeneratedImageFileLoader.cpp index f98e94fba594b..416a710136c36 100644 --- a/xbmc/video/VideoGeneratedImageFileLoader.cpp +++ b/xbmc/video/VideoGeneratedImageFileLoader.cpp @@ -64,7 +64,7 @@ std::unique_ptr CVideoGeneratedImageFileLoader::Load( return {}; } - std::string filePath = imageFile.GetTargetFile(); + const std::string& filePath = imageFile.GetTargetFile(); CFileItem item{filePath, false}; if (URIUtils::IsInRAR(filePath)) diff --git a/xbmc/windowing/X11/GLContext.h b/xbmc/windowing/X11/GLContext.h index 95e22ecebeba9..c44adb8084f82 100644 --- a/xbmc/windowing/X11/GLContext.h +++ b/xbmc/windowing/X11/GLContext.h @@ -31,7 +31,7 @@ class CGLContext virtual uint64_t GetVblankTiming(uint64_t& msc, uint64_t& interval) { return 0; } bool IsExtSupported(const char* extension) const; - std::string ExtPrefix() { return m_extPrefix; } + const std::string& ExtPrefix() const { return m_extPrefix; } std::string m_extPrefix; std::string m_extensions; From 97fe73af3d1306d656c66b0a3a2d06bb64ba6bd1 Mon Sep 17 00:00:00 2001 From: Rechi Date: Fri, 2 Aug 2024 19:06:53 +0200 Subject: [PATCH 343/651] [cppcheck] returnStdMoveLocal --- xbmc/imagefiles/ImageCacheCleaner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/imagefiles/ImageCacheCleaner.cpp b/xbmc/imagefiles/ImageCacheCleaner.cpp index 6821b8dc434c1..ae7d7401d3d52 100644 --- a/xbmc/imagefiles/ImageCacheCleaner.cpp +++ b/xbmc/imagefiles/ImageCacheCleaner.cpp @@ -22,7 +22,7 @@ std::optional CImageCacheCleaner::Create() { auto result = CImageCacheCleaner(); if (result.m_valid) - return std::move(result); + return result; return {}; } From 8cdd5082234cf61a8c3c4a130b3b1702d618e78b Mon Sep 17 00:00:00 2001 From: Rechi Date: Fri, 2 Aug 2024 19:06:53 +0200 Subject: [PATCH 344/651] [cppcheck] stlIfStrFind --- xbmc/URL.cpp | 2 +- .../httprequesthandler/HTTPImageTransformationHandler.cpp | 2 +- xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/xbmc/URL.cpp b/xbmc/URL.cpp index 073d1104e00a5..3102a4d69f10c 100644 --- a/xbmc/URL.cpp +++ b/xbmc/URL.cpp @@ -259,7 +259,7 @@ void CURL::Parse(std::string strURL1) // if [] found, let's store string inside as hostname // and remove that parsed part from strHostNameAndPort size_t iBrk = strHostNameAndPort.rfind(']'); - if (iBrk != std::string::npos && strHostNameAndPort.find('[') == 0) + if (iBrk != std::string::npos && strHostNameAndPort.starts_with('[')) { m_strHostName = strHostNameAndPort.substr(1, iBrk-1); strHostNameAndPort.erase(0, iBrk+1); diff --git a/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.cpp b/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.cpp index 71f7a93731157..6510376fdcef4 100644 --- a/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.cpp +++ b/xbmc/network/httprequesthandler/HTTPImageTransformationHandler.cpp @@ -95,7 +95,7 @@ CHTTPImageTransformationHandler::~CHTTPImageTransformationHandler() bool CHTTPImageTransformationHandler::CanHandleRequest(const HTTPRequest &request) const { if ((request.method != GET && request.method != HEAD) || - request.pathUrl.find(ImageBasePath) != 0 || request.pathUrl.size() <= ImageBasePath.size()) + !request.pathUrl.starts_with(ImageBasePath) || request.pathUrl.size() <= ImageBasePath.size()) return false; // get the transformation options diff --git a/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.cpp b/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.cpp index fe6e76038c183..6f8508fa57cba 100644 --- a/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.cpp +++ b/xbmc/network/httprequesthandler/HTTPWebinterfaceHandler.cpp @@ -83,7 +83,7 @@ bool CHTTPWebinterfaceHandler::ResolveAddon(const std::string &url, ADDON::Addon std::string path = url; // check if the URL references a specific addon - if (url.find("/addons/") == 0 && url.size() > 8) + if (url.starts_with("/addons/") && url.size() > 8) { std::vector components; StringUtils::Tokenize(path, components, WEBSERVER_DIRECTORY_SEPARATOR); From f3217b3c4578586f37252dc473a4f2bdfd60e9ce Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 2 Aug 2024 19:14:58 +0200 Subject: [PATCH 345/651] [PVR] Fix CAddonRecording member init. --- xbmc/pvr/addons/PVRClient.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index abdad03aca196..e5cada8e319fb 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -121,7 +121,7 @@ class CAddonRecording : public PVR_RECORDING m_iconPath(recording.ClientIconPath()), m_thumbnailPath(recording.ClientThumbnailPath()), m_fanartPath(recording.ClientFanartPath()), - m_firstAired(recording.FirstAired().GetAsW3CDate()), + m_firstAired(recording.FirstAired().IsValid() ? recording.FirstAired().GetAsW3CDate() : ""), m_providerName(recording.ProviderName()) { time_t recTime; @@ -156,8 +156,7 @@ class CAddonRecording : public PVR_RECORDING iChannelUid = recording.ChannelUid(); channelType = recording.IsRadio() ? PVR_RECORDING_CHANNEL_TYPE_RADIO : PVR_RECORDING_CHANNEL_TYPE_TV; - if (recording.FirstAired().IsValid()) - strFirstAired = m_firstAired.c_str(); + strFirstAired = m_firstAired.c_str(); iFlags = recording.Flags(); sizeInBytes = recording.GetSizeInBytes(); strProviderName = m_providerName.c_str(); From 6a9c668439bc633e3070bc95563ba11de359e15d Mon Sep 17 00:00:00 2001 From: sarbes Date: Sat, 3 Aug 2024 18:24:05 +0200 Subject: [PATCH 346/651] GUILIB: Only fade between two textures --- xbmc/guilib/GUIBorderedImage.cpp | 12 +- xbmc/guilib/GUIImage.cpp | 368 +++++++++++++++++++------------ xbmc/guilib/GUIImage.h | 20 +- 3 files changed, 246 insertions(+), 154 deletions(-) diff --git a/xbmc/guilib/GUIBorderedImage.cpp b/xbmc/guilib/GUIBorderedImage.cpp index 0ed3044a115f1..b3290f8cf2ded 100644 --- a/xbmc/guilib/GUIBorderedImage.cpp +++ b/xbmc/guilib/GUIBorderedImage.cpp @@ -39,12 +39,12 @@ CGUIBorderedImage::CGUIBorderedImage(const CGUIBorderedImage& right) void CGUIBorderedImage::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) { CGUIImage::Process(currentTime, dirtyregions); - if (!m_borderImage->GetFileName().empty() && m_texture->ReadyToRender()) + if (!m_borderImage->GetFileName().empty() && m_textureCurrent->ReadyToRender()) { - CRect rect = CRect(m_texture->GetXPosition(), m_texture->GetYPosition(), - m_texture->GetXPosition() + m_texture->GetWidth(), - m_texture->GetYPosition() + m_texture->GetHeight()); - rect.Intersect(m_texture->GetRenderRect()); + CRect rect = CRect(m_textureCurrent->GetXPosition(), m_textureCurrent->GetYPosition(), + m_textureCurrent->GetXPosition() + m_textureCurrent->GetWidth(), + m_textureCurrent->GetYPosition() + m_textureCurrent->GetHeight()); + rect.Intersect(m_textureCurrent->GetRenderRect()); m_borderImage->SetPosition(rect.x1 - m_borderSize.x1, rect.y1 - m_borderSize.y1); m_borderImage->SetWidth(rect.Width() + m_borderSize.x1 + m_borderSize.x2); m_borderImage->SetHeight(rect.Height() + m_borderSize.y1 + m_borderSize.y2); @@ -62,7 +62,7 @@ void CGUIBorderedImage::Render() if (renderFrontToBack) CGUIImage::Render(); - if (!m_borderImage->GetFileName().empty() && m_texture->ReadyToRender()) + if (!m_borderImage->GetFileName().empty() && m_textureCurrent->ReadyToRender()) m_borderImage->Render(-1); if (!renderFrontToBack) diff --git a/xbmc/guilib/GUIImage.cpp b/xbmc/guilib/GUIImage.cpp index 9b7ad23de55a7..113297792a3d1 100644 --- a/xbmc/guilib/GUIImage.cpp +++ b/xbmc/guilib/GUIImage.cpp @@ -23,22 +23,23 @@ CGUIImage::CGUIImage(int parentID, float height, const CTextureInfo& texture) : CGUIControl(parentID, controlID, posX, posY, width, height), - m_texture(CGUITexture::CreateTexture(posX, posY, width, height, texture)) + m_textureCurrent(CGUITexture::CreateTexture(posX, posY, width, height, texture)), + m_textureNext(CGUITexture::CreateTexture(posX, posY, width, height, texture)) { m_crossFadeTime = 0; m_currentFadeTime = 0; m_lastRenderTime = 0; ControlType = GUICONTROL_IMAGE; m_bDynamicResourceAlloc=false; + m_textureNext->SetFileName(""); } CGUIImage::CGUIImage(const CGUIImage& left) : CGUIControl(left), m_image(left.m_image), m_info(left.m_info), - m_texture(left.m_texture->Clone()), - m_fadingTextures(), - m_currentTexture(), + m_textureCurrent(left.m_textureCurrent->Clone()), + m_textureNext(left.m_textureNext->Clone()), m_currentFallback() { m_crossFadeTime = left.m_crossFadeTime; @@ -62,16 +63,15 @@ void CGUIImage::UpdateVisibility(const CGUIListItem *item) void CGUIImage::UpdateDiffuseColor(const CGUIListItem* item) { - if (m_texture->SetDiffuseColor(m_diffuseColor, item)) - { + if (m_textureCurrent->SetDiffuseColor(m_diffuseColor, item)) MarkDirtyRegion(); - } + m_textureNext->SetDiffuseColor(m_diffuseColor, item); } void CGUIImage::UpdateInfo(const CGUIListItem *item) { // The texture may also depend on info conditions. Update the diffuse color in that case. - if (m_texture->GetDiffuseColor().HasInfo()) + if (m_textureCurrent->GetDiffuseColor().HasInfo()) UpdateDiffuseColor(item); if (m_info.IsConstant()) @@ -90,125 +90,204 @@ void CGUIImage::UpdateInfo(const CGUIListItem *item) void CGUIImage::AllocateOnDemand() { // if we're hidden, we can free our resources and return - if (!IsVisible() && m_visible != DELAYED) + if (!IsVisible() && m_visible != DELAYED && m_bDynamicResourceAlloc) { - if (m_bDynamicResourceAlloc && m_texture->IsAllocated()) - FreeResourcesButNotAnims(); + FreeResourcesButNotAnims(); return; } // either visible or delayed - we need the resources allocated in either case - if (!m_texture->IsAllocated()) + if (!m_textureCurrent->IsAllocated()) AllocResources(); } void CGUIImage::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) { - // check whether our image failed to allocate, and if so drop back to the fallback image - if (m_texture->FailedToAlloc() && m_texture->GetFileName() != m_info.GetFallback()) - { - if (!m_currentFallback.empty() && m_texture->GetFileName() != m_currentFallback) - m_texture->SetFileName(m_currentFallback); - else - m_texture->SetFileName(m_info.GetFallback()); - } + ProcessState(); + + ProcessAllocation(); - if (m_crossFadeTime) + if (!m_isTransitioning || + (!m_textureNext->ReadyToRender() && !m_textureNext->GetFileName().empty())) + ProcessNoTransition(currentTime); + else if (!m_crossFadeTime) + ProcessInstantTransition(currentTime); + else + ProcessFadingTransition(currentTime); + + if (!m_textureCurrent->GetDiffuseColor().HasInfo()) + UpdateDiffuseColor(nullptr); + + CGUIControl::Process(currentTime, dirtyregions); +} + +void CGUIImage::ProcessState() +{ + if (!m_hasNewStagingTexture) + return; + + std::string fileName = m_nameStaging; + if (fileName.empty()) + fileName = GetFallback(fileName); + + if (m_nameCurrent == fileName || m_textureCurrent->GetFileName() == fileName) { - // make sure our texture has started allocating - if (m_texture->AllocResources()) - MarkDirtyRegion(); - - // compute the frame time - unsigned int frameTime = 0; - if (m_lastRenderTime) - frameTime = currentTime - m_lastRenderTime; - if (!frameTime) - frameTime = (unsigned int)(1000 / CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS()); - m_lastRenderTime = currentTime; - - if (m_fadingTextures.size()) // have some fading images - { // anything other than the last old texture needs to be faded out as per usual - for (std::vector::iterator i = m_fadingTextures.begin(); i != m_fadingTextures.end() - 1;) + // the current texture might be a fallback from a image which failed to + // load, and it might be the texture we want. + if (m_textureCurrent->GetFileName() == fileName && m_nameCurrent != fileName) + m_nameCurrent = fileName; + + if (m_isTransitioning) + { + if (m_textureNext->ReadyToRender() || m_textureNext->GetFileName().empty()) { - if (!ProcessFading(*i, frameTime, currentTime)) - i = m_fadingTextures.erase(i); - else - ++i; - } - - if (m_texture->ReadyToRender() || m_texture->GetFileName().empty()) - { // fade out the last one as well - if (!ProcessFading(m_fadingTextures[m_fadingTextures.size() - 1], frameTime, currentTime)) - m_fadingTextures.erase(m_fadingTextures.end() - 1); + // if the current texture (which is fading out) is our desired texture, + // reverse the animation. + std::swap(m_textureCurrent, m_textureNext); + std::swap(m_nameCurrent, m_nameNext); + m_currentFadeTime = m_crossFadeTime - m_currentFadeTime; } else - { // keep the last one fading in - CFadingTexture *texture = m_fadingTextures[m_fadingTextures.size() - 1]; - texture->m_fadeTime += frameTime; - if (texture->m_fadeTime > m_crossFadeTime) - texture->m_fadeTime = m_crossFadeTime; - - if (texture->m_texture->SetAlpha(GetFadeLevel(texture->m_fadeTime))) - MarkDirtyRegion(); - if (texture->m_texture->SetDiffuseColor(m_diffuseColor)) - MarkDirtyRegion(); - if (texture->m_texture->Process(currentTime)) - MarkDirtyRegion(); + { + // if we are about to fade but the new texture is not ready, we want to + // keep the current texture, and cancel the new texture. + m_isTransitioning = false; + m_textureNext->SetFileName(""); + m_nameNext = ""; } } - if (m_texture->ReadyToRender() || m_texture->GetFileName().empty()) - { // fade the new one in - m_currentFadeTime += frameTime; - if (m_currentFadeTime > m_crossFadeTime || frameTime == 0) // for if we allocate straight away on creation - m_currentFadeTime = m_crossFadeTime; - } - if (m_texture->SetAlpha(GetFadeLevel(m_currentFadeTime))) - MarkDirtyRegion(); + m_hasNewStagingTexture = false; + return; } - if (!m_texture->GetDiffuseColor().HasInfo()) - UpdateDiffuseColor(nullptr); + // our fading-in texture is already set + if (m_nameNext == fileName || m_textureNext->GetFileName() == fileName) + { + // the next texture might be a fallback from a image which failed to load, + // and it might be the texture we want. + if (m_textureNext->GetFileName() == fileName && m_nameNext != fileName) + m_nameNext = fileName; - if (m_texture->Process(currentTime)) - MarkDirtyRegion(); + // ensure that the transition is on its way. + if (!m_isTransitioning && + (m_textureNext->ReadyToRender() || m_textureNext->GetFileName().empty())) + m_isTransitioning = true; - CGUIControl::Process(currentTime, dirtyregions); + m_hasNewStagingTexture = false; + return; + } + + // can't set new texture during animation. + if (m_isTransitioning && (m_textureNext->ReadyToRender() || m_textureNext->GetFileName().empty())) + return; + + // finally, we can request a new image. + m_textureNext->SetFileName(fileName); + m_nameNext = fileName; + m_isTransitioning = true; + m_hasNewStagingTexture = false; } -void CGUIImage::Render() +void CGUIImage::ProcessAllocation() { - if (!IsVisible()) return; + m_textureCurrent->AllocResources(); + m_textureNext->AllocResources(); - for (auto& itr : m_fadingTextures) - itr->m_texture->Render(); + if (m_isTransitioning && m_textureNext->FailedToAlloc()) + { + if (m_textureNext->GetFileName() != m_info.GetFallback()) + m_textureNext->SetFileName(GetFallback(m_nameNext)); + else + m_textureNext->SetFileName(""); - m_texture->Render(); + m_textureNext->AllocResources(); + } - CGUIControl::Render(); + if (m_textureCurrent->FailedToAlloc()) + { + if (m_textureCurrent->GetFileName() != m_info.GetFallback()) + m_textureCurrent->SetFileName(GetFallback(m_nameCurrent)); + else + m_textureCurrent->SetFileName(""); + + m_textureCurrent->AllocResources(); + } } -bool CGUIImage::ProcessFading(CGUIImage::CFadingTexture *texture, unsigned int frameTime, unsigned int currentTime) +void CGUIImage::ProcessNoTransition(unsigned int currentTime) { - assert(texture); - if (texture->m_fadeTime <= frameTime) - { // time to kill off the texture + if (m_textureCurrent->Process(currentTime)) MarkDirtyRegion(); - delete texture; - return false; +} + +void CGUIImage::ProcessInstantTransition(unsigned int currentTime) +{ + std::swap(m_textureCurrent, m_textureNext); + std::swap(m_nameCurrent, m_nameNext); + + m_nameNext = ""; + m_textureNext->SetFileName(""); + + m_textureCurrent->Process(currentTime); + + m_isTransitioning = false; + + MarkDirtyRegion(); +} + +void CGUIImage::ProcessFadingTransition(unsigned int currentTime) +{ + unsigned int frameTime = 0; + if (m_lastRenderTime) + frameTime = currentTime - m_lastRenderTime; + if (!frameTime) + frameTime = (unsigned int)(1000 / CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS()); + m_lastRenderTime = currentTime; + + m_currentFadeTime += frameTime; + if (m_currentFadeTime > m_crossFadeTime || + frameTime == 0) // for if we allocate straight away on creation + m_currentFadeTime = m_crossFadeTime; + + if (m_currentFadeTime < m_crossFadeTime) + { + m_textureCurrent->SetAlpha(GetFadeLevel(m_crossFadeTime - m_currentFadeTime)); + + m_textureNext->SetAlpha(GetFadeLevel(m_currentFadeTime)); + m_textureNext->Process(currentTime); } - // render this texture - texture->m_fadeTime -= frameTime; + else + { + std::swap(m_textureCurrent, m_textureNext); + std::swap(m_nameCurrent, m_nameNext); - if (texture->m_texture->SetAlpha(GetFadeLevel(texture->m_fadeTime))) - MarkDirtyRegion(); - if (texture->m_texture->SetDiffuseColor(m_diffuseColor)) - MarkDirtyRegion(); - if (texture->m_texture->Process(currentTime)) - MarkDirtyRegion(); + m_textureCurrent->SetAlpha(0xff); + + m_nameNext = ""; + m_textureNext->SetFileName(""); + + m_currentFadeTime = 0; + m_lastRenderTime = 0; + m_isTransitioning = false; + } + + m_textureCurrent->Process(currentTime); + + MarkDirtyRegion(); +} + +void CGUIImage::Render() +{ + if (!IsVisible()) + return; + + if (m_isTransitioning) + m_textureNext->Render(); + + m_textureCurrent->Render(); - return true; + CGUIControl::Render(); } bool CGUIImage::OnAction(const CAction &action) @@ -239,22 +318,29 @@ bool CGUIImage::OnMessage(CGUIMessage& message) void CGUIImage::AllocResources() { - if (m_texture->GetFileName().empty()) + if (m_textureCurrent->GetFileName().empty()) return; CGUIControl::AllocResources(); - m_texture->AllocResources(); + m_textureCurrent->AllocResources(); } void CGUIImage::FreeTextures(bool immediately /* = false */) { - m_texture->FreeResources(immediately); - for (unsigned int i = 0; i < m_fadingTextures.size(); i++) - delete m_fadingTextures[i]; - m_fadingTextures.clear(); - m_currentTexture.clear(); + m_textureNext->FreeResources(immediately); + m_textureNext->SetFileName(""); + m_nameNext = ""; + + m_textureCurrent->FreeResources(immediately); if (!m_info.IsConstant()) // constant textures never change - m_texture->SetFileName(""); + { + m_textureCurrent->SetFileName(""); + m_nameCurrent = ""; + } + + m_isTransitioning = false; + m_lastRenderTime = 0; + m_currentFadeTime = 0; } void CGUIImage::FreeResources(bool immediately) @@ -265,7 +351,8 @@ void CGUIImage::FreeResources(bool immediately) void CGUIImage::SetInvalid() { - m_texture->SetInvalid(); + m_textureCurrent->SetInvalid(); + m_textureNext->SetInvalid(); CGUIControl::SetInvalid(); } @@ -281,7 +368,8 @@ void CGUIImage::FreeResourcesButNotAnims() void CGUIImage::DynamicResourceAlloc(bool bOnOff) { m_bDynamicResourceAlloc = bOnOff; - m_texture->DynamicResourceAlloc(bOnOff); + m_textureCurrent->DynamicResourceAlloc(bOnOff); + m_textureNext->DynamicResourceAlloc(bOnOff); CGUIControl::DynamicResourceAlloc(bOnOff); } @@ -292,99 +380,80 @@ bool CGUIImage::CanFocus() const float CGUIImage::GetTextureWidth() const { - return m_texture->GetTextureWidth(); + return m_textureCurrent->GetTextureWidth(); } float CGUIImage::GetTextureHeight() const { - return m_texture->GetTextureHeight(); + return m_textureCurrent->GetTextureHeight(); } CRect CGUIImage::CalcRenderRegion() const { - CRect region = m_texture->GetRenderRect(); + CRect region = m_textureCurrent->GetRenderRect(); - for (const auto& itr : m_fadingTextures) - region.Union(itr->m_texture->GetRenderRect()); + if (m_isTransitioning && m_textureNext->ReadyToRender()) + region.Union(m_textureNext->GetRenderRect()); return CGUIControl::CalcRenderRegion().Intersect(region); } const std::string &CGUIImage::GetFileName() const { - return m_texture->GetFileName(); + return m_textureCurrent->GetFileName(); } void CGUIImage::SetAspectRatio(const CAspectRatio &aspect) { - m_texture->SetAspectRatio(aspect); + m_textureCurrent->SetAspectRatio(aspect); + m_textureNext->SetAspectRatio(aspect); } void CGUIImage::SetCrossFade(unsigned int time) { m_crossFadeTime = time; - if (!m_crossFadeTime && m_texture->IsLazyLoaded() && !m_info.GetFallback().empty()) - m_crossFadeTime = 1; } void CGUIImage::SetFileName(const std::string& strFileName, bool setConstant, const bool useCache) { if (setConstant) m_info.SetLabel(strFileName, "", GetParentID()); - - // Set whether or not to use cache - m_texture->SetUseCache(useCache); - - if (m_crossFadeTime) - { - // set filename on the next texture - if (m_currentTexture == strFileName) - return; // nothing to do - we already have this image - - if (m_texture->ReadyToRender() || m_texture->GetFileName().empty()) - { // save the current image - m_fadingTextures.push_back(new CFadingTexture(m_texture.get(), m_currentFadeTime)); - MarkDirtyRegion(); - } - m_currentFadeTime = 0; - } - if (m_currentTexture != strFileName) - { // texture is changing - attempt to load it, and save the name in m_currentTexture. - // we'll check whether it loaded or not in Render() - m_currentTexture = strFileName; - if (m_texture->SetFileName(m_currentTexture)) - MarkDirtyRegion(); - } + m_nameStaging = strFileName; + m_hasNewStagingTexture = true; } #ifdef _DEBUG void CGUIImage::DumpTextureUse() { - if (m_texture->IsAllocated()) + if (m_textureCurrent->IsAllocated()) { if (GetID()) - CLog::Log(LOGDEBUG, "Image control {} using texture {}", GetID(), m_texture->GetFileName()); + CLog::Log(LOGDEBUG, "Image control {} using texture {}", GetID(), + m_textureCurrent->GetFileName()); else - CLog::Log(LOGDEBUG, "Using texture {}", m_texture->GetFileName()); + CLog::Log(LOGDEBUG, "Using texture {}", m_textureCurrent->GetFileName()); } } #endif void CGUIImage::SetWidth(float width) { - m_texture->SetWidth(width); - CGUIControl::SetWidth(m_texture->GetWidth()); + m_textureCurrent->SetWidth(width); + m_textureNext->SetWidth(width); + CGUIControl::SetWidth(m_textureCurrent->GetWidth()); } void CGUIImage::SetHeight(float height) { - m_texture->SetHeight(height); - CGUIControl::SetHeight(m_texture->GetHeight()); + m_textureCurrent->SetHeight(height); + m_textureNext->SetHeight(height); + CGUIControl::SetHeight(m_textureCurrent->GetHeight()); } void CGUIImage::SetPosition(float posX, float posY) { - m_texture->SetPosition(posX, posY); + m_textureCurrent->SetPosition(posX, posY); + m_textureNext->SetPosition(posX, posY); CGUIControl::SetPosition(posX, posY); } @@ -393,7 +462,10 @@ void CGUIImage::SetInfo(const GUIINFO::CGUIInfoLabel &info) m_info = info; // a constant image never needs updating if (m_info.IsConstant()) - m_texture->SetFileName(m_info.GetLabel(0)); + { + m_textureCurrent->SetFileName(m_info.GetLabel(0)); + m_nameCurrent = m_info.GetLabel(0); + } } unsigned char CGUIImage::GetFadeLevel(unsigned int time) const @@ -410,6 +482,14 @@ unsigned char CGUIImage::GetFadeLevel(unsigned int time) const return (unsigned char)(255.0f * (1 - pow(1-alpha, amount))/alpha); } +std::string CGUIImage::GetFallback(const std::string& currentName) +{ + if (!m_currentFallback.empty() && currentName != m_currentFallback) + return m_currentFallback; + else + return m_info.GetFallback(); +} + std::string CGUIImage::GetDescription(void) const { return GetFileName(); diff --git a/xbmc/guilib/GUIImage.h b/xbmc/guilib/GUIImage.h index e9e934767f8cb..4e93a96688354 100644 --- a/xbmc/guilib/GUIImage.h +++ b/xbmc/guilib/GUIImage.h @@ -93,7 +93,12 @@ class CGUIImage : public CGUIControl virtual void FreeTextures(bool immediately = false); void FreeResourcesButNotAnims(); unsigned char GetFadeLevel(unsigned int time) const; - bool ProcessFading(CFadingTexture *texture, unsigned int frameTime, unsigned int currentTime); + std::string GetFallback(const std::string& currentName); + void ProcessState(); + void ProcessAllocation(); + void ProcessNoTransition(unsigned int currentTime); + void ProcessInstantTransition(unsigned int currentTime); + void ProcessFadingTransition(unsigned int currentTime); /*! * \brief Update the diffuse color based on the current item infos @@ -107,9 +112,16 @@ class CGUIImage : public CGUIControl CTextureInfo m_image; KODI::GUILIB::GUIINFO::CGUIInfoLabel m_info; - std::unique_ptr m_texture; - std::vector m_fadingTextures; - std::string m_currentTexture; + bool m_isTransitioning{false}; + bool m_hasNewStagingTexture{false}; + + std::unique_ptr m_textureCurrent; + std::unique_ptr m_textureNext; + + std::string m_nameCurrent{}; + std::string m_nameNext{}; + std::string m_nameStaging{}; + std::string m_currentFallback; unsigned int m_crossFadeTime; From 53f94f7f31346844ab441daeb8204d75c07773e2 Mon Sep 17 00:00:00 2001 From: Bas Rieter Date: Sat, 3 Aug 2024 23:30:21 +0200 Subject: [PATCH 347/651] Added: Mark issues and PR's older then 365 days as `stale`. --- .github/workflows/stale.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000000..f92b30b9553b8 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,23 @@ +name: 'Close stale issues and PRs' +on: + workflow_dispatch: + schedule: + - cron: '0 */2 * * *' + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + days-before-stale: 365 + stale-issue-message: 'This issue is stale because it has been open over a year without activity. Remove stale label or comment on it to reset the stale state.' + stale-issue-label: Stale + stale-pr-message: 'This PR is stale because it has been open for over a year without activity. Remove stale label or comment on it to reset the stale state.' + stale-pr-label: Stale + operations-per-run: 100 + debug-only: true From cc5e3b9289b4a07d7b2893bfa2e3f62951d1a704 Mon Sep 17 00:00:00 2001 From: Bas Rieter Date: Sun, 4 Aug 2024 13:05:26 +0200 Subject: [PATCH 348/651] Fixed: Phrasing of messages. --- .github/workflows/stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f92b30b9553b8..27ffa8d02e60f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -15,9 +15,9 @@ jobs: - uses: actions/stale@v9 with: days-before-stale: 365 - stale-issue-message: 'This issue is stale because it has been open over a year without activity. Remove stale label or comment on it to reset the stale state.' + stale-issue-message: 'This issue is now marked stale because it has been open over a year without activity. Remove the stale label or add a comment to reset the stale state.' stale-issue-label: Stale - stale-pr-message: 'This PR is stale because it has been open for over a year without activity. Remove stale label or comment on it to reset the stale state.' + stale-pr-message: 'This pull request is now marked stale because it has been open over a year without activity. Remove the stale label or add a comment to reset the stale state.' stale-pr-label: Stale operations-per-run: 100 debug-only: true From 073286268d10f79b32dc59d3477bc54f69600797 Mon Sep 17 00:00:00 2001 From: Bas Rieter Date: Sun, 4 Aug 2024 15:42:07 +0200 Subject: [PATCH 349/651] Fixed: Specify `days-before-close` to make sure we don't close issues. --- .github/workflows/stale.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 27ffa8d02e60f..98eb8bfeede89 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -15,6 +15,7 @@ jobs: - uses: actions/stale@v9 with: days-before-stale: 365 + days-before-close: -1 stale-issue-message: 'This issue is now marked stale because it has been open over a year without activity. Remove the stale label or add a comment to reset the stale state.' stale-issue-label: Stale stale-pr-message: 'This pull request is now marked stale because it has been open over a year without activity. Remove the stale label or add a comment to reset the stale state.' From 770d2025a6ca4bfc4afd533a191fd181668c63c6 Mon Sep 17 00:00:00 2001 From: Bas Rieter Date: Sun, 4 Aug 2024 18:01:34 +0200 Subject: [PATCH 350/651] Removed: `debug-only` option to make it actually work. --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 98eb8bfeede89..686ca8d9b4ece 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -21,4 +21,4 @@ jobs: stale-pr-message: 'This pull request is now marked stale because it has been open over a year without activity. Remove the stale label or add a comment to reset the stale state.' stale-pr-label: Stale operations-per-run: 100 - debug-only: true + debug-only: false From ba63a35d3298c4495c457e4acaff2456329355de Mon Sep 17 00:00:00 2001 From: Bas Rieter Date: Mon, 5 Aug 2024 21:10:39 +0200 Subject: [PATCH 351/651] Changed: Exclude `Roadmap` labels. --- .github/workflows/stale.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 686ca8d9b4ece..d9d796b0562ca 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,9 +16,14 @@ jobs: with: days-before-stale: 365 days-before-close: -1 + stale-issue-message: 'This issue is now marked stale because it has been open over a year without activity. Remove the stale label or add a comment to reset the stale state.' stale-issue-label: Stale stale-pr-message: 'This pull request is now marked stale because it has been open over a year without activity. Remove the stale label or add a comment to reset the stale state.' stale-pr-label: Stale + + exempt-issue-labels: Roadmap + exempt-pr-labels: Roadmap + operations-per-run: 100 debug-only: false From 5498d212911cd7b4e84a8f9893a74edcfe5a8ed4 Mon Sep 17 00:00:00 2001 From: Bas Rieter Date: Mon, 5 Aug 2024 21:21:18 +0200 Subject: [PATCH 352/651] Changed: Cron for stale to once a day. --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index d9d796b0562ca..3fb1f13bef241 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,7 +2,7 @@ name: 'Close stale issues and PRs' on: workflow_dispatch: schedule: - - cron: '0 */2 * * *' + - cron: '0 2 * * *' permissions: issues: write From c5bb2800ad3fbf8319b3b1f158a4c3604f8fc5ff Mon Sep 17 00:00:00 2001 From: Bas Rieter Date: Mon, 5 Aug 2024 21:36:08 +0200 Subject: [PATCH 353/651] Fixed: Actions/Stale needs to write to the Action cache. --- .github/workflows/stale.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3fb1f13bef241..d48d39a8dec7b 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,6 +7,7 @@ on: permissions: issues: write pull-requests: write + actions: write jobs: stale: From 6fcd685e1d481854f3d7abe8cccc3b6995dceefa Mon Sep 17 00:00:00 2001 From: Bas Rieter Date: Mon, 5 Aug 2024 22:01:28 +0200 Subject: [PATCH 354/651] Changed: With our numbers of issues/pr's, we need more runs and more operations per run. --- .github/workflows/stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index d48d39a8dec7b..54d59b99756e4 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,7 +2,7 @@ name: 'Close stale issues and PRs' on: workflow_dispatch: schedule: - - cron: '0 2 * * *' + - cron: '0 */2 * * *' permissions: issues: write @@ -26,5 +26,5 @@ jobs: exempt-issue-labels: Roadmap exempt-pr-labels: Roadmap - operations-per-run: 100 + operations-per-run: 200 debug-only: false From 0c8b3beb4a824ec64970f2ddeae8b11868f7230e Mon Sep 17 00:00:00 2001 From: Bas Rieter Date: Tue, 6 Aug 2024 10:35:54 +0200 Subject: [PATCH 355/651] Changed: We need a higher rate limit otherwise it takes too much time to process the list. --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 54d59b99756e4..5c157d7cb8840 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -26,5 +26,5 @@ jobs: exempt-issue-labels: Roadmap exempt-pr-labels: Roadmap - operations-per-run: 200 + operations-per-run: 1000 debug-only: false From 2aeb57284e2e63ae8b7c951ea0c5afc02b863ca5 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:58:26 +0200 Subject: [PATCH 356/651] [PVR] CPVRClients::UpdateTimerTypes: Fix crash with addons not supporting Timer Type API. --- xbmc/pvr/addons/PVRClient.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index e5cada8e319fb..88772c6c6b57f 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -1297,16 +1297,18 @@ PVR_ERROR CPVRClient::UpdateTimerTypes() types_array = new PVR_TIMER_TYPE*[size]; // manual one time - (*types_array)[0].iId = 1; - (*types_array)[0].iAttributes = + types_array[0] = new PVR_TIMER_TYPE{}; + types_array[0]->iId = 1; + types_array[0]->iAttributes = PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | PVR_TIMER_TYPE_SUPPORTS_END_TIME | PVR_TIMER_TYPE_SUPPORTS_PRIORITY | PVR_TIMER_TYPE_SUPPORTS_LIFETIME | PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS; // manual timer rule - (*types_array)[1].iId = 2; - (*types_array)[1].iAttributes = + types_array[1] = new PVR_TIMER_TYPE{}; + types_array[1]->iId = 2; + types_array[1]->iAttributes = PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_IS_REPEATING | PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | PVR_TIMER_TYPE_SUPPORTS_END_TIME | @@ -1317,8 +1319,9 @@ PVR_ERROR CPVRClient::UpdateTimerTypes() if (m_clientCapabilities.SupportsEPG()) { // One-shot epg-based - (*types_array)[2].iId = 3; - (*types_array)[2].iAttributes = + types_array[2] = new PVR_TIMER_TYPE{}; + types_array[2]->iId = 3; + types_array[2]->iAttributes = PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE | PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | PVR_TIMER_TYPE_SUPPORTS_END_TIME | PVR_TIMER_TYPE_SUPPORTS_PRIORITY | @@ -1339,7 +1342,8 @@ PVR_ERROR CPVRClient::UpdateTimerTypes() CLog::LogF(LOGERROR, "Invalid timer type supplied by add-on {}.", GetID()); continue; } - timerTypes.emplace_back(std::make_shared(*types_array[i], m_iClientId)); + timerTypes.emplace_back( + std::make_shared(*(types_array[i]), m_iClientId)); } } @@ -1347,6 +1351,9 @@ PVR_ERROR CPVRClient::UpdateTimerTypes() if (array_owner) { // begin compat section + for (unsigned int i = 0; i < size; ++i) + delete types_array[i]; + delete[] types_array; // end compat section } From 1f5af2cffdaf7a49afd3c19bca5fc67da89e1615 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 5 Aug 2024 22:43:41 +0200 Subject: [PATCH 357/651] [PVR] When hiding a member of the all channels group we must not delete the respective database record. --- xbmc/pvr/channels/PVRChannelGroup.cpp | 10 ++++------ xbmc/pvr/channels/PVRChannelGroup.h | 6 ------ xbmc/pvr/channels/PVRChannelGroups.cpp | 2 -- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/xbmc/pvr/channels/PVRChannelGroup.cpp b/xbmc/pvr/channels/PVRChannelGroup.cpp index a7339ed1a915d..f802ba1a7ea40 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.cpp +++ b/xbmc/pvr/channels/PVRChannelGroup.cpp @@ -783,9 +783,12 @@ bool CPVRChannelGroup::RemoveFromGroup( } } - // no need to renumber if nothing was removed + // no need to delete and renumber if nothing was removed if (bReturn) + { + DeleteGroupMembersFromDb({std::make_shared(*groupMember)}); Renumber(); + } return bReturn; } @@ -878,11 +881,6 @@ void CPVRChannelGroup::Delete() } } -void CPVRChannelGroup::DeleteGroupMember(const std::shared_ptr& member) -{ - DeleteGroupMembersFromDb({member}); -} - bool CPVRChannelGroup::Renumber(RenumberMode mode /* = NORMAL */) { bool bReturn(false); diff --git a/xbmc/pvr/channels/PVRChannelGroup.h b/xbmc/pvr/channels/PVRChannelGroup.h index 0ce1bed4faaa4..f75cb00b5eb3a 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.h +++ b/xbmc/pvr/channels/PVRChannelGroup.h @@ -441,12 +441,6 @@ class CPVRChannelGroup : public IChannelGroupSettingsCallback */ void Delete(); - /*! - * @brief Remove the given group member from the database. - * @param member The member to remove from the database. - */ - void DeleteGroupMember(const std::shared_ptr& member); - /*! * @brief Whether this group is deleted. * @return True, if deleted, false otherwise. diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp index d15c8c530f081..55af6dd8da4f9 100644 --- a/xbmc/pvr/channels/PVRChannelGroups.cpp +++ b/xbmc/pvr/channels/PVRChannelGroups.cpp @@ -764,8 +764,6 @@ bool CPVRChannelGroups::RemoveFromGroup(const std::shared_ptr& if (group->RemoveFromGroup(groupMember)) { - group->DeleteGroupMember(groupMember); - // Changes in the all channels group may require resorting/renumbering of other groups. if (group->IsChannelsOwner()) UpdateChannelNumbersFromAllChannelsGroup(); From 65cde24fd8e141b0e201bdb271ef0fa50e7251e5 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 5 Aug 2024 23:02:23 +0200 Subject: [PATCH 358/651] [PVR] Slightly optimize queueing of channel group member database removal. --- xbmc/pvr/channels/PVRChannelGroup.cpp | 2 +- xbmc/pvr/channels/PVRChannelGroupMember.cpp | 10 ---------- xbmc/pvr/channels/PVRChannelGroupMember.h | 6 ------ 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/xbmc/pvr/channels/PVRChannelGroup.cpp b/xbmc/pvr/channels/PVRChannelGroup.cpp index f802ba1a7ea40..1ae0e3e1e8aa9 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.cpp +++ b/xbmc/pvr/channels/PVRChannelGroup.cpp @@ -562,7 +562,7 @@ void CPVRChannelGroup::DeleteGroupMembersFromDb( for (const auto& member : membersToDelete) { - commitPending |= member->QueueDelete(); + commitPending |= database->QueueDeleteQuery(*member); size_t queryCount = database->GetDeleteQueriesCount(); if (queryCount > CHANNEL_COMMIT_QUERY_COUNT_LIMIT) diff --git a/xbmc/pvr/channels/PVRChannelGroupMember.cpp b/xbmc/pvr/channels/PVRChannelGroupMember.cpp index de04a968cd344..8d513f55a0ee0 100644 --- a/xbmc/pvr/channels/PVRChannelGroupMember.cpp +++ b/xbmc/pvr/channels/PVRChannelGroupMember.cpp @@ -9,7 +9,6 @@ #include "PVRChannelGroupMember.h" #include "ServiceBroker.h" -#include "pvr/PVRDatabase.h" #include "pvr/PVRManager.h" #include "pvr/addons/PVRClient.h" #include "pvr/channels/PVRChannel.h" @@ -132,12 +131,3 @@ void CPVRChannelGroupMember::SetOrder(int iOrder) m_bNeedsSave = true; } } - -bool CPVRChannelGroupMember::QueueDelete() -{ - const std::shared_ptr database = CServiceBroker::GetPVRManager().GetTVDatabase(); - if (!database) - return false; - - return database->QueueDeleteQuery(*this); -} diff --git a/xbmc/pvr/channels/PVRChannelGroupMember.h b/xbmc/pvr/channels/PVRChannelGroupMember.h index f31b79363457e..d6ddd666942cd 100644 --- a/xbmc/pvr/channels/PVRChannelGroupMember.h +++ b/xbmc/pvr/channels/PVRChannelGroupMember.h @@ -77,12 +77,6 @@ class CPVRChannelGroupMember : public ISerializable, public ISortable bool IsRadio() const { return m_bIsRadio; } - /*! - * @brief Delete this group member from the database. - * @return True if it was deleted successfully, false otherwise. - */ - bool QueueDelete(); - private: int m_iGroupID = -1; int m_iGroupClientID = -1; From 6c49df769b7a21a3857b25ea12dc8ba0302051aa Mon Sep 17 00:00:00 2001 From: boogie Date: Wed, 7 Aug 2024 22:35:43 +0200 Subject: [PATCH 359/651] CDRMAtomic: Backlog only the last known good drmrequest --- xbmc/windowing/gbm/drm/DRMAtomic.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/xbmc/windowing/gbm/drm/DRMAtomic.cpp b/xbmc/windowing/gbm/drm/DRMAtomic.cpp index ff7f137d60e65..70ae92e9482dc 100644 --- a/xbmc/windowing/gbm/drm/DRMAtomic.cpp +++ b/xbmc/windowing/gbm/drm/DRMAtomic.cpp @@ -149,6 +149,11 @@ void CDRMAtomic::DrmAtomicCommit(int fb_id, int flags, bool rendered, bool video { CLog::Log(LOGERROR, "CDRMAtomic::{} - atomic commit failed: {}", __FUNCTION__, strerror(errno)); + m_atomicRequestQueue.pop_back(); + } + else if (m_atomicRequestQueue.size() > 1) + { + m_atomicRequestQueue.pop_front(); } if (m_inFenceFd != -1) @@ -164,9 +169,6 @@ void CDRMAtomic::DrmAtomicCommit(int fb_id, int flags, bool rendered, bool video strerror(errno)); } - if (m_atomicRequestQueue.size() > 1) - m_atomicRequestQueue.pop_back(); - m_atomicRequestQueue.emplace_back(std::make_unique()); m_req = m_atomicRequestQueue.back().get(); } From 1299d99d83f541213e17b76db300473d0e26b33f Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 4 Aug 2024 19:59:12 +0200 Subject: [PATCH 360/651] [PVR] Fix major design flaw to use std::hash value as persistent client UID. --- .../binary-addons/AddonInstanceHandler.cpp | 5 + .../binary-addons/AddonInstanceHandler.h | 1 + xbmc/pvr/PVRDatabase.cpp | 140 ++++++++++++++++-- xbmc/pvr/PVRDatabase.h | 12 +- xbmc/pvr/addons/PVRClientUID.cpp | 58 +++++++- xbmc/pvr/addons/PVRClientUID.h | 6 + 6 files changed, 203 insertions(+), 19 deletions(-) diff --git a/xbmc/addons/binary-addons/AddonInstanceHandler.cpp b/xbmc/addons/binary-addons/AddonInstanceHandler.cpp index 34c303b965901..695efd3290a33 100644 --- a/xbmc/addons/binary-addons/AddonInstanceHandler.cpp +++ b/xbmc/addons/binary-addons/AddonInstanceHandler.cpp @@ -79,6 +79,11 @@ std::string IAddonInstanceHandler::ID() const return m_addon ? m_addon->ID() : ""; } +AddonInstanceId IAddonInstanceHandler::InstanceID() const +{ + return m_instanceId; +} + std::string IAddonInstanceHandler::Name() const { return m_addon ? m_addon->Name() : ""; diff --git a/xbmc/addons/binary-addons/AddonInstanceHandler.h b/xbmc/addons/binary-addons/AddonInstanceHandler.h index 27ba95b2be16a..d3b40e03d435a 100644 --- a/xbmc/addons/binary-addons/AddonInstanceHandler.h +++ b/xbmc/addons/binary-addons/AddonInstanceHandler.h @@ -70,6 +70,7 @@ class IAddonInstanceHandler const std::string& UniqueWorkID() { return m_uniqueWorkID; } std::string ID() const; + AddonInstanceId InstanceID() const; std::string Name() const; std::string Author() const; std::string Icon() const; diff --git a/xbmc/pvr/PVRDatabase.cpp b/xbmc/pvr/PVRDatabase.cpp index c84b658e3c842..fb8bb87394185 100644 --- a/xbmc/pvr/PVRDatabase.cpp +++ b/xbmc/pvr/PVRDatabase.cpp @@ -9,8 +9,12 @@ #include "PVRDatabase.h" #include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" #include "dbwrappers/dataset.h" #include "pvr/addons/PVRClient.h" +#include "pvr/addons/PVRClientUID.h" #include "pvr/channels/PVRChannel.h" #include "pvr/channels/PVRChannelGroupFactory.h" #include "pvr/channels/PVRChannelGroupMember.h" @@ -29,6 +33,7 @@ #include #include #include +#include #include #include @@ -191,12 +196,12 @@ void CPVRDatabase::CreateTables() ); CLog::LogFC(LOGDEBUG, LOGPVR, "Creating table 'clients'"); - m_pDS->exec( - "CREATE TABLE clients (" - "idClient integer primary key, " - "iPriority integer" - ")" - ); + m_pDS->exec("CREATE TABLE clients (" + "idClient integer primary key, " + "iPriority integer, " + "sAddonID TEXT, " + "iInstanceID integer" + ")"); CLog::LogFC(LOGDEBUG, LOGPVR, "Creating table 'timers'"); m_pDS->exec(sqlCreateTimersTable); @@ -376,6 +381,14 @@ void CPVRDatabase::UpdateTables(int iVersion) m_pDS->exec("ALTER TABLE channels ADD sDateTimeAdded varchar(20)"); m_pDS->exec("UPDATE channels SET sDateTimeAdded = ''"); } + + if (iVersion < 46) + { + m_pDS->exec("ALTER TABLE clients ADD sAddonID TEXT"); + m_pDS->exec("ALTER TABLE clients ADD iInstanceID integer"); + + FixupClientIDs(); + } } /********** Client methods **********/ @@ -397,10 +410,10 @@ bool CPVRDatabase::Persist(const CPVRClient& client) std::unique_lock lock(m_critSection); - const std::string strQuery = PrepareSQL("REPLACE INTO clients (idClient, iPriority) VALUES (%i, %i);", - client.GetID(), client.GetPriority()); - - return ExecuteQuery(strQuery); + const std::string sql{PrepareSQL( + "REPLACE INTO clients (idClient, iPriority, sAddonID, iInstanceID) VALUES (%i, %i, '%s', %i)", + client.GetID(), client.GetPriority(), client.ID().c_str(), client.InstanceID())}; + return ExecuteQuery(sql); } bool CPVRDatabase::Delete(const CPVRClient& client) @@ -436,6 +449,113 @@ int CPVRDatabase::GetPriority(const CPVRClient& client) const return atoi(strValue.c_str()); } +void CPVRDatabase::FixupClientIDs() +{ + // Get enabled and disabled PVR client addon infos + std::vector addonInfos; + CServiceBroker::GetAddonMgr().GetAddonInfos(addonInfos, false, ADDON::AddonType::PVRDLL); + + std::vector> clientInfos; + for (const auto& addonInfo : addonInfos) + { + const std::vector instanceIds{addonInfo->GetKnownInstanceIds()}; + for (const auto instanceId : instanceIds) + { + clientInfos.emplace_back(addonInfo->ID(), instanceId, addonInfo->Name()); + } + } + + for (const auto& [addonID, instanceID, addonName] : clientInfos) + { + // Entry with legacy client id present in clients or channels table? + const CPVRClientUID uid{addonID, instanceID}; + int legacyID{uid.GetLegacyUID()}; + std::string sql{PrepareSQL("idClient = %i", legacyID)}; + int id{GetSingleValueInt("clients", "idClient", sql)}; + if (id == legacyID) + { + // Add addon id and instance id to existing clients table entry. + sql = PrepareSQL("UPDATE clients SET sAddonID = '%s', iInstanceID = %i WHERE idClient = %i", + addonID.c_str(), instanceID, legacyID); + ExecuteQuery(sql); + } + else + { + sql = PrepareSQL("iClientId = %i", legacyID); + id = GetSingleValueInt("channels", "iClientId", sql); + if (id == legacyID) + { + // Create a new entry with the legacy client id in clients table. + sql = PrepareSQL("REPLACE INTO clients (idClient, iPriority, sAddonID, iInstanceID) VALUES " + "(%i, %i, '%s', %i)", + legacyID, 0, addonID.c_str(), instanceID); + ExecuteQuery(sql); + } + else + { + // The legacy id was not found in channels table. This happens if the std::hash + // implementation changed (for example after a Kodi update), which according to std::hash + // documentation can happen: "Hash functions are only required to produce the same result + // for the same input within a single execution of a program" + + // We can only fix some of the ids in this case: We can try to find the legacy id via the + // addon's name in the providers table. This is not guaranteed to always work (theoretically + // addon name might have changed) and cannot work if providers table contains more than one + // entry for the same multi-instance addon. + + sql = PrepareSQL("SELECT iClientId FROM providers WHERE iType = 1 AND sName = '%s'", + addonName.c_str()); + + if (ResultQuery(sql)) + { + if (m_pDS->num_rows() != 1) + { + CLog::Log(LOGERROR, "Unable to fixup client id {} for addon '{}', instance {}!", + legacyID, addonID.c_str(), instanceID); + } + else + { + // There is exactly one provider with the addon name in question. + // Its client id is the legacy id we're looking for! + try + { + legacyID = m_pDS->fv("iClientId").get_asInt(); + sql = PrepareSQL( + "REPLACE INTO clients (idClient, iPriority, sAddonID, iInstanceID) VALUES " + "(%i, %i, '%s', %i)", + legacyID, 0, addonID.c_str(), instanceID); + ExecuteQuery(sql); + } + catch (...) + { + CLog::LogF(LOGERROR, "Couldn't obtain providers for addon '{}'.", addonID); + } + } + } + } + } + } +} + +int CPVRDatabase::GetClientID(const std::string& addonID, unsigned int instanceID) +{ + std::unique_lock lock(m_critSection); + + // Client id already present in clients table? + std::string sql{PrepareSQL("sAddonID = '%s' AND iInstanceID = %i", addonID.c_str(), instanceID)}; + const std::string idString{GetSingleValue("clients", "idClient", sql)}; + if (!idString.empty()) + return std::atoi(idString.c_str()); + + // Create a new entry with a new client id in clients table. + sql = PrepareSQL("INSERT INTO clients (iPriority, sAddonID, iInstanceID) VALUES (%i, '%s', %i)", + 0, addonID.c_str(), instanceID); + if (ExecuteQuery(sql)) + return static_cast(m_pDS->lastinsertid()); + + return -1; +} + /********** Channel provider methods **********/ bool CPVRDatabase::DeleteProviders() diff --git a/xbmc/pvr/PVRDatabase.h b/xbmc/pvr/PVRDatabase.h index 509360d5b6477..69272b55036c8 100644 --- a/xbmc/pvr/PVRDatabase.h +++ b/xbmc/pvr/PVRDatabase.h @@ -64,7 +64,7 @@ namespace PVR * @brief Get the minimal database version that is required to operate correctly. * @return The minimal database version. */ - int GetSchemaVersion() const override { return 45; } + int GetSchemaVersion() const override { return 46; } /*! * @brief Get the default sqlite database filename. @@ -102,6 +102,14 @@ namespace PVR */ int GetPriority(const CPVRClient& client) const; + /*! + * @brief Get the numeric client ID for given addon ID and instance ID from the database. + * @param addonID The addon ID. + * @param instanceID The instance ID. + * @return The client ID on success, -1 otherwise. + */ + int GetClientID(const std::string& addonID, unsigned int instanceID); + /*! @name Channel methods */ //@{ @@ -327,6 +335,8 @@ namespace PVR bool RemoveChannelsFromGroup(const CPVRChannelGroup& group); + void FixupClientIDs(); + mutable CCriticalSection m_critSection; }; } diff --git a/xbmc/pvr/addons/PVRClientUID.cpp b/xbmc/pvr/addons/PVRClientUID.cpp index 87883851eaa45..72b5cbed026c3 100644 --- a/xbmc/pvr/addons/PVRClientUID.cpp +++ b/xbmc/pvr/addons/PVRClientUID.cpp @@ -8,26 +8,68 @@ #include "PVRClientUID.h" +#include "pvr/PVRDatabase.h" +#include "utils/log.h" + #include +#include +#include using namespace PVR; +namespace +{ +using ClientUIDParts = std::pair; +static std::map s_idMap; +} // unnamed namespace + int CPVRClientUID::GetUID() const { if (!m_uidCreated) { - std::hash hasher; + const auto it = s_idMap.find({m_addonID, m_instanceID}); + if (it != s_idMap.cend()) + { + // Cache hit + m_uid = (*it).second; + } + else + { + // Cache miss. Read from db and cache. + CPVRDatabase db; + if (!db.Open()) + { + CLog::LogF(LOGERROR, "Unable to open TV database!"); + return -1; + } - // Note: For database backwards compatibility reasons the hash of the first instance - // must be calculated just from the addonId, not from addonId and instanceId. - m_uid = static_cast(hasher( - (m_instanceID > ADDON::ADDON_FIRST_INSTANCE_ID ? std::to_string(m_instanceID) + "@" : "") + - m_addonID)); - if (m_uid < 0) - m_uid = -m_uid; + m_uid = db.GetClientID(m_addonID, m_instanceID); + if (m_uid == -1) + { + CLog::LogF(LOGERROR, "Unable to get client id from TV database!"); + return -1; + } + s_idMap.insert({{m_addonID, m_instanceID}, m_uid}); + } m_uidCreated = true; } return m_uid; } + +int CPVRClientUID::GetLegacyUID() const +{ + // Note: For database backwards compatibility reasons the hash of the first instance + // must be calculated just from the addonId, not from addonId and instanceId. + std::string prefix; + if (m_instanceID > ADDON::ADDON_FIRST_INSTANCE_ID) + prefix = std::to_string(m_instanceID) + "@"; + + std::hash hasher; + int uid{static_cast(hasher(prefix + m_addonID))}; + if (uid < 0) + uid = -uid; + + return uid; +} diff --git a/xbmc/pvr/addons/PVRClientUID.h b/xbmc/pvr/addons/PVRClientUID.h index 5b5d1c05071e2..d3acec2dcfc63 100644 --- a/xbmc/pvr/addons/PVRClientUID.h +++ b/xbmc/pvr/addons/PVRClientUID.h @@ -30,6 +30,12 @@ class CPVRClientUID final */ int GetUID() const; + /*! + * @brief Return the numeric legacy UID (compatibility/migration purposes only). + * @return The numeric legacy UID. + */ + int GetLegacyUID() const; + private: CPVRClientUID() = delete; From d493d2279f6e6c824c9e1ed215b6b91ef9162f47 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 3 Aug 2024 13:03:08 +0200 Subject: [PATCH 361/651] [PVR] CPVRClients::UpdateClients: Add log info for clients to create / destroy. --- xbmc/pvr/addons/PVRClients.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/xbmc/pvr/addons/PVRClients.cpp b/xbmc/pvr/addons/PVRClients.cpp index 46cf2f510a8bb..9308a62a66709 100644 --- a/xbmc/pvr/addons/PVRClients.cpp +++ b/xbmc/pvr/addons/PVRClients.cpp @@ -126,9 +126,17 @@ void CPVRClients::UpdateClients( instanceEnabled = client->IsEnabled(); if (instanceEnabled) + { + CLog::LogF(LOGINFO, "Creating PVR client: addonId={}, instanceId={}, clientId={}", + addon->ID(), instanceId, clientId); clientsToCreate.emplace_back(client); + } else if (isKnownClient) + { + CLog::LogF(LOGINFO, "Destroying PVR client: addonId={}, instanceId={}, clientId={}", + addon->ID(), instanceId, clientId); clientsToDestroy.emplace_back(clientId); + } } else if (IsCreatedClient(clientId)) { @@ -140,9 +148,17 @@ void CPVRClients::UpdateClients( } if (instanceEnabled) + { + CLog::LogF(LOGINFO, "Recreating PVR client: addonId={}, instanceId={}, clientId={}", + addon->ID(), instanceId, clientId); clientsToReCreate.emplace_back(clientId, addon->Name()); + } else + { + CLog::LogF(LOGINFO, "Destroying PVR client: addonId={}, instanceId={}, clientId={}", + addon->ID(), instanceId, clientId); clientsToDestroy.emplace_back(clientId); + } } } } From 23f881e248e150e8dd72219bf601538e692bfdea Mon Sep 17 00:00:00 2001 From: CrystalP Date: Tue, 6 Aug 2024 23:25:53 -0400 Subject: [PATCH 362/651] [videodb] remove unused seasons table from episode_view --- xbmc/video/VideoDatabase.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 994cf34a68928..3b67e5430b004 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -407,8 +407,6 @@ void CVideoDatabase::CreateViews() " files.idFile=episode.idFile" " JOIN tvshow ON" " tvshow.idShow=episode.idShow" - " JOIN seasons ON" - " seasons.idSeason=episode.idSeason" " JOIN path ON" " files.idPath=path.idPath" " LEFT JOIN bookmark ON" @@ -6444,11 +6442,20 @@ void CVideoDatabase::UpdateTables(int iVersion) } m_pDS->close(); } + + if (iVersion < 133) + { + // Remove episodes with invalid idSeason values. + // Since 2015 they were masked from episode_view and are not going to be missed. + // Those records would be misses in database converted in 2015 (see videodb version 99). + + m_pDS->exec("DELETE FROM episode WHERE idSeason NOT IN (SELECT idSeason from seasons)"); + } } int CVideoDatabase::GetSchemaVersion() const { - return 132; + return 133; } bool CVideoDatabase::LookupByFolders(const std::string &path, bool shows) From 63f0f8d77bbf344be97952d7841950e37240d646 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 9 Aug 2024 04:21:46 +0200 Subject: [PATCH 363/651] [video] Change select action choose implementation to open the full context menu of the item, not a special short context menu. Brings back old behavior for PVR recordings, adds more flexibility to other video items. --- .../resources/strings.po | 1 - system/settings/settings.xml | 18 ++++----- xbmc/favourites/GUIWindowFavourites.cpp | 2 +- .../listproviders/DirectoryProvider.cpp | 2 +- xbmc/pvr/windows/GUIWindowPVRRecordings.cpp | 2 +- xbmc/video/ContextMenus.cpp | 2 +- xbmc/video/guilib/VideoAction.h | 2 +- .../guilib/VideoSelectActionProcessor.cpp | 38 +------------------ .../video/guilib/VideoSelectActionProcessor.h | 3 +- xbmc/video/windows/GUIWindowVideoBase.cpp | 2 +- 10 files changed, 17 insertions(+), 55 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index d0064af1b9a88..cc8070cf57ac8 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -15675,7 +15675,6 @@ msgstr "" #. Label to denote that there is something 'more' #. xbmc/favourites/ContextMenus.h #: xbmc/guilib/listproviders/DirectoryProvider.cpp -#: xbmc/video/guilib/VideoSelectActionProcessor.cpp #: system/settings/settings.xml msgctxt "#22082" msgid "More..." diff --git a/system/settings/settings.xml b/system/settings/settings.xml index 987e366fd8012..e5bee132ab8b0 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -1081,14 +1081,14 @@ 0 - 1 + 1 - - - - - + + + + + @@ -1100,11 +1100,11 @@ 0 - 1 + 1 - - + + diff --git a/xbmc/favourites/GUIWindowFavourites.cpp b/xbmc/favourites/GUIWindowFavourites.cpp index de3d25de371b1..daae1c3d3493a 100644 --- a/xbmc/favourites/GUIWindowFavourites.cpp +++ b/xbmc/favourites/GUIWindowFavourites.cpp @@ -90,7 +90,7 @@ class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProc return UTILS::GUILIB::CGUIContentUtils::ShowInfoForItem(*m_item); } - bool OnMoreSelected() override + bool OnChooseSelected() override { CONTEXTMENU::ShowFor(m_item, CContextMenuManager::MAIN); return true; diff --git a/xbmc/guilib/listproviders/DirectoryProvider.cpp b/xbmc/guilib/listproviders/DirectoryProvider.cpp index c71855255ae49..e0cd42d039813 100644 --- a/xbmc/guilib/listproviders/DirectoryProvider.cpp +++ b/xbmc/guilib/listproviders/DirectoryProvider.cpp @@ -516,7 +516,7 @@ class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProc return true; } - bool OnMoreSelected() override + bool OnChooseSelected() override { m_provider.OnContextMenu(m_item); return true; diff --git a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp index 0e5886a52b170..50d4b3ef523ea 100644 --- a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp @@ -268,7 +268,7 @@ class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProc return true; } - bool OnMoreSelected() override + bool OnChooseSelected() override { m_window.OnPopupMenu(m_itemIndex); return true; diff --git a/xbmc/video/ContextMenus.cpp b/xbmc/video/ContextMenus.cpp index 0d463b81050d7..3b502e1479133 100644 --- a/xbmc/video/ContextMenus.cpp +++ b/xbmc/video/ContextMenus.cpp @@ -228,7 +228,7 @@ class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProc return true; } - bool OnMoreSelected() override + bool OnChooseSelected() override { CONTEXTMENU::ShowFor(m_item, CContextMenuManager::MAIN); return true; diff --git a/xbmc/video/guilib/VideoAction.h b/xbmc/video/guilib/VideoAction.h index d8d0ccb782fc0..d2b060434b3fb 100644 --- a/xbmc/video/guilib/VideoAction.h +++ b/xbmc/video/guilib/VideoAction.h @@ -18,7 +18,7 @@ enum Action ACTION_PLAY_OR_RESUME = 1, // if resume is possible, ask user. play from beginning otherwise ACTION_RESUME = 2, // resume if possibly, play from beginning otherwise ACTION_INFO = 3, - ACTION_MORE = 4, + // 4 unused ACTION_PLAY_FROM_BEGINNING = 5, // play from beginning, also if resume would be possible ACTION_PLAYPART = 6, ACTION_QUEUE = 7, diff --git a/xbmc/video/guilib/VideoSelectActionProcessor.cpp b/xbmc/video/guilib/VideoSelectActionProcessor.cpp index 4935d62f04eb6..5f9e9dab33866 100644 --- a/xbmc/video/guilib/VideoSelectActionProcessor.cpp +++ b/xbmc/video/guilib/VideoSelectActionProcessor.cpp @@ -11,7 +11,6 @@ #include "FileItem.h" #include "FileItemList.h" #include "ServiceBroker.h" -#include "dialogs/GUIDialogContextMenu.h" #include "dialogs/GUIDialogSelect.h" #include "filesystem/Directory.h" #include "guilib/GUIComponent.h" @@ -22,7 +21,6 @@ #include "utils/StringUtils.h" #include "utils/Variant.h" #include "video/VideoFileItemClassify.h" -#include "video/VideoInfoTag.h" #include "video/guilib/VideoGUIUtils.h" namespace KODI::VIDEO::GUILIB @@ -47,16 +45,7 @@ bool CVideoSelectActionProcessorBase::Process(Action action) switch (action) { case ACTION_CHOOSE: - { - const Action selectedAction = ChooseVideoItemSelectAction(); - if (selectedAction < 0) - { - m_userCancelled = true; - return true; // User cancelled the select menu. We're done. - } - - return Process(selectedAction); - } + return OnChooseSelected(); case ACTION_PLAYPART: { @@ -83,9 +72,6 @@ bool CVideoSelectActionProcessorBase::Process(Action action) return OnInfoSelected(); } - case ACTION_MORE: - return OnMoreSelected(); - default: break; } @@ -115,26 +101,4 @@ unsigned int CVideoSelectActionProcessorBase::ChooseStackItemPartNumber() const return dialog->GetSelectedItem() + 1; // part numbers are 1-based } -Action CVideoSelectActionProcessorBase::ChooseVideoItemSelectAction() const -{ - CContextButtons choices; - - const std::string resumeString = UTILS::GetResumeString(*m_item); - if (!resumeString.empty()) - { - choices.Add(ACTION_RESUME, resumeString); - choices.Add(ACTION_PLAY_FROM_BEGINNING, 12021); // Play from beginning - } - else - { - choices.Add(ACTION_PLAY_FROM_BEGINNING, 208); // Play - } - - choices.Add(ACTION_INFO, 22081); // Show information - choices.Add(ACTION_QUEUE, 13347); // Queue item - choices.Add(ACTION_MORE, 22082); // More - - return static_cast(CGUIDialogContextMenu::ShowAndGetChoice(choices)); -} - } // namespace KODI::VIDEO::GUILIB diff --git a/xbmc/video/guilib/VideoSelectActionProcessor.h b/xbmc/video/guilib/VideoSelectActionProcessor.h index 30e2dc10495c9..565546a19cb62 100644 --- a/xbmc/video/guilib/VideoSelectActionProcessor.h +++ b/xbmc/video/guilib/VideoSelectActionProcessor.h @@ -35,11 +35,10 @@ class CVideoSelectActionProcessorBase : public CVideoPlayActionProcessorBase virtual bool OnPlayPartSelected(unsigned int part) = 0; virtual bool OnQueueSelected() = 0; virtual bool OnInfoSelected() = 0; - virtual bool OnMoreSelected() = 0; + virtual bool OnChooseSelected() = 0; private: CVideoSelectActionProcessorBase() = delete; - Action ChooseVideoItemSelectAction() const; unsigned int ChooseStackItemPartNumber() const; }; } // namespace KODI::VIDEO::GUILIB diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index e93b2b02aa1ff..a875ae4bfaca6 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -605,7 +605,7 @@ class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProc bool OnInfoSelected() override { return m_window.OnItemInfo(*m_item); } - bool OnMoreSelected() override + bool OnChooseSelected() override { // window only shows the default version, so no window specific context menu items available if (m_item->HasVideoVersions() && !m_item->GetVideoInfoTag()->IsDefaultVideoVersion()) From a07be19af51bd9f5746679b62369957a9e2ea41a Mon Sep 17 00:00:00 2001 From: CrystalP Date: Fri, 9 Aug 2024 09:00:26 -0400 Subject: [PATCH 364/651] [cosmetic] separate c-lang format commit --- xbmc/video/VideoDatabase.cpp | 73 ++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 3b67e5430b004..e4e51a7f86b44 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -382,43 +382,42 @@ void CVideoDatabase::CreateAnalytics() void CVideoDatabase::CreateViews() { CLog::Log(LOGINFO, "create episode_view"); - std::string episodeview = PrepareSQL("CREATE VIEW episode_view AS SELECT " - " episode.*," - " files.strFileName AS strFileName," - " path.strPath AS strPath," - " files.playCount AS playCount," - " files.lastPlayed AS lastPlayed," - " files.dateAdded AS dateAdded," - " tvshow.c%02d AS strTitle," - " tvshow.c%02d AS genre," - " tvshow.c%02d AS studio," - " tvshow.c%02d AS premiered," - " tvshow.c%02d AS mpaa," - " bookmark.timeInSeconds AS resumeTimeInSeconds, " - " bookmark.totalTimeInSeconds AS totalTimeInSeconds, " - " bookmark.playerState AS playerState, " - " rating.rating AS rating, " - " rating.votes AS votes, " - " rating.rating_type AS rating_type, " - " uniqueid.value AS uniqueid_value, " - " uniqueid.type AS uniqueid_type " - "FROM episode" - " JOIN files ON" - " files.idFile=episode.idFile" - " JOIN tvshow ON" - " tvshow.idShow=episode.idShow" - " JOIN path ON" - " files.idPath=path.idPath" - " LEFT JOIN bookmark ON" - " bookmark.idFile=episode.idFile AND bookmark.type=1" - " LEFT JOIN rating ON" - " rating.rating_id=episode.c%02d" - " LEFT JOIN uniqueid ON" - " uniqueid.uniqueid_id=episode.c%02d", - VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_GENRE, - VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_PREMIERED, - VIDEODB_ID_TV_MPAA, VIDEODB_ID_EPISODE_RATING_ID, - VIDEODB_ID_EPISODE_IDENT_ID); + std::string episodeview = PrepareSQL( + "CREATE VIEW episode_view AS SELECT " + " episode.*," + " files.strFileName AS strFileName," + " path.strPath AS strPath," + " files.playCount AS playCount," + " files.lastPlayed AS lastPlayed," + " files.dateAdded AS dateAdded," + " tvshow.c%02d AS strTitle," + " tvshow.c%02d AS genre," + " tvshow.c%02d AS studio," + " tvshow.c%02d AS premiered," + " tvshow.c%02d AS mpaa," + " bookmark.timeInSeconds AS resumeTimeInSeconds, " + " bookmark.totalTimeInSeconds AS totalTimeInSeconds, " + " bookmark.playerState AS playerState, " + " rating.rating AS rating, " + " rating.votes AS votes, " + " rating.rating_type AS rating_type, " + " uniqueid.value AS uniqueid_value, " + " uniqueid.type AS uniqueid_type " + "FROM episode" + " JOIN files ON" + " files.idFile=episode.idFile" + " JOIN tvshow ON" + " tvshow.idShow=episode.idShow" + " JOIN path ON" + " files.idPath=path.idPath" + " LEFT JOIN bookmark ON" + " bookmark.idFile=episode.idFile AND bookmark.type=1" + " LEFT JOIN rating ON" + " rating.rating_id=episode.c%02d" + " LEFT JOIN uniqueid ON" + " uniqueid.uniqueid_id=episode.c%02d", + VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_GENRE, VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_PREMIERED, + VIDEODB_ID_TV_MPAA, VIDEODB_ID_EPISODE_RATING_ID, VIDEODB_ID_EPISODE_IDENT_ID); m_pDS->exec(episodeview); CLog::Log(LOGINFO, "create tvshowcounts"); From 127e70e5cfbc3e7cd696807c7e4b9ce10f0b9cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Wed, 14 Aug 2024 00:27:09 +0200 Subject: [PATCH 365/651] TestConversionMatrix: Add missing `` include --- .../VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp index cdc8b91e0e2cd..18f76ace0dd3e 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp @@ -10,6 +10,7 @@ #include "cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h" #include "xbmc/utils/MathUtils.h" +#include #include #include From 31f7ca0f6001cf904a320e6a67a1479141134c97 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 14 Aug 2024 16:06:25 +1000 Subject: [PATCH 366/651] [tools/depends][target] Fix deprecation warnings meson.build:615: WARNING: dri3 option "false" deprecated, please use "disabled" instead. meson.build:661: WARNING: gallium-vdpau option "false" deprecated, please use "disabled" instead. meson.build:803: WARNING: gallium-va option "false" deprecated, please use "disabled" instead. meson.build:847: WARNING: gallium-xa option "false" deprecated, please use "disabled" instead. --- tools/depends/target/mesa/Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/depends/target/mesa/Makefile b/tools/depends/target/mesa/Makefile index 4e88970452696..f24b673ccf101 100644 --- a/tools/depends/target/mesa/Makefile +++ b/tools/depends/target/mesa/Makefile @@ -46,13 +46,13 @@ CONFIGURE = $(NATIVEPREFIX)/bin/python3 $(NATIVEPREFIX)/bin/meson \ -Dbuild-tests=false \ -Dselinux=false \ -Dosmesa=false \ - -Ddri3=false \ + -Ddri3=disabled \ -Dglx=disabled \ -Dglvnd=false \ -Dllvm=false \ - -Dgallium-vdpau=false \ - -Dgallium-va=false \ - -Dgallium-xa=false \ + -Dgallium-vdpau=disabled \ + -Dgallium-va=disabled \ + -Dgallium-xa=disabled \ -Dgles1=disabled \ -Dgles2=enabled \ $(MESA_EXTRA) From 46072773bd18bc9e1096f3c4a3eb75c2f87e6783 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 14 Aug 2024 16:08:46 +1000 Subject: [PATCH 367/651] [tools/depends] meson.cross fix deprecation warning for pkgconfig As per https://mesonbuild.com/Release-notes-for-1-3-0.html#machine-files-pkgconfig-field-deprecated-and-replaced-by-pkgconfig Recommended solution for now is to duplicate, which turns off warning. DEPRECATION: "pkgconfig" entry is deprecated and should be replaced by "pkg-config" --- tools/depends/configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac index 80bca65150e54..15c395b005c4f 100644 --- a/tools/depends/configure.ac +++ b/tools/depends/configure.ac @@ -797,6 +797,7 @@ cpp = $MESON_CXX ar = '$AR' strip = '$STRIP' pkgconfig = '$prefix/$tool_dir/bin/pkg-config' +pkg-config = '$prefix/$tool_dir/bin/pkg-config' [host_machine] system = '$meson_system' From 8590cc20c62024c533a1b96ba0f5e78ffc9ec6cf Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 11 Aug 2024 17:29:12 +1000 Subject: [PATCH 368/651] [tools/depends][native] Introduce setuptools python module --- tools/depends/native/Makefile | 1 + .../01-distutils-flag.patch | 15 ++++++++++++ .../native/pythonmodule-setuptools/Makefile | 23 +++++++++++++++++++ .../PYTHONMODULE-SETUPTOOLS-VERSION | 4 ++++ 4 files changed, 43 insertions(+) create mode 100644 tools/depends/native/pythonmodule-setuptools/01-distutils-flag.patch create mode 100644 tools/depends/native/pythonmodule-setuptools/Makefile create mode 100644 tools/depends/native/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION diff --git a/tools/depends/native/Makefile b/tools/depends/native/Makefile index 8fcb0a83a5c7e..6705d4810a92e 100644 --- a/tools/depends/native/Makefile +++ b/tools/depends/native/Makefile @@ -89,6 +89,7 @@ openssl: zlib pcre2: cmake pugixml: cmake python3: $(EXPAT) $(LIBFFI) pkg-config zlib openssl autoconf-archive +pythonmodule-setuptools: python3 swig: bison cmake pcre2 tar: xz automake TexturePacker: cmake libpng liblzo2 giflib libjpeg-turbo diff --git a/tools/depends/native/pythonmodule-setuptools/01-distutils-flag.patch b/tools/depends/native/pythonmodule-setuptools/01-distutils-flag.patch new file mode 100644 index 0000000000000..21655c228c29b --- /dev/null +++ b/tools/depends/native/pythonmodule-setuptools/01-distutils-flag.patch @@ -0,0 +1,15 @@ +--- a/setuptools/_distutils/sysconfig.py ++++ b/setuptools/_distutils/sysconfig.py +@@ -317,6 +317,12 @@ + 'AR', + 'ARFLAGS', + ) ++ ++ # get_config_vars returns host vars. clear cflags, ldshared for crosscompile use ++ cflags = "" ++ cppflags = "" ++ ldflags = "" ++ ldshared = cc + " -shared" + + if 'CC' in os.environ: + newcc = os.environ['CC'] diff --git a/tools/depends/native/pythonmodule-setuptools/Makefile b/tools/depends/native/pythonmodule-setuptools/Makefile new file mode 100644 index 0000000000000..f9a2da83a561f --- /dev/null +++ b/tools/depends/native/pythonmodule-setuptools/Makefile @@ -0,0 +1,23 @@ +include ../../Makefile.include PYTHONMODULE-SETUPTOOLS-VERSION ../../download-files.include +PLATFORM=$(NATIVEPLATFORM) +DEPS= ../../Makefile.include Makefile PYTHONMODULE-SETUPTOOLS-VERSION ../../download-files.include 01-distutils-flag.patch + +LIBDYLIB=$(PLATFORM)/dist/$(LIBNAME)-$(VERSION)-py$(PYTHON_VERSION).egg + +all: .installed-$(PLATFORM) + +$(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) + rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM) + cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) + +.installed-$(PLATFORM): $(PLATFORM) + cd $(PLATFORM); patch -p1 -i ../01-distutils-flag.patch + cd $(PLATFORM); PYTHONPATH="$(NATIVEPREFIX)/lib/python${PYTHON_VERSION}/site-packages" $(NATIVEPREFIX)/bin/python3 setup.py install --prefix=$(NATIVEPREFIX) + touch $@ + +clean: + $(MAKE) -C $(PLATFORM) clean + rm -f .installed-$(PLATFORM) + +distclean:: + rm -rf $(PLATFORM) .installed-$(PLATFORM) diff --git a/tools/depends/native/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION b/tools/depends/native/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION new file mode 100644 index 0000000000000..1bf06499f3f8f --- /dev/null +++ b/tools/depends/native/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION @@ -0,0 +1,4 @@ +LIBNAME=setuptools +VERSION=72.1.0 +ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz +SHA512=d0a34f16dfa6bb9a6df39076cd43528cf854d343f6f801c448ea0ebab2a259aec3d03571e2a26709df6082ed2fcb6c43b86448be556fd559b6af41831b4f38e0 From 8ace54403bcbb9d0a16ab30eca0fdacaec1b23a6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 11 Aug 2024 17:39:57 +1000 Subject: [PATCH 369/651] [tools/depends][native] Bump meson 1.5.1 --- tools/depends/native/Makefile | 3 ++- tools/depends/native/meson/MESON-VERSION | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/depends/native/Makefile b/tools/depends/native/Makefile index 6705d4810a92e..0ab2fb7579d40 100644 --- a/tools/depends/native/Makefile +++ b/tools/depends/native/Makefile @@ -26,6 +26,7 @@ NATIVE= \ openssl \ pcre2 \ perlmodule-parseyapp \ + pythonmodule-setuptools \ pkg-config \ python3 \ swig \ @@ -83,7 +84,7 @@ libjpeg-turbo: cmake nasm libpng: zlib automake libtool: automake Mako: MarkupSafe -meson: python3 +meson: python3 pythonmodule-setuptools ninja: meson openssl: zlib pcre2: cmake diff --git a/tools/depends/native/meson/MESON-VERSION b/tools/depends/native/meson/MESON-VERSION index cabee64aa20b9..c2f22ee47b688 100644 --- a/tools/depends/native/meson/MESON-VERSION +++ b/tools/depends/native/meson/MESON-VERSION @@ -1,4 +1,4 @@ LIBNAME=meson -VERSION=1.2.1 +VERSION=1.5.1 ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz -SHA512=6221a14a6046aaba2c6eb601a9a5b928308bbd9da813ccec16b8f7578296b27d741e30e9343723770c3c7825c86b53193b41b9672dd17468d06d3b8d743bf52e +SHA512=3239d6f3d64dcedddd456dc451278a37aa6c4460708b0efdff1b04b6e8844c20f5f882060de311c59a678bebd51ee09e1906c9384d4b0c85b28015fd1713ab0a From a82d75066de226d08f4d0480e6831850424fe950 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 13 Aug 2024 23:22:05 +0200 Subject: [PATCH 370/651] [video][windows] Fix videos provided by plugins not played from info dialog for certain settings combination. --- xbmc/video/guilib/VideoGUIUtils.cpp | 13 ++++++++----- xbmc/windows/GUIMediaWindow.cpp | 8 ++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/xbmc/video/guilib/VideoGUIUtils.cpp b/xbmc/video/guilib/VideoGUIUtils.cpp index 70c60037510ab..e12f072016a76 100644 --- a/xbmc/video/guilib/VideoGUIUtils.cpp +++ b/xbmc/video/guilib/VideoGUIUtils.cpp @@ -141,12 +141,15 @@ void CAsyncGetItemsForPlaylist::GetItemsForPlaylist(const std::shared_ptrm_bIsFolder) { - // check if it's a folder with dvd or bluray files, then just add the relevant file - const std::string mediapath = VIDEO::UTILS::GetOpticalMediaPath(*item); - if (!mediapath.empty()) + if (!item->IsPlugin()) { - m_queuedItems.Add(std::make_shared(mediapath, false)); - return; + // check if it's a folder with dvd or bluray files, then just add the relevant file + const std::string mediapath = VIDEO::UTILS::GetOpticalMediaPath(*item); + if (!mediapath.empty()) + { + m_queuedItems.Add(std::make_shared(mediapath, false)); + return; + } } // Check if we add a locked share diff --git a/xbmc/windows/GUIMediaWindow.cpp b/xbmc/windows/GUIMediaWindow.cpp index afb236c2050a5..dffccda6211ef 100644 --- a/xbmc/windows/GUIMediaWindow.cpp +++ b/xbmc/windows/GUIMediaWindow.cpp @@ -769,6 +769,12 @@ bool CGUIMediaWindow::GetDirectory(const std::string &strDirectory, CFileItemLis m_history.RemoveParentPath(); } + // Store parent path along with item as parent path cannot safely be calculated from item's path. + for (const auto& item : items) + { + item->SetProperty("ParentPath", m_vecItems->GetPath()); + } + // update the view state's reference to the current items m_guiState.reset(CGUIViewState::GetViewState(GetID(), items)); @@ -1743,8 +1749,6 @@ bool CGUIMediaWindow::OnPopupMenu(int itemIdx) if (!item) return false; - item->SetProperty("ParentPath", m_vecItems->GetPath()); - CContextButtons buttons; //Add items from plugin From d0ca52f8315e47b5f8b1d433375f4d9c45f2b704 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 14 Aug 2024 22:05:37 +0200 Subject: [PATCH 371/651] [PVR] Saved searches: Add possibility to set a user defined icon for a search. --- .../resources/strings.po | 6 ++- xbmc/FileItem.cpp | 6 ++- xbmc/pvr/PVRContextMenus.cpp | 15 ++++++ xbmc/pvr/epg/EpgDatabase.cpp | 26 ++++++---- xbmc/pvr/epg/EpgDatabase.h | 2 +- xbmc/pvr/epg/EpgSearchFilter.cpp | 9 ++++ xbmc/pvr/epg/EpgSearchFilter.h | 4 ++ xbmc/pvr/guilib/PVRGUIActionsEPG.cpp | 48 +++++++++++++++++++ xbmc/pvr/guilib/PVRGUIActionsEPG.h | 7 +++ 9 files changed, 112 insertions(+), 11 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index cc8070cf57ac8..b12f00ede95ae 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -11354,23 +11354,27 @@ msgstr "" #. Label for a select option or list item, representing an icon graphic (like a TV channel icon) #: xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp +#: xbmc/pvr/guilib/PVRGUIActionsEPG.cpp msgctxt "#19282" msgid "Current icon" msgstr "" #. Label for a select option or list item, representing an icon graphic (like a TV channel icon) #: xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp +#: xbmc/pvr/guilib/PVRGUIActionsEPG.cpp msgctxt "#19283" msgid "No icon" msgstr "" -#. used by several skins +#. used by several skins and PVR +#: xbmc/pvr/PVRContextMenus.cpp msgctxt "#19284" msgid "Choose icon" msgstr "" #. Label for a select/menu option to select an icon graphic #: xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp +#: xbmc/pvr/guilib/PVRGUIActionsEPG.cpp msgctxt "#19285" msgid "Browse for icon" msgstr "" diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 9d59e55889941..481284a51e40d 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -190,7 +190,11 @@ CFileItem::CFileItem(const std::shared_ptr& filter) if (lastExec.IsValid()) m_dateTime.SetFromUTCDateTime(lastExec); - SetArt("icon", "DefaultPVRSearch.png"); + const std::string iconPath = filter->GetIconPath(); + if (!iconPath.empty()) + SetArt("icon", iconPath); + else + SetArt("icon", "DefaultPVRSearch.png"); // Speedup FillInDefaultIcon() SetProperty("icon_never_overlay", true); diff --git a/xbmc/pvr/PVRContextMenus.cpp b/xbmc/pvr/PVRContextMenus.cpp index 47004fa744912..9ab47757f70fd 100644 --- a/xbmc/pvr/PVRContextMenus.cpp +++ b/xbmc/pvr/PVRContextMenus.cpp @@ -80,6 +80,7 @@ DECL_STATICCONTEXTMENUITEM(AddReminder); DECL_STATICCONTEXTMENUITEM(ExecuteSearch); DECL_STATICCONTEXTMENUITEM(EditSearch); DECL_STATICCONTEXTMENUITEM(RenameSearch); +DECL_STATICCONTEXTMENUITEM(ChooseIconForSearch); DECL_STATICCONTEXTMENUITEM(DeleteSearch); class PVRClientMenuHook : public IContextMenuItem @@ -734,6 +735,19 @@ bool RenameSearch::Execute(const std::shared_ptr& item) const return CServiceBroker::GetPVRManager().Get().RenameSavedSearch(*item); } +/////////////////////////////////////////////////////////////////////////////// +// Choose icon for saved search + +bool ChooseIconForSearch::IsVisible(const CFileItem& item) const +{ + return item.HasEPGSearchFilter(); +} + +bool ChooseIconForSearch::Execute(const std::shared_ptr& item) const +{ + return CServiceBroker::GetPVRManager().Get().ChooseIconForSavedSearch(*item); +} + /////////////////////////////////////////////////////////////////////////////// // Delete saved search @@ -779,6 +793,7 @@ CPVRContextMenuManager::CPVRContextMenuManager() std::make_shared(137), /* Search */ std::make_shared(21450), /* Edit */ std::make_shared(118), /* Rename */ + std::make_shared(19284), /* Choose icon */ std::make_shared(117), /* Delete */ }) { diff --git a/xbmc/pvr/epg/EpgDatabase.cpp b/xbmc/pvr/epg/EpgDatabase.cpp index dbe3a49c3f6e7..8f257ae89690c 100644 --- a/xbmc/pvr/epg/EpgDatabase.cpp +++ b/xbmc/pvr/epg/EpgDatabase.cpp @@ -128,8 +128,9 @@ void CPVREpgDatabase::CreateTables() "bIgnoreFutureBroadcasts bool, " "bFreeToAirOnly bool, " "bIgnorePresentTimers bool, " - "bIgnorePresentRecordings bool," - "iChannelGroup integer" + "bIgnorePresentRecordings bool, " + "iChannelGroup integer, " + "sIconPath varchar(255)" ")"); } @@ -324,6 +325,12 @@ void CPVREpgDatabase::UpdateTables(int iVersion) m_pDS->exec("ALTER TABLE savedsearches ADD iChannelGroup integer;"); m_pDS->exec("UPDATE savedsearches SET iChannelGroup = -1"); } + + if (iVersion < 17) + { + m_pDS->exec("ALTER TABLE savedsearches ADD sIconPath varchar(255);"); + m_pDS->exec("UPDATE savedsearches SET sIconPath = ''"); + } } bool CPVREpgDatabase::DeleteEpg() @@ -1321,6 +1328,7 @@ std::shared_ptr CPVREpgDatabase::CreateEpgSearchFilter( newSearch->SetIgnorePresentTimers(m_pDS->fv("bIgnorePresentTimers").get_asBool()); newSearch->SetIgnorePresentRecordings(m_pDS->fv("bIgnorePresentRecordings").get_asBool()); newSearch->SetChannelGroupID(m_pDS->fv("iChannelGroup").get_asInt()); + newSearch->SetIconPath(m_pDS->fv("sIconPath").get_asString()); newSearch->SetChanged(false); @@ -1392,9 +1400,9 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch) "iGenreType, bIncludeUnknownGenres, sStartDateTime, sEndDateTime, iMinimumDuration, " "iMaximumDuration, bIsRadio, iClientId, iChannelUid, bRemoveDuplicates, " "bIgnoreFinishedBroadcasts, bIgnoreFutureBroadcasts, bFreeToAirOnly, bIgnorePresentTimers, " - "bIgnorePresentRecordings, iChannelGroup) " + "bIgnorePresentRecordings, iChannelGroup, sIconPath) " "VALUES ('%s', '%s', '%s', %i, %i, %i, %i, '%s', '%s', %i, %i, %i, %i, %i, %i, %i, %i, " - "%i, %i, %i, %i);", + "%i, %i, %i, %i, '%s');", epgSearch.GetTitle().c_str(), epgSearch.GetLastExecutedDateTime().IsValid() ? epgSearch.GetLastExecutedDateTime().GetAsDBDateTime().c_str() @@ -1413,7 +1421,8 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch) epgSearch.ShouldIgnoreFinishedBroadcasts() ? 1 : 0, epgSearch.ShouldIgnoreFutureBroadcasts() ? 1 : 0, epgSearch.IsFreeToAirOnly() ? 1 : 0, epgSearch.ShouldIgnorePresentTimers() ? 1 : 0, - epgSearch.ShouldIgnorePresentRecordings() ? 1 : 0, epgSearch.GetChannelGroupID()); + epgSearch.ShouldIgnorePresentRecordings() ? 1 : 0, epgSearch.GetChannelGroupID(), + epgSearch.GetIconPath().c_str()); else strQuery = PrepareSQL( "REPLACE INTO savedsearches " @@ -1421,9 +1430,9 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch) "bIsCaseSensitive, iGenreType, bIncludeUnknownGenres, sStartDateTime, sEndDateTime, " "iMinimumDuration, iMaximumDuration, bIsRadio, iClientId, iChannelUid, bRemoveDuplicates, " "bIgnoreFinishedBroadcasts, bIgnoreFutureBroadcasts, bFreeToAirOnly, bIgnorePresentTimers, " - "bIgnorePresentRecordings, iChannelGroup) " + "bIgnorePresentRecordings, iChannelGroup, sIconPath) " "VALUES (%i, '%s', '%s', '%s', %i, %i, %i, %i, '%s', '%s', %i, %i, %i, %i, %i, %i, %i, %i, " - "%i, %i, %i, %i);", + "%i, %i, %i, %i, '%s');", epgSearch.GetDatabaseId(), epgSearch.GetTitle().c_str(), epgSearch.GetLastExecutedDateTime().IsValid() ? epgSearch.GetLastExecutedDateTime().GetAsDBDateTime().c_str() @@ -1442,7 +1451,8 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch) epgSearch.ShouldIgnoreFinishedBroadcasts() ? 1 : 0, epgSearch.ShouldIgnoreFutureBroadcasts() ? 1 : 0, epgSearch.IsFreeToAirOnly() ? 1 : 0, epgSearch.ShouldIgnorePresentTimers() ? 1 : 0, - epgSearch.ShouldIgnorePresentRecordings() ? 1 : 0, epgSearch.GetChannelGroupID()); + epgSearch.ShouldIgnorePresentRecordings() ? 1 : 0, epgSearch.GetChannelGroupID(), + epgSearch.GetIconPath().c_str()); bool bReturn = ExecuteQuery(strQuery); diff --git a/xbmc/pvr/epg/EpgDatabase.h b/xbmc/pvr/epg/EpgDatabase.h index b3cb2bb265da4..32673b8e58622 100644 --- a/xbmc/pvr/epg/EpgDatabase.h +++ b/xbmc/pvr/epg/EpgDatabase.h @@ -66,7 +66,7 @@ namespace PVR * @brief Get the minimal database version that is required to operate correctly. * @return The minimal database version. */ - int GetSchemaVersion() const override { return 16; } + int GetSchemaVersion() const override { return 17; } /*! * @brief Get the default sqlite database filename. diff --git a/xbmc/pvr/epg/EpgSearchFilter.cpp b/xbmc/pvr/epg/EpgSearchFilter.cpp index 4d96993a177f5..87056db5f216a 100644 --- a/xbmc/pvr/epg/EpgSearchFilter.cpp +++ b/xbmc/pvr/epg/EpgSearchFilter.cpp @@ -251,6 +251,15 @@ void CPVREpgSearchFilter::SetTitle(const std::string& title) } } +void CPVREpgSearchFilter::SetIconPath(const std::string& iconPath) +{ + if (m_iconPath != iconPath) + { + m_iconPath = iconPath; + m_bChanged = true; + } +} + void CPVREpgSearchFilter::SetLastExecutedDateTime(const CDateTime& lastExecutedDateTime) { // Note: No need to set m_bChanged here diff --git a/xbmc/pvr/epg/EpgSearchFilter.h b/xbmc/pvr/epg/EpgSearchFilter.h index 5966f6b0577c9..89473c10eee54 100644 --- a/xbmc/pvr/epg/EpgSearchFilter.h +++ b/xbmc/pvr/epg/EpgSearchFilter.h @@ -123,6 +123,9 @@ namespace PVR const std::string& GetTitle() const { return m_title; } void SetTitle(const std::string& title); + const std::string& GetIconPath() const { return m_iconPath; } + void SetIconPath(const std::string& iconPath); + const CDateTime& GetLastExecutedDateTime() const { return m_lastExecutedDateTime; } void SetLastExecutedDateTime(const CDateTime& lastExecutedDateTime); @@ -166,6 +169,7 @@ namespace PVR int m_iDatabaseId = -1; std::string m_title; + std::string m_iconPath; CDateTime m_lastExecutedDateTime; }; } diff --git a/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp b/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp index 8dcf7113eb1e5..9b76bb7672547 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp @@ -9,7 +9,9 @@ #include "PVRGUIActionsEPG.h" #include "FileItem.h" +#include "FileItemList.h" #include "ServiceBroker.h" +#include "dialogs/GUIDialogFileBrowser.h" #include "dialogs/GUIDialogYesNo.h" #include "guilib/GUIComponent.h" #include "guilib/GUIKeyboardFactory.h" @@ -27,6 +29,7 @@ #include "pvr/windows/GUIWindowPVRSearch.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" #include "utils/Variant.h" #include "utils/log.h" @@ -200,6 +203,51 @@ bool CPVRGUIActionsEPG::RenameSavedSearch(const CFileItem& item) return false; } +bool CPVRGUIActionsEPG::ChooseIconForSavedSearch(const CFileItem& item) +{ + const auto searchFilter{item.GetEPGSearchFilter()}; + + if (!searchFilter) + { + CLog::LogF(LOGERROR, "Wrong item type. No EPG search filter present."); + return false; + } + + // setup our icon list + CFileItemList items; + + // Add the current icon, if available. + const std::string iconPath{searchFilter->GetIconPath()}; + auto current{std::make_shared("icon://Current", false)}; + current->SetArt("icon", iconPath.empty() ? "DefaultPVRSearch.png" : iconPath); + current->SetLabel(g_localizeStrings.Get(19282)); // Current icon + items.Add(std::move(current)); + + // And add a "No icon" entry as well. + auto nothumb{std::make_shared("icon://None", false)}; + nothumb->SetArt("icon", "DefaultPVRSearch.png"); + nothumb->SetLabel(g_localizeStrings.Get(19283)); // No icon + items.Add(std::move(nothumb)); + + std::string icon; + VECSOURCES sources; + CServiceBroker::GetMediaManager().GetLocalDrives(sources); + if (!CGUIDialogFileBrowser::ShowAndGetImage(items, sources, + g_localizeStrings.Get(19285), // Browse for icon + icon)) + return false; + + if (icon == "icon://Current") + return true; + + if (icon == "icon://None") + icon.clear(); + + searchFilter->SetIconPath(icon); + CServiceBroker::GetPVRManager().EpgContainer().PersistSavedSearch(*searchFilter); + return true; +} + bool CPVRGUIActionsEPG::DeleteSavedSearch(const CFileItem& item) { const auto searchFilter = item.GetEPGSearchFilter(); diff --git a/xbmc/pvr/guilib/PVRGUIActionsEPG.h b/xbmc/pvr/guilib/PVRGUIActionsEPG.h index c66740e98503f..5041b1adc37bc 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsEPG.h +++ b/xbmc/pvr/guilib/PVRGUIActionsEPG.h @@ -68,6 +68,13 @@ class CPVRGUIActionsEPG : public IPVRComponent */ bool RenameSavedSearch(const CFileItem& item); + /*! + * @brief Choose an icon for a saved search. Opens an art chooser dialog. + * @param item The item containing a search filter. + * @return True on success, false otherwise. + */ + bool ChooseIconForSavedSearch(const CFileItem& item); + /*! * @brief Delete a saved search. Opens confirmation dialog before deleting. * @param item The item containing a search filter. From 18b9791a57b70b0f66f352b748e3af9b2164b684 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 2 Sep 2023 23:52:03 +0200 Subject: [PATCH 372/651] [PVR] CPVRGUIDirectory: Add support for saved search results. --- xbmc/pvr/epg/CMakeLists.txt | 2 + xbmc/pvr/epg/EpgContainer.cpp | 6 ++- xbmc/pvr/epg/EpgContainer.h | 4 +- xbmc/pvr/epg/EpgSearch.cpp | 51 +++++++++++++++++++++++++ xbmc/pvr/epg/EpgSearch.h | 48 +++++++++++++++++++++++ xbmc/pvr/filesystem/PVRGUIDirectory.cpp | 21 ++++++++++ xbmc/pvr/filesystem/PVRGUIDirectory.h | 1 + xbmc/pvr/windows/GUIWindowPVRSearch.cpp | 26 +++---------- 8 files changed, 134 insertions(+), 25 deletions(-) create mode 100644 xbmc/pvr/epg/EpgSearch.cpp create mode 100644 xbmc/pvr/epg/EpgSearch.h diff --git a/xbmc/pvr/epg/CMakeLists.txt b/xbmc/pvr/epg/CMakeLists.txt index 0ea75b702a804..ee5e7f40152d2 100644 --- a/xbmc/pvr/epg/CMakeLists.txt +++ b/xbmc/pvr/epg/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES EpgContainer.cpp Epg.cpp EpgDatabase.cpp EpgInfoTag.cpp + EpgSearch.cpp EpgSearchFilter.cpp EpgSearchPath.cpp EpgChannelData.cpp @@ -12,6 +13,7 @@ set(HEADERS Epg.h EpgContainer.h EpgDatabase.h EpgInfoTag.h + EpgSearch.h EpgSearchData.h EpgSearchFilter.h EpgSearchPath.h diff --git a/xbmc/pvr/epg/EpgContainer.cpp b/xbmc/pvr/epg/EpgContainer.cpp index 4e20f790b1880..72521d1593e22 100644 --- a/xbmc/pvr/epg/EpgContainer.cpp +++ b/xbmc/pvr/epg/EpgContainer.cpp @@ -953,7 +953,8 @@ int CPVREpgContainer::CleanupCachedImages() }); } -std::vector> CPVREpgContainer::GetSavedSearches(bool bRadio) +std::vector> CPVREpgContainer::GetSavedSearches( + bool bRadio) const { const std::shared_ptr database = GetEpgDatabase(); if (!database) @@ -965,7 +966,8 @@ std::vector> CPVREpgContainer::GetSavedSear return database->GetSavedSearches(bRadio); } -std::shared_ptr CPVREpgContainer::GetSavedSearchById(bool bRadio, int iId) +std::shared_ptr CPVREpgContainer::GetSavedSearchById(bool bRadio, + int iId) const { const std::shared_ptr database = GetEpgDatabase(); if (!database) diff --git a/xbmc/pvr/epg/EpgContainer.h b/xbmc/pvr/epg/EpgContainer.h index 0b6c426b61046..dbfb5acf6b307 100644 --- a/xbmc/pvr/epg/EpgContainer.h +++ b/xbmc/pvr/epg/EpgContainer.h @@ -224,7 +224,7 @@ namespace PVR * @param bRadio Whether to fetch saved searches for radio or TV. * @return The searches. */ - std::vector> GetSavedSearches(bool bRadio); + std::vector> GetSavedSearches(bool bRadio) const; /*! * @brief Get the saved search matching the given id. @@ -232,7 +232,7 @@ namespace PVR * @param iId The id. * @return The saved search or nullptr if not found. */ - std::shared_ptr GetSavedSearchById(bool bRadio, int iId); + std::shared_ptr GetSavedSearchById(bool bRadio, int iId) const; /*! * @brief Persist a saved search in the database. diff --git a/xbmc/pvr/epg/EpgSearch.cpp b/xbmc/pvr/epg/EpgSearch.cpp new file mode 100644 index 0000000000000..5981b87a47a52 --- /dev/null +++ b/xbmc/pvr/epg/EpgSearch.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 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 "EpgSearch.h" + +#include "ServiceBroker.h" +#include "pvr/PVRManager.h" +#include "pvr/epg/EpgContainer.h" +#include "pvr/epg/EpgSearchFilter.h" + +#include +#include + +using namespace PVR; + +void CPVREpgSearch::Execute() +{ + std::unique_lock lock(m_critSection); + + std::vector> tags{ + CServiceBroker::GetPVRManager().EpgContainer().GetTags(m_filter.GetEpgSearchData())}; + m_filter.SetEpgSearchDataFiltered(); + + // Tags can still contain false positives, for search criteria that cannot be handled via + // database. So, run extended search filters on what we got from the database. + for (auto it = tags.cbegin(); it != tags.cend();) + { + it = tags.erase(std::remove_if(tags.begin(), tags.end(), + [this](const std::shared_ptr& entry) + { return !m_filter.FilterEntry(entry); }), + tags.cend()); + } + + if (m_filter.ShouldRemoveDuplicates()) + m_filter.RemoveDuplicates(tags); + + m_filter.SetLastExecutedDateTime(CDateTime::GetUTCDateTime()); + + m_results = tags; +} + +const std::vector>& CPVREpgSearch::GetResults() const +{ + std::unique_lock lock(m_critSection); + return m_results; +} diff --git a/xbmc/pvr/epg/EpgSearch.h b/xbmc/pvr/epg/EpgSearch.h new file mode 100644 index 0000000000000..6dbbf89b2911f --- /dev/null +++ b/xbmc/pvr/epg/EpgSearch.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 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 "threads/CriticalSection.h" + +#include +#include + +namespace PVR +{ +class CPVREpgInfoTag; +class CPVREpgSearchFilter; + +class CPVREpgSearch +{ +public: + CPVREpgSearch() = delete; + + /*! + * @brief ctor. + * @param filter The filter defining the search criteria. + */ + explicit CPVREpgSearch(CPVREpgSearchFilter& filter) : m_filter(filter) {} + + /*! + * @brief Execute the search. + */ + void Execute(); + + /*! + * @brief Get the last search results. + * @return the results. + */ + const std::vector>& GetResults() const; + +private: + mutable CCriticalSection m_critSection; + CPVREpgSearchFilter& m_filter; + std::vector> m_results; +}; +} // namespace PVR diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp index 63341bd70795c..290b2b8bce41a 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp @@ -23,6 +23,7 @@ #include "pvr/channels/PVRChannelGroupsContainer.h" #include "pvr/channels/PVRChannelsPath.h" #include "pvr/epg/EpgContainer.h" +#include "pvr/epg/EpgSearch.h" #include "pvr/epg/EpgSearchFilter.h" #include "pvr/epg/EpgSearchPath.h" #include "pvr/recordings/PVRRecording.h" @@ -224,6 +225,8 @@ bool CPVRGUIDirectory::GetDirectory(CFileItemList& results) const { if (path.IsSavedSearchesRoot()) return GetSavedSearchesDirectory(path.IsRadio(), results); + else if (path.IsSavedSearch()) + return GetSavedSearchResults(path.IsRadio(), path.GetId(), results); } return true; } @@ -435,6 +438,24 @@ bool CPVRGUIDirectory::GetSavedSearchesDirectory(bool bRadio, CFileItemList& res return true; } +bool CPVRGUIDirectory::GetSavedSearchResults(bool isRadio, int id, CFileItemList& results) const +{ + auto& epgContainer{CServiceBroker::GetPVRManager().EpgContainer()}; + const std::shared_ptr filter{epgContainer.GetSavedSearchById(isRadio, id)}; + if (filter) + { + CPVREpgSearch search(*filter); + search.Execute(); + const auto tags{search.GetResults()}; + for (const auto& tag : tags) + { + results.Add(std::make_shared(tag)); + } + return true; + } + return false; +} + bool CPVRGUIDirectory::GetChannelGroupsDirectory(bool bRadio, bool bExcludeHidden, CFileItemList& results) diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.h b/xbmc/pvr/filesystem/PVRGUIDirectory.h index 462c55360c0f3..31375ade32271 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.h +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.h @@ -100,6 +100,7 @@ class CPVRGUIDirectory bool GetTimersDirectory(CFileItemList& results) const; bool GetRecordingsDirectory(CFileItemList& results) const; bool GetSavedSearchesDirectory(bool bRadio, CFileItemList& results) const; + bool GetSavedSearchResults(bool isRadio, int id, CFileItemList& results) const; const CURL m_url; }; diff --git a/xbmc/pvr/windows/GUIWindowPVRSearch.cpp b/xbmc/pvr/windows/GUIWindowPVRSearch.cpp index 4a9ad6b10e9cc..356e65dcc08cc 100644 --- a/xbmc/pvr/windows/GUIWindowPVRSearch.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRSearch.cpp @@ -26,6 +26,7 @@ #include "pvr/epg/Epg.h" #include "pvr/epg/EpgContainer.h" #include "pvr/epg/EpgInfoTag.h" +#include "pvr/epg/EpgSearch.h" #include "pvr/epg/EpgSearchFilter.h" #include "pvr/epg/EpgSearchPath.h" #include "pvr/guilib/PVRGUIActionsEPG.h" @@ -71,27 +72,10 @@ bool AsyncSearchAction::Execute() void AsyncSearchAction::Run() { - std::vector> results = - CServiceBroker::GetPVRManager().EpgContainer().GetTags(m_filter->GetEpgSearchData()); - m_filter->SetEpgSearchDataFiltered(); - - // Tags can still contain false positives, for search criteria that cannot be handled via - // database. So, run extended search filters on what we got from the database. - for (auto it = results.begin(); it != results.end();) - { - it = results.erase(std::remove_if(results.begin(), results.end(), - [this](const std::shared_ptr& entry) { - return !m_filter->FilterEntry(entry); - }), - results.end()); - } - - if (m_filter->ShouldRemoveDuplicates()) - m_filter->RemoveDuplicates(results); - - m_filter->SetLastExecutedDateTime(CDateTime::GetUTCDateTime()); - - for (const auto& tag : results) + CPVREpgSearch search(*m_filter); + search.Execute(); + const auto tags{search.GetResults()}; + for (const auto& tag : tags) { m_items->Add(std::make_shared(tag)); } From 2e6e702de0e5c630a4e7549ad85f587d43c8bd48 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 11 Aug 2024 17:44:51 +1000 Subject: [PATCH 373/651] [tools/depends] Bump Python 3.12.5 [tools/depends][native] Bump Markupsafe 2.1.5 [tools/depends][native] Bump Mako 1.3.5 [tools/depends][target] Bump pythonmodules-setuptools 72.1.0 [tools/depends][target] Bump Python 3.12.5 [tools/depends][target] samba-gplv3 patch distutils usage python 3.12 removed distutils from core cpython, and it is now only available from setuptools. Patch to import setuptools before trying to use distutils in samba python functionality testing [tools/depends][target] Mesa support setuptools for py3.12 Fix failure to find Mako due to error on import of distutils Patch imports setuptools first, which allows distutils to succeed --- cmake/modules/FindPython.cmake | 2 +- tools/depends/Makefile.include.in | 2 +- tools/depends/native/Mako/MAKO-VERSION | 4 + tools/depends/native/Mako/Makefile | 10 +- .../01-all-GH399-removedistutils.patch | 48 +++ .../native/MarkupSafe/MARKUPSAFE-VERSION | 4 + tools/depends/native/MarkupSafe/Makefile | 12 +- .../native/python3/01-distutil-flags.patch | 12 - tools/depends/native/python3/Makefile | 7 +- tools/depends/native/python3/PYTHON3-VERSION | 4 +- .../target/mesa/01-all-py312-setuptools.patch | 10 + tools/depends/target/mesa/Makefile | 4 +- .../python3/01-py312-cpython118618-1.patch | 369 ++++++++++++++++++ .../python3/01-py312-cpython118618-2.patch | 71 ++++ .../python3/02-android-cpython114875.patch | 36 ++ .../target/python3/10-linux-modules.patch | 11 - .../target/python3/10-osx-modules.patch | 11 - tools/depends/target/python3/Makefile | 78 +++- tools/depends/target/python3/PYTHON3-VERSION | 4 +- tools/depends/target/python3/apple.patch | 9 - .../depends/target/python3/crosscompile.patch | 64 --- .../target/python3/darwin_embedded.patch | 11 - tools/depends/target/python3/modules.setup | 304 --------------- .../PYTHONMODULE-SETUPTOOLS-VERSION | 4 +- .../samba-gplv3/08-py312-distutils.patch | 11 + tools/depends/target/samba-gplv3/Makefile | 2 + 26 files changed, 630 insertions(+), 474 deletions(-) create mode 100644 tools/depends/native/Mako/MAKO-VERSION create mode 100644 tools/depends/native/MarkupSafe/01-all-GH399-removedistutils.patch create mode 100644 tools/depends/native/MarkupSafe/MARKUPSAFE-VERSION delete mode 100644 tools/depends/native/python3/01-distutil-flags.patch create mode 100644 tools/depends/target/mesa/01-all-py312-setuptools.patch create mode 100644 tools/depends/target/python3/01-py312-cpython118618-1.patch create mode 100644 tools/depends/target/python3/01-py312-cpython118618-2.patch create mode 100644 tools/depends/target/python3/02-android-cpython114875.patch delete mode 100644 tools/depends/target/python3/10-linux-modules.patch delete mode 100644 tools/depends/target/python3/10-osx-modules.patch delete mode 100644 tools/depends/target/python3/crosscompile.patch delete mode 100644 tools/depends/target/python3/modules.setup create mode 100644 tools/depends/target/samba-gplv3/08-py312-distutils.patch diff --git a/cmake/modules/FindPython.cmake b/cmake/modules/FindPython.cmake index 3fc61da50c290..7874b281873d7 100644 --- a/cmake/modules/FindPython.cmake +++ b/cmake/modules/FindPython.cmake @@ -30,7 +30,7 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) if(KODI_DEPENDSBUILD) # Force set to tools/depends python version - set(PYTHON_VER 3.11) + set(PYTHON_VER 3.12) endif() endif() diff --git a/tools/depends/Makefile.include.in b/tools/depends/Makefile.include.in index 1903fecd8564d..8a42fea4bc0d2 100644 --- a/tools/depends/Makefile.include.in +++ b/tools/depends/Makefile.include.in @@ -108,7 +108,7 @@ VERSION.TXT := $(CMAKE_SOURCE_DIR)/version.txt APP_NAME=$(shell awk '/APP_NAME/ {print tolower($$2)}' $(VERSION.TXT)) # Python related vars -PYTHON_VERSION=3.11 +PYTHON_VERSION=3.12 PYTHON_SITE_PKG=@prefix@/@deps_dir@/lib/python${PYTHON_VERSION}/site-packages ifeq ($(CPU), arm64) diff --git a/tools/depends/native/Mako/MAKO-VERSION b/tools/depends/native/Mako/MAKO-VERSION new file mode 100644 index 0000000000000..d863d172c76b4 --- /dev/null +++ b/tools/depends/native/Mako/MAKO-VERSION @@ -0,0 +1,4 @@ +LIBNAME=Mako +VERSION=1.3.5 +ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz +SHA512=9a2f96bcb650f40cc2a9daa05904e54efca1fa30022ab641c850f6e32b84a38368d4c5d328f94ac4495ed97778d6ab0b661bc93a14740ed7e5d518f03bc9a59f diff --git a/tools/depends/native/Mako/Makefile b/tools/depends/native/Mako/Makefile index c31e462db4bc2..559c88b0f28a2 100644 --- a/tools/depends/native/Mako/Makefile +++ b/tools/depends/native/Mako/Makefile @@ -1,14 +1,8 @@ -include ../../Makefile.include +include ../../Makefile.include MAKO-VERSION ../../download-files.include PREFIX=$(NATIVEPREFIX) PLATFORM=$(NATIVEPLATFORM) -DEPS = ../../Makefile.include Makefile ../../download-files.include +DEPS = ../../Makefile.include Makefile MAKO-VERSION ../../download-files.include -# lib name, version -LIBNAME=Mako -VERSION=1.1.3 -ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz -SHA512=a9b94fa34a61e7794b6e4549fa0bada6ff84dfb0d9edb8d5c7f9b95d12184fa4499f42303cfee720b576a9f7e986a57d91ad3aeb26c9f93154dbc08fb2975952 -include ../../download-files.include all: .installed-$(PLATFORM) diff --git a/tools/depends/native/MarkupSafe/01-all-GH399-removedistutils.patch b/tools/depends/native/MarkupSafe/01-all-GH399-removedistutils.patch new file mode 100644 index 0000000000000..029157b33f322 --- /dev/null +++ b/tools/depends/native/MarkupSafe/01-all-GH399-removedistutils.patch @@ -0,0 +1,48 @@ +From 1f6697fcb27824fefd02b5c0890a319cf17fa3de Mon Sep 17 00:00:00 2001 +From: David Lord +Date: Thu, 7 Sep 2023 10:17:17 -0700 +Subject: [PATCH] import from setuptools instead of distutils + +--- + setup.py | 14 +++++++------- + 1 files changed, 7 insertions(+), 7 deletions(-) + +diff --git a/setup.py b/setup.py +index 7208cdd7..d19a4faa 100644 +--- a/setup.py ++++ b/setup.py +@@ -2,12 +2,12 @@ + import platform + import sys + +-from distutils.errors import CCompilerError +-from distutils.errors import DistutilsExecError +-from distutils.errors import DistutilsPlatformError + from setuptools import Extension + from setuptools import setup + from setuptools.command.build_ext import build_ext ++from setuptools.errors import CCompilerError ++from setuptools.errors import ExecError ++from setuptools.errors import PlatformError + + ext_modules = [Extension("markupsafe._speedups", ["src/markupsafe/_speedups.c"])] + +@@ -21,14 +21,14 @@ class ve_build_ext(build_ext): + + def run(self): + try: +- build_ext.run(self) +- except DistutilsPlatformError as e: ++ super().run() ++ except PlatformError as e: + raise BuildFailed() from e + + def build_extension(self, ext): + try: +- build_ext.build_extension(self, ext) +- except (CCompilerError, DistutilsExecError, DistutilsPlatformError) as e: ++ super().build_extension(ext) ++ except (CCompilerError, ExecError, PlatformError) as e: + raise BuildFailed() from e + except ValueError as e: + # this can happen on Windows 64 bit, see Python issue 7511 diff --git a/tools/depends/native/MarkupSafe/MARKUPSAFE-VERSION b/tools/depends/native/MarkupSafe/MARKUPSAFE-VERSION new file mode 100644 index 0000000000000..d7b442fa7e421 --- /dev/null +++ b/tools/depends/native/MarkupSafe/MARKUPSAFE-VERSION @@ -0,0 +1,4 @@ +LIBNAME=MarkupSafe +VERSION=2.1.5 +ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz +SHA512=3ba5af43d23c266377f5d32b11e1faa7955ea8c67eb1c32886c308527f93e75e387294d0eec7794c0c20aad0c705b27f3d1f86b04202f3b63068d12d4053cc71 diff --git a/tools/depends/native/MarkupSafe/Makefile b/tools/depends/native/MarkupSafe/Makefile index fa4372735ebe8..358e5ccb3927e 100644 --- a/tools/depends/native/MarkupSafe/Makefile +++ b/tools/depends/native/MarkupSafe/Makefile @@ -1,14 +1,9 @@ -include ../../Makefile.include +include ../../Makefile.include MARKUPSAFE-VERSION ../../download-files.include PREFIX=$(NATIVEPREFIX) PLATFORM=$(NATIVEPLATFORM) -DEPS = ../../Makefile.include Makefile ../../download-files.include +DEPS = ../../Makefile.include Makefile MARKUPSAFE-VERSION ../../download-files.include \ + 01-all-GH399-removedistutils.patch -# lib name, version -LIBNAME=MarkupSafe -VERSION=1.1.1 -ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz -SHA512=f3014e6131a3ab866914c5635b5397ef71906bffb1b6f8c5f2ed2acf167429ff7914236d38943e872683a57a9be9669f4c5aace6274f3307ab21ef25373db0b6 -include ../../download-files.include all: .installed-$(PLATFORM) @@ -17,6 +12,7 @@ $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) .installed-$(PLATFORM): $(PLATFORM) + cd $(PLATFORM); patch -p1 -i ../01-all-GH399-removedistutils.patch cd $(PLATFORM); $(PREFIX)/bin/python3 setup.py install --prefix=$(PREFIX) touch $@ diff --git a/tools/depends/native/python3/01-distutil-flags.patch b/tools/depends/native/python3/01-distutil-flags.patch deleted file mode 100644 index 28cd2f98baa7c..0000000000000 --- a/tools/depends/native/python3/01-distutil-flags.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- a/Lib/distutils/sysconfig.py -+++ b/Lib/distutils/sysconfig.py -@@ -214,6 +214,9 @@ - (cc, cxx, cflags, ccshared, ldshared, shlib_suffix, ar, ar_flags) = \ - get_config_vars('CC', 'CXX', 'CFLAGS', - 'CCSHARED', 'LDSHARED', 'SHLIB_SUFFIX', 'AR', 'ARFLAGS') -+ # get_config_vars returns host vars. clear cflags, ldshared for crosscompile use -+ cflags = "" -+ ldshared = cc + " -shared" - - if 'CC' in os.environ: - newcc = os.environ['CC'] diff --git a/tools/depends/native/python3/Makefile b/tools/depends/native/python3/Makefile index 74ebe061a0040..bb93cc87e7940 100644 --- a/tools/depends/native/python3/Makefile +++ b/tools/depends/native/python3/Makefile @@ -1,7 +1,6 @@ include ../../Makefile.include PYTHON3-VERSION ../../download-files.include PLATFORM=$(NATIVEPLATFORM) -DEPS = ../../Makefile.include Makefile PYTHON3-VERSION ../../download-files.include \ - 01-distutil-flags.patch +DEPS = ../../Makefile.include Makefile PYTHON3-VERSION ../../download-files.include CONFIGURE=./configure --prefix=$(NATIVEPREFIX) \ --disable-shared \ @@ -32,11 +31,7 @@ $(LIBDYLIB): $(PLATFORM) cd $(PLATFORM); $(MAKE) .installed-$(PLATFORM): $(LIBDYLIB) - cd $(PLATFORM); patch -p1 -i ../01-distutil-flags.patch cd $(PLATFORM); $(MAKE) install -# Sed patch setuptools that is installed via ensurepip as we cant patch the source - cd $(NATIVE_SITEPACKAGES); sed -ie "s|cflags = cflags + ' ' + os.environ\['CFLAGS'\]|cflags = os.environ\['CFLAGS'\]|" setuptools/_distutils/sysconfig.py - touch $(LIBDYLIB) touch $@ clean: diff --git a/tools/depends/native/python3/PYTHON3-VERSION b/tools/depends/native/python3/PYTHON3-VERSION index c5e1760fb27a9..9c97bd4018c1f 100644 --- a/tools/depends/native/python3/PYTHON3-VERSION +++ b/tools/depends/native/python3/PYTHON3-VERSION @@ -1,4 +1,4 @@ LIBNAME=Python -VERSION=3.11.7 +VERSION=3.12.5 ARCHIVE=$(LIBNAME)-$(VERSION).tar.xz -SHA512=11e06f2ffe1f66888cb5b4e9f607de815294d6863a77eda6ec6d7c724ef158df9f51881f4a956d4a6fa973c2fb6fd031d495e3496e9b0bb53793fb1cc8434c63 +SHA512=7a1c30d798434fe24697bc253f6010d75145e7650f66803328425c8525331b9fa6b63d12a652687582db205f8d4c8279c8f73c338168592481517b063351c921 diff --git a/tools/depends/target/mesa/01-all-py312-setuptools.patch b/tools/depends/target/mesa/01-all-py312-setuptools.patch new file mode 100644 index 0000000000000..fda20612072f7 --- /dev/null +++ b/tools/depends/target/mesa/01-all-py312-setuptools.patch @@ -0,0 +1,10 @@ +--- a/meson.build ++++ b/meson.build +@@ -1022,6 +1022,7 @@ + has_mako = run_command( + prog_python, '-c', + ''' ++import setuptools + from distutils.version import StrictVersion + import mako + assert StrictVersion(mako.__version__) > StrictVersion("0.8.0") diff --git a/tools/depends/target/mesa/Makefile b/tools/depends/target/mesa/Makefile index f24b673ccf101..9312d35512a87 100644 --- a/tools/depends/target/mesa/Makefile +++ b/tools/depends/target/mesa/Makefile @@ -1,5 +1,6 @@ include ../../Makefile.include -DEPS =../../Makefile.include Makefile ../../download-files.include +DEPS =../../Makefile.include Makefile ../../download-files.include \ + 01-all-py312-setuptools.patch LIBNAME=mesa VERSION=23.0.1 @@ -76,6 +77,7 @@ $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM) cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) cd $(PLATFORM); rm -rf build; mkdir -p build + cd $(PLATFORM); patch -p1 -i ../01-all-py312-setuptools.patch cd $(PLATFORM); $(CONFIGURE) . build $(LIBDYLIB): $(PLATFORM) diff --git a/tools/depends/target/python3/01-py312-cpython118618-1.patch b/tools/depends/target/python3/01-py312-cpython118618-1.patch new file mode 100644 index 0000000000000..5055a39779880 --- /dev/null +++ b/tools/depends/target/python3/01-py312-cpython118618-1.patch @@ -0,0 +1,369 @@ +From 21f8fbaa7c01a8ec2fa2420f44f5cb05a54f55b6 Mon Sep 17 00:00:00 2001 +From: Neil Schemenauer +Date: Wed, 27 Mar 2024 09:54:02 -0700 +Subject: [PATCH] Use pointer for interp->obmalloc state. + +For interpreters that share state with the main interpreter, this points +to the same static memory structure. For interpreters with their own +obmalloc state, it is heap allocated. Add free_obmalloc_arenas() which +will free the obmalloc arenas and radix tree structures for interpreters +with their own obmalloc state. +--- + Include/internal/pycore_interp.h | 12 +- + Include/internal/pycore_obmalloc.h | 2 + + Include/internal/pycore_obmalloc_init.h | 7 - + Include/internal/pycore_runtime_init.h | 1 - + ...-12-22-13-21-39.gh-issue-113055.47xBMF.rst | 5 + + Objects/obmalloc.c | 121 +++++++++++++++++- + Python/pylifecycle.c | 16 +++ + Python/pystate.c | 13 +- + Tools/c-analyzer/cpython/ignored.tsv | 3 +- + 9 files changed, 157 insertions(+), 23 deletions(-) + create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst + +diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h +index 37cc88ed081b72..a0ef5990259e29 100644 +--- a/Include/internal/pycore_interp.h ++++ b/Include/internal/pycore_interp.h +@@ -178,7 +178,17 @@ struct _is { + struct _warnings_runtime_state warnings; + struct atexit_state atexit; + +- struct _obmalloc_state obmalloc; ++ // Per-interpreter state for the obmalloc allocator. For the main ++ // interpreter and for all interpreters that don't have their ++ // own obmalloc state, this points to the static structure in ++ // obmalloc.c obmalloc_state_main. For other interpreters, it is ++ // heap allocated by _PyMem_init_obmalloc() and freed when the ++ // interpreter structure is freed. In the case of a heap allocated ++ // obmalloc state, it is not safe to hold on to or use memory after ++ // the interpreter is freed. The obmalloc state corresponding to ++ // that allocated memory is gone. See free_obmalloc_arenas() for ++ // more comments. ++ struct _obmalloc_state *obmalloc; + + PyObject *audit_hooks; + PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS]; +diff --git a/Include/internal/pycore_obmalloc.h b/Include/internal/pycore_obmalloc.h +index b1c00654ac1c5d..38427e194956ac 100644 +--- a/Include/internal/pycore_obmalloc.h ++++ b/Include/internal/pycore_obmalloc.h +@@ -686,6 +686,8 @@ extern Py_ssize_t _Py_GetGlobalAllocatedBlocks(void); + _Py_GetGlobalAllocatedBlocks() + extern Py_ssize_t _PyInterpreterState_GetAllocatedBlocks(PyInterpreterState *); + extern void _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *); ++extern int _PyMem_init_obmalloc(PyInterpreterState *interp); ++extern bool _PyMem_obmalloc_state_on_heap(PyInterpreterState *interp); + + + #ifdef WITH_PYMALLOC +diff --git a/Include/internal/pycore_obmalloc_init.h b/Include/internal/pycore_obmalloc_init.h +index 8ee72ff2d4126f..e6811b7aeca73c 100644 +--- a/Include/internal/pycore_obmalloc_init.h ++++ b/Include/internal/pycore_obmalloc_init.h +@@ -59,13 +59,6 @@ extern "C" { + .dump_debug_stats = -1, \ + } + +-#define _obmalloc_state_INIT(obmalloc) \ +- { \ +- .pools = { \ +- .used = _obmalloc_pools_INIT(obmalloc.pools), \ +- }, \ +- } +- + + #ifdef __cplusplus + } +diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h +index e5f9e17efff24b..d3a64b3d4a7895 100644 +--- a/Include/internal/pycore_runtime_init.h ++++ b/Include/internal/pycore_runtime_init.h +@@ -88,7 +88,6 @@ extern PyTypeObject _PyExc_MemoryError; + { \ + .id_refcount = -1, \ + .imports = IMPORTS_INIT, \ +- .obmalloc = _obmalloc_state_INIT(INTERP.obmalloc), \ + .ceval = { \ + .recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \ + }, \ +diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst +new file mode 100644 +index 00000000000000..90f49272218c96 +--- /dev/null ++++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst +@@ -0,0 +1,5 @@ ++Make interp->obmalloc a pointer. For interpreters that share state with the ++main interpreter, this points to the same static memory structure. For ++interpreters with their own obmalloc state, it is heap allocated. Add ++free_obmalloc_arenas() which will free the obmalloc arenas and radix tree ++structures for interpreters with their own obmalloc state. +diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c +index 9620a8fbb44cac..acbefef614195c 100644 +--- a/Objects/obmalloc.c ++++ b/Objects/obmalloc.c +@@ -3,6 +3,7 @@ + #include "Python.h" + #include "pycore_code.h" // stats + #include "pycore_pystate.h" // _PyInterpreterState_GET ++#include "pycore_obmalloc_init.h" + + #include "pycore_obmalloc.h" + #include "pycore_pymem.h" +@@ -852,6 +853,13 @@ static int running_on_valgrind = -1; + + typedef struct _obmalloc_state OMState; + ++/* obmalloc state for main interpreter and shared by all interpreters without ++ * their own obmalloc state. By not explicitly initalizing this structure, it ++ * will be allocated in the BSS which is a small performance win. The radix ++ * tree arrays are fairly large but are sparsely used. */ ++static struct _obmalloc_state obmalloc_state_main; ++static bool obmalloc_state_initialized; ++ + static inline int + has_own_state(PyInterpreterState *interp) + { +@@ -864,10 +872,8 @@ static inline OMState * + get_state(void) + { + PyInterpreterState *interp = _PyInterpreterState_GET(); +- if (!has_own_state(interp)) { +- interp = _PyInterpreterState_Main(); +- } +- return &interp->obmalloc; ++ assert(interp->obmalloc != NULL); // otherwise not initialized or freed ++ return interp->obmalloc; + } + + // These macros all rely on a local "state" variable. +@@ -893,7 +899,11 @@ _PyInterpreterState_GetAllocatedBlocks(PyInterpreterState *interp) + "the interpreter doesn't have its own allocator"); + } + #endif +- OMState *state = &interp->obmalloc; ++ OMState *state = interp->obmalloc; ++ ++ if (state == NULL) { ++ return 0; ++ } + + Py_ssize_t n = raw_allocated_blocks; + /* add up allocated blocks for used pools */ +@@ -915,13 +925,25 @@ _PyInterpreterState_GetAllocatedBlocks(PyInterpreterState *interp) + return n; + } + ++static void free_obmalloc_arenas(PyInterpreterState *interp); ++ + void + _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *interp) + { +- if (has_own_state(interp)) { ++ if (has_own_state(interp) && interp->obmalloc != NULL) { + Py_ssize_t leaked = _PyInterpreterState_GetAllocatedBlocks(interp); + assert(has_own_state(interp) || leaked == 0); + interp->runtime->obmalloc.interpreter_leaks += leaked; ++ if (_PyMem_obmalloc_state_on_heap(interp) && leaked == 0) { ++ // free the obmalloc arenas and radix tree nodes. If leaked > 0 ++ // then some of the memory allocated by obmalloc has not been ++ // freed. It might be safe to free the arenas in that case but ++ // it's possible that extension modules are still using that ++ // memory. So, it is safer to not free and to leak. Perhaps there ++ // should be warning when this happens. It should be possible to ++ // use a tool like "-fsanitize=address" to track down these leaks. ++ free_obmalloc_arenas(interp); ++ } + } + } + +@@ -2511,9 +2533,96 @@ _PyDebugAllocatorStats(FILE *out, + (void)printone(out, buf2, num_blocks * sizeof_block); + } + ++// Return true if the obmalloc state structure is heap allocated, ++// by PyMem_RawCalloc(). For the main interpreter, this structure ++// allocated in the BSS. Allocating that way gives some memory savings ++// and a small performance win (at least on a demand paged OS). On ++// 64-bit platforms, the obmalloc structure is 256 kB. Most of that ++// memory is for the arena_map_top array. Since normally only one entry ++// of that array is used, only one page of resident memory is actually ++// used, rather than the full 256 kB. ++bool _PyMem_obmalloc_state_on_heap(PyInterpreterState *interp) ++{ ++#if WITH_PYMALLOC ++ return interp->obmalloc && interp->obmalloc != &obmalloc_state_main; ++#else ++ return false; ++#endif ++} ++ ++#ifdef WITH_PYMALLOC ++static void ++init_obmalloc_pools(PyInterpreterState *interp) ++{ ++ // initialize the obmalloc->pools structure. This must be done ++ // before the obmalloc alloc/free functions can be called. ++ poolp temp[OBMALLOC_USED_POOLS_SIZE] = ++ _obmalloc_pools_INIT(interp->obmalloc->pools); ++ memcpy(&interp->obmalloc->pools.used, temp, sizeof(temp)); ++} ++#endif /* WITH_PYMALLOC */ ++ ++int _PyMem_init_obmalloc(PyInterpreterState *interp) ++{ ++#ifdef WITH_PYMALLOC ++ /* Initialize obmalloc, but only for subinterpreters, ++ since the main interpreter is initialized statically. */ ++ if (_Py_IsMainInterpreter(interp) ++ || _PyInterpreterState_HasFeature(interp, ++ Py_RTFLAGS_USE_MAIN_OBMALLOC)) { ++ interp->obmalloc = &obmalloc_state_main; ++ if (!obmalloc_state_initialized) { ++ init_obmalloc_pools(interp); ++ obmalloc_state_initialized = true; ++ } ++ } else { ++ interp->obmalloc = PyMem_RawCalloc(1, sizeof(struct _obmalloc_state)); ++ if (interp->obmalloc == NULL) { ++ return -1; ++ } ++ init_obmalloc_pools(interp); ++ } ++#endif /* WITH_PYMALLOC */ ++ return 0; // success ++} ++ + + #ifdef WITH_PYMALLOC + ++static void ++free_obmalloc_arenas(PyInterpreterState *interp) ++{ ++ OMState *state = interp->obmalloc; ++ for (uint i = 0; i < maxarenas; ++i) { ++ // free each obmalloc memory arena ++ struct arena_object *ao = &allarenas[i]; ++ _PyObject_Arena.free(_PyObject_Arena.ctx, ++ (void *)ao->address, ARENA_SIZE); ++ } ++ // free the array containing pointers to all arenas ++ PyMem_RawFree(allarenas); ++#if WITH_PYMALLOC_RADIX_TREE ++#ifdef USE_INTERIOR_NODES ++ // Free the middle and bottom nodes of the radix tree. These are allocated ++ // by arena_map_mark_used() but not freed when arenas are freed. ++ for (int i1 = 0; i1 < MAP_TOP_LENGTH; i1++) { ++ arena_map_mid_t *mid = arena_map_root.ptrs[i1]; ++ if (mid == NULL) { ++ continue; ++ } ++ for (int i2 = 0; i2 < MAP_MID_LENGTH; i2++) { ++ arena_map_bot_t *bot = arena_map_root.ptrs[i1]->ptrs[i2]; ++ if (bot == NULL) { ++ continue; ++ } ++ PyMem_RawFree(bot); ++ } ++ PyMem_RawFree(mid); ++ } ++#endif ++#endif ++} ++ + #ifdef Py_DEBUG + /* Is target in the list? The list is traversed via the nextpool pointers. + * The list may be NULL-terminated, or circular. Return 1 if target is in +diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c +index a0130fde15d574..fb833ba61cbd9b 100644 +--- a/Python/pylifecycle.c ++++ b/Python/pylifecycle.c +@@ -28,6 +28,7 @@ + #include "pycore_typeobject.h" // _PyTypes_InitTypes() + #include "pycore_typevarobject.h" // _Py_clear_generic_types() + #include "pycore_unicodeobject.h" // _PyUnicode_InitTypes() ++#include "pycore_obmalloc.h" // _PyMem_init_obmalloc() + #include "opcode.h" + + #include // setlocale() +@@ -636,6 +637,13 @@ pycore_create_interpreter(_PyRuntimeState *runtime, + return status; + } + ++ // initialize the interp->obmalloc state. This must be done after ++ // the settings are loaded (so that feature_flags are set) but before ++ // any calls are made to obmalloc functions. ++ if (_PyMem_init_obmalloc(interp) < 0) { ++ return _PyStatus_NO_MEMORY(); ++ } ++ + /* Auto-thread-state API */ + status = _PyGILState_Init(interp); + if (_PyStatus_EXCEPTION(status)) { +@@ -2051,6 +2059,14 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) + return _PyStatus_OK(); + } + ++ // initialize the interp->obmalloc state. This must be done after ++ // the settings are loaded (so that feature_flags are set) but before ++ // any calls are made to obmalloc functions. ++ if (_PyMem_init_obmalloc(interp) < 0) { ++ status = _PyStatus_NO_MEMORY(); ++ goto error; ++ } ++ + PyThreadState *tstate = _PyThreadState_New(interp); + if (tstate == NULL) { + PyInterpreterState_Delete(interp); +diff --git a/Python/pystate.c b/Python/pystate.c +index 1337516aa59cbc..a25c3dcf9d09ea 100644 +--- a/Python/pystate.c ++++ b/Python/pystate.c +@@ -14,6 +14,7 @@ + #include "pycore_pystate.h" + #include "pycore_runtime_init.h" // _PyRuntimeState_INIT + #include "pycore_sysmodule.h" ++#include "pycore_obmalloc.h" // _PyMem_obmalloc_state_on_heap() + + /* -------------------------------------------------------------------------- + CAUTION +@@ -636,6 +637,11 @@ free_interpreter(PyInterpreterState *interp) + // The main interpreter is statically allocated so + // should not be freed. + if (interp != &_PyRuntime._main_interpreter) { ++ if (_PyMem_obmalloc_state_on_heap(interp)) { ++ // interpreter has its own obmalloc state, free it ++ PyMem_RawFree(interp->obmalloc); ++ interp->obmalloc = NULL; ++ } + PyMem_RawFree(interp); + } + } +@@ -679,13 +685,6 @@ init_interpreter(PyInterpreterState *interp, + assert(next != NULL || (interp == runtime->interpreters.main)); + interp->next = next; + +- /* Initialize obmalloc, but only for subinterpreters, +- since the main interpreter is initialized statically. */ +- if (interp != &runtime->_main_interpreter) { +- poolp temp[OBMALLOC_USED_POOLS_SIZE] = \ +- _obmalloc_pools_INIT(interp->obmalloc.pools); +- memcpy(&interp->obmalloc.pools.used, temp, sizeof(temp)); +- } + _PyObject_InitState(interp); + + _PyEval_InitState(interp, pending_lock); +diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv +index 9f36c47ca7ea03..7bcca27ecc32f6 100644 +--- a/Tools/c-analyzer/cpython/ignored.tsv ++++ b/Tools/c-analyzer/cpython/ignored.tsv +@@ -318,7 +318,8 @@ Objects/obmalloc.c - _PyMem_Debug - + Objects/obmalloc.c - _PyMem_Raw - + Objects/obmalloc.c - _PyObject - + Objects/obmalloc.c - last_final_leaks - +-Objects/obmalloc.c - usedpools - ++Objects/obmalloc.c - obmalloc_state_main - ++Objects/obmalloc.c - obmalloc_state_initialized - + Objects/typeobject.c - name_op - + Objects/typeobject.c - slotdefs - + Objects/unicodeobject.c - stripfuncnames - diff --git a/tools/depends/target/python3/01-py312-cpython118618-2.patch b/tools/depends/target/python3/01-py312-cpython118618-2.patch new file mode 100644 index 0000000000000..bbd7a5e0d19cd --- /dev/null +++ b/tools/depends/target/python3/01-py312-cpython118618-2.patch @@ -0,0 +1,71 @@ +From a867732a619e1cc02369cf0185b53a484d049369 Mon Sep 17 00:00:00 2001 +From: Neil Schemenauer +Date: Mon, 6 May 2024 10:02:17 -0700 +Subject: [PATCH] Fix merge, move _PyMem_init_obmalloc() calls. + +--- + Python/pylifecycle.c | 30 +++++++++++++++--------------- + 1 file changed, 15 insertions(+), 15 deletions(-) + +diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c +index fb833ba61cbd9b..31a24d4a65aebf 100644 +--- a/Python/pylifecycle.c ++++ b/Python/pylifecycle.c +@@ -637,13 +637,6 @@ pycore_create_interpreter(_PyRuntimeState *runtime, + return status; + } + +- // initialize the interp->obmalloc state. This must be done after +- // the settings are loaded (so that feature_flags are set) but before +- // any calls are made to obmalloc functions. +- if (_PyMem_init_obmalloc(interp) < 0) { +- return _PyStatus_NO_MEMORY(); +- } +- + /* Auto-thread-state API */ + status = _PyGILState_Init(interp); + if (_PyStatus_EXCEPTION(status)) { +@@ -658,6 +651,13 @@ pycore_create_interpreter(_PyRuntimeState *runtime, + return status; + } + ++ // initialize the interp->obmalloc state. This must be done after ++ // the settings are loaded (so that feature_flags are set) but before ++ // any calls are made to obmalloc functions. ++ if (_PyMem_init_obmalloc(interp) < 0) { ++ return _PyStatus_NO_MEMORY(); ++ } ++ + PyThreadState *tstate = _PyThreadState_New(interp); + if (tstate == NULL) { + return _PyStatus_ERR("can't make first thread"); +@@ -2059,14 +2059,6 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) + return _PyStatus_OK(); + } + +- // initialize the interp->obmalloc state. This must be done after +- // the settings are loaded (so that feature_flags are set) but before +- // any calls are made to obmalloc functions. +- if (_PyMem_init_obmalloc(interp) < 0) { +- status = _PyStatus_NO_MEMORY(); +- goto error; +- } +- + PyThreadState *tstate = _PyThreadState_New(interp); + if (tstate == NULL) { + PyInterpreterState_Delete(interp); +@@ -2110,6 +2102,14 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) + goto error; + } + ++ // initialize the interp->obmalloc state. This must be done after ++ // the settings are loaded (so that feature_flags are set) but before ++ // any calls are made to obmalloc functions. ++ if (_PyMem_init_obmalloc(interp) < 0) { ++ status = _PyStatus_NO_MEMORY(); ++ goto error; ++ } ++ + status = init_interp_create_gil(tstate, config->gil); + if (_PyStatus_EXCEPTION(status)) { + goto error; diff --git a/tools/depends/target/python3/02-android-cpython114875.patch b/tools/depends/target/python3/02-android-cpython114875.patch new file mode 100644 index 0000000000000..edf2335a8f09f --- /dev/null +++ b/tools/depends/target/python3/02-android-cpython114875.patch @@ -0,0 +1,36 @@ +--- a/configure.ac ++++ b/configure.ac +@@ -4906,7 +4906,7 @@ + copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ + faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ + fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ +- gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \ ++ gai_strerror getegid getentropy geteuid getgid getgrent getgrgid getgrgid_r \ + getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \ + getpeername getpgid getpid getppid getpriority _getpty \ + getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \ +@@ -7445,7 +7445,9 @@ + -a "$ac_cv_header_netinet_in_h" = "yes"])) + + dnl platform specific extensions +-PY_STDLIB_MOD([grp], [], [test "$ac_cv_func_getgrgid" = yes -o "$ac_cv_func_getgrgid_r" = yes]) ++PY_STDLIB_MOD([grp], [], ++ [test "$ac_cv_func_getgrent" = "yes" && ++ { test "$ac_cv_func_getgrgid" = "yes" || test "$ac_cv_func_getgrgid_r" = "yes"; }]) + PY_STDLIB_MOD([ossaudiodev], + [], [test "$ac_cv_header_linux_soundcard_h" = yes -o "$ac_cv_header_sys_soundcard_h" = yes], + [], [$OSSAUDIODEV_LIBS]) +diff --git a/pyconfig.h.in b/pyconfig.h.in +index d8a9f68951afbd..36a46b1d14909f 100644 +--- a/pyconfig.h.in ++++ b/pyconfig.h.in +@@ -477,6 +477,9 @@ + /* Define to 1 if you have the `getgid' function. */ + #undef HAVE_GETGID + ++/* Define to 1 if you have the `getgrent' function. */ ++#undef HAVE_GETGRENT ++ + /* Define to 1 if you have the `getgrgid' function. */ + #undef HAVE_GETGRGID + diff --git a/tools/depends/target/python3/10-linux-modules.patch b/tools/depends/target/python3/10-linux-modules.patch deleted file mode 100644 index 97753d9aa7503..0000000000000 --- a/tools/depends/target/python3/10-linux-modules.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/Modules/Setup -+++ b/Modules/Setup -@@ -182,7 +182,7 @@ - # Modules with some UNIX dependencies - - _posixsubprocess _posixsubprocess.c --_posixshmem -I$(srcdir)/Modules/_multiprocessing _multiprocessing/posixshmem.c # -lrt # _posixshmem -+_posixshmem -I$(srcdir)/Modules/_multiprocessing _multiprocessing/posixshmem.c -lrt # _posixshmem - fcntl fcntlmodule.c - #grp grpmodule.c - #ossaudiodev ossaudiodev.c diff --git a/tools/depends/target/python3/10-osx-modules.patch b/tools/depends/target/python3/10-osx-modules.patch deleted file mode 100644 index 8050f5ae5d664..0000000000000 --- a/tools/depends/target/python3/10-osx-modules.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/Modules/Setup -+++ b/Modules/Setup -@@ -270,7 +270,7 @@ - - # macOS specific module, needs SystemConfiguration and CoreFoundation framework - # _scproxy _scproxy.c --$(OSX_SCPROXY) -+_scproxy _scproxy.c -framework SystemConfiguration -framework CoreFoundation - - # Examples - diff --git a/tools/depends/target/python3/Makefile b/tools/depends/target/python3/Makefile index 53942259aabc7..c8682f6029a88 100644 --- a/tools/depends/target/python3/Makefile +++ b/tools/depends/target/python3/Makefile @@ -1,16 +1,14 @@ include ../../Makefile.include PYTHON3-VERSION ../../download-files.include DEPS = ../../Makefile.include Makefile PYTHON3-VERSION ../../download-files.include \ + 01-py312-cpython118618-1.patch \ + 01-py312-cpython118618-2.patch \ + 02-android-cpython114875.patch \ apple.patch \ - crosscompile.patch \ darwin_embedded.patch \ - 10-android-modules.patch \ - 10-linux-modules.patch \ - 10-osx-modules.patch \ - modules.setup + 10-android-modules.patch ifeq ($(findstring apple-darwin, $(HOST)), apple-darwin) HOSTPLATFORM=_PYTHON_HOST_PLATFORM="darwin" - LINK_ICONV=-liconv ifeq ($(OS), darwin_embedded) EXTRA_CONFIGURE=ac_cv_func_wait3=no ac_cv_func_wait4=no ac_cv_func_waitpid=no \ ac_cv_func_execv=no ac_cv_func_fexecv=no ac_cv_func_getentropy=no \ @@ -19,23 +17,64 @@ ifeq ($(findstring apple-darwin, $(HOST)), apple-darwin) ac_cv_func_forkpty=no ac_cv_lib_util_forkpty=no \ ac_cv_func_getgroups=no \ ac_cv_func_system=no + export SDKROOT + endif + ifeq ($(OS), osx) + export SDKROOT endif # required for _localemodule EXTRA_CONFIGURE+= ac_cv_lib_intl_textdomain=yes - # uses SDK ffi - EXTRA_CONFIGURE+= --with-system-ffi endif ifeq ($(OS),android) - LDFLAGS+= -liconv + LIBS=-liconv endif ifeq ($(OS), linux) EXTRA_CONFIGURE=ac_cv_pthread=yes ifeq ($(TARGET_PLATFORM),webos) - LDFLAGS+= -liconv + # Force intl check to succeed + EXTRA_CONFIGURE+=ac_cv_lib_intl_textdomain=yes + # Export iconv as LIBS for link ordering (After -lintl from configure search) + LIBS=-liconv endif +endif + +# Disabled c extension modules for all platforms +PY_MODULES = py_cv_module_audioop=n/a \ + py_cv_module_grp=n/a \ + py_cv_module_ossaudiodev=n/a \ + py_cv_module_spwd=n/a \ + py_cv_module_syslog=n/a \ + py_cv_module__crypt=n/a \ + py_cv_module_nis=n/a \ + py_cv_module__dbm=n/a \ + py_cv_module__gdbm=n/a \ + py_cv_module__uuid=n/a \ + py_cv_module_readline=n/a \ + py_cv_module__curses=n/a \ + py_cv_module__curses_panel=n/a \ + py_cv_module__scproxy=n/a \ + py_cv_module_xx=n/a \ + py_cv_module_xxlimited=n/a \ + py_cv_module_xxlimited_35=n/a \ + py_cv_module_xxsubtype=n/a \ + py_cv_module__xxsubinterpreters=n/a \ + py_cv_module__tkinter=n/a \ + py_cv_module__curses=n/a \ + py_cv_module__codecs_jp=n/a \ + py_cv_module__codecs_kr=n/a \ + py_cv_module__codecs_tw=n/a + +# These modules use "internal" libs for building. The required static archives +# are not installed outside of the cpython build tree, and cause failure in kodi linking +# If we wish to support them in the future, we should create "system libs" for them +PY_MODULES+= py_cv_module__decimal=n/a \ + py_cv_module__sha2=n/a + +ifeq ($(OS), darwin_embedded) + PY_MODULES+= py_cv_module__posixsubprocess=n/a endif # configuration settings @@ -49,9 +88,11 @@ CONFIGURE=./configure --prefix=$(PREFIX) \ --with-system-expat=yes \ --disable-test-modules \ MODULE_BUILDTYPE=static \ + $(PY_MODULES) \ $(EXTRA_CONFIGURE) export LDFLAGS +export LIBS LIBDYLIB=$(PLATFORM)/libpython$(PYTHON_VERSION).a @@ -60,32 +101,27 @@ all: .installed-$(PLATFORM) $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM) cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) - cd $(PLATFORM); patch -p1 -i ../crosscompile.patch + cd $(PLATFORM); patch -p1 -i ../01-py312-cpython118618-1.patch + cd $(PLATFORM); patch -p1 -i ../01-py312-cpython118618-2.patch cd $(PLATFORM); patch -p1 -i ../apple.patch ifeq ($(OS),darwin_embedded) cd $(PLATFORM); patch -p1 -i ../darwin_embedded.patch endif - cp modules.setup $(PLATFORM)/Modules/Setup ifeq ($(OS),android) + cd $(PLATFORM); patch -p1 -i ../02-android-cpython114875.patch cd $(PLATFORM); patch -p1 -i ../10-android-modules.patch endif -ifeq ($(OS),linux) - cd $(PLATFORM); patch -p1 -i ../10-linux-modules.patch -endif -ifeq ($(OS),osx) - cd $(PLATFORM); patch -p1 -i ../10-osx-modules.patch -endif - cd $(PLATFORM); $(AUTORECONF) cd $(PLATFORM); $(CONFIGURE) $(LIBDYLIB): $(PLATFORM) - $(MAKE) -C $(PLATFORM) $(HOSTPLATFORM) CROSS_COMPILE_TARGET=yes libpython$(PYTHON_VERSION).a + $(MAKE) -C $(PLATFORM) $(HOSTPLATFORM) libpython$(PYTHON_VERSION).a touch $@ .installed-$(PLATFORM): $(LIBDYLIB) - $(MAKE) -C $(PLATFORM) $(HOSTPLATFORM) CROSS_COMPILE_TARGET=yes install +# We specifically use -j1 as some threading issues can occur with install directory creation + $(MAKE) -C $(PLATFORM) $(HOSTPLATFORM) install -j1 find $(PREFIX)/lib/python$(PYTHON_VERSION) -type f -name "*.pyc" -delete touch $(LIBDYLIB) touch $@ diff --git a/tools/depends/target/python3/PYTHON3-VERSION b/tools/depends/target/python3/PYTHON3-VERSION index c5e1760fb27a9..9c97bd4018c1f 100644 --- a/tools/depends/target/python3/PYTHON3-VERSION +++ b/tools/depends/target/python3/PYTHON3-VERSION @@ -1,4 +1,4 @@ LIBNAME=Python -VERSION=3.11.7 +VERSION=3.12.5 ARCHIVE=$(LIBNAME)-$(VERSION).tar.xz -SHA512=11e06f2ffe1f66888cb5b4e9f607de815294d6863a77eda6ec6d7c724ef158df9f51881f4a956d4a6fa973c2fb6fd031d495e3496e9b0bb53793fb1cc8434c63 +SHA512=7a1c30d798434fe24697bc253f6010d75145e7650f66803328425c8525331b9fa6b63d12a652687582db205f8d4c8279c8f73c338168592481517b063351c921 diff --git a/tools/depends/target/python3/apple.patch b/tools/depends/target/python3/apple.patch index 4deda311fbf66..1cad7696d29ce 100644 --- a/tools/depends/target/python3/apple.patch +++ b/tools/depends/target/python3/apple.patch @@ -28,12 +28,3 @@ # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from # defining NI_NUMERICHOST. QNX/6.3.2) -@@ -2947,7 +2947,7 @@ - return 1; - } - } -- ]])],[ac_osx_32bit=yes],[ac_osx_32bit=no],[ac_osx_32bit=yes]) -+ ]])],[ac_osx_32bit=yes],[ac_osx_32bit=no],[ac_osx_32bit=no]) - - if test "${ac_osx_32bit}" = "yes"; then - case `/usr/bin/arch` in diff --git a/tools/depends/target/python3/crosscompile.patch b/tools/depends/target/python3/crosscompile.patch deleted file mode 100644 index 6757c75ace92d..0000000000000 --- a/tools/depends/target/python3/crosscompile.patch +++ /dev/null @@ -1,64 +0,0 @@ ---- a/configure.ac -+++ b/configure.ac -@@ -1625,15 +1625,6 @@ - ARFLAGS="rcs" - fi - --AC_CHECK_TOOLS([READELF], [readelf], [:]) --if test "$cross_compiling" = yes; then -- case "$READELF" in -- readelf|:) -- AC_MSG_ERROR([readelf for the host is required for cross builds]) -- ;; -- esac --fi --AC_SUBST(READELF) - - - case $MACHDEP in ---- a/Makefile.pre.in -+++ b/Makefile.pre.in -@@ -2233,10 +2233,11 @@ - # This goes into $(exec_prefix) - sharedinstall: all - $(RUNSHARED) $(PYTHON_FOR_BUILD) $(srcdir)/setup.py install \ -+ --skip-build \ - --prefix=$(prefix) \ -- --install-scripts=$(BINDIR) \ -- --install-platlib=$(DESTSHARED) \ -- --root=$(DESTDIR)/ -+ --install-scripts=$(DESTDIR)$(BINDIR) \ -+ --install-platlib=$(DESTDIR)$(DESTSHARED) \ -+ --root=/ - -rm $(DESTDIR)$(DESTSHARED)/_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).py - -rm -r $(DESTDIR)$(DESTSHARED)/__pycache__ - ---- a/setup.py -+++ b/setup.py -@@ -77,7 +77,7 @@ - return sys.platform - - --CROSS_COMPILING = ("_PYTHON_HOST_PLATFORM" in os.environ) -+CROSS_COMPILING = ("_PYTHON_HOST_PLATFORM" in os.environ) or ('CROSS_COMPILE_TARGET' in os.environ) - HOST_PLATFORM = get_platform() - MS_WINDOWS = (HOST_PLATFORM == 'win32') - CYGWIN = (HOST_PLATFORM == 'cygwin') -@@ -488,6 +488,7 @@ - self.compiler.set_executables(**args) - - def build_extensions(self): -+ return - self.set_srcdir() - self.set_compiler_executables() - self.configure_compiler() -@@ -1343,7 +1343,7 @@ - # These are extensions are required to bootstrap the interpreter or - # build process. - self.detect_simple_extensions() -- self.detect_test_extensions() -+ #self.detect_test_extensions() - self.detect_readline_curses() - self.detect_crypt() - self.detect_openssl_hashlib() - diff --git a/tools/depends/target/python3/darwin_embedded.patch b/tools/depends/target/python3/darwin_embedded.patch index 77c9d0f30a767..bd27f7ef1fa8c 100644 --- a/tools/depends/target/python3/darwin_embedded.patch +++ b/tools/depends/target/python3/darwin_embedded.patch @@ -1,14 +1,3 @@ ---- a/configure.ac -+++ b/configure.ac -@@ -6827,7 +6827,7 @@ - AS_CASE([$ac_sys_system], - [AIX], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])], - [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [_crypt], [termios], [grp])], -- [Darwin], [PY_STDLIB_MOD_SET_NA([ossaudiodev], [spwd])], -+ [Darwin], [PY_STDLIB_MOD_SET_NA([ossaudiodev], [spwd], [_posixsubprocess], [_scproxy], [_tkinter], [_xxsubinterpreters])], - [CYGWIN*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])], - [QNX*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])], - [FreeBSD*], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])], --- a/Lib/os.py +++ b/Lib/os.py @@ -605,6 +605,7 @@ diff --git a/tools/depends/target/python3/modules.setup b/tools/depends/target/python3/modules.setup deleted file mode 100644 index c974a066c95d4..0000000000000 --- a/tools/depends/target/python3/modules.setup +++ /dev/null @@ -1,304 +0,0 @@ -# -*- makefile -*- -# The file Setup is used by the makesetup script to construct the files -# Makefile and config.c, from Makefile.pre and config.c.in, -# respectively. Note that Makefile.pre is created from Makefile.pre.in -# by the toplevel configure script. - -# (VPATH notes: Setup and Makefile.pre are in the build directory, as -# are Makefile and config.c; the *.in files are in the source directory.) - -# Each line in this file describes one or more optional modules. -# Modules configured here will not be compiled by the setup.py script, -# so the file can be used to override setup.py's behavior. -# Tag lines containing just the word "*static*", "*shared*" or "*disabled*" -# (without the quotes but with the stars) are used to tag the following module -# descriptions. Tag lines may alternate throughout this file. Modules are -# built statically when they are preceded by a "*static*" tag line or when -# there is no tag line between the start of the file and the module -# description. Modules are built as a shared library when they are preceded by -# a "*shared*" tag line. Modules are not built at all, not by the Makefile, -# nor by the setup.py script, when they are preceded by a "*disabled*" tag -# line. - -# Lines have the following structure: -# -# ... [ ...] [ ...] [ ...] -# -# is anything ending in .c (.C, .cc, .c++ are C++ files) -# is anything starting with -I, -D, -U or -C -# is anything ending in .a or beginning with -l or -L -# is anything else but should be a valid Python -# identifier (letters, digits, underscores, beginning with non-digit) -# -# (As the makesetup script changes, it may recognize some other -# arguments as well, e.g. *.so and *.sl as libraries. See the big -# case statement in the makesetup script.) -# -# Lines can also have the form -# -# = -# -# which defines a Make variable definition inserted into Makefile.in. -# You can also use any Make variable that is detected by configure and -# defined in Makefile.pre.in, e.g. OpenSSL flags $(OPENSSL_INCLUDES). -# -# Rules generated by makesetup use additional variables: -# -# - All source file rules have a dependency on $(PYTHON_HEADERS) and on -# optional variable $(MODULES_{mod_upper}_DEPS). -# - If no and no arguments are given, then makesetup -# defaults to $(MODULES_{mod_upper}_CFLAGS) cppargs and -# $(MODULES_{mod_upper}_LDFLAGS) libraries. The variables are typically -# defined by configure. -# -# The build process works like this: -# -# 1. Build all modules that are declared as static in Modules/Setup, -# combine them into libpythonxy.a, combine that into python. -# 2. Build all modules that are listed as shared in Modules/Setup. -# 3. Invoke setup.py. That builds all modules that -# a) are not builtin, and -# b) are not listed in Modules/Setup, and -# c) can be build on the target -# -# Therefore, modules declared to be shared will not be -# included in the config.c file, nor in the list of objects to be -# added to the library archive, and their linker options won't be -# added to the linker options. Rules to create their .o files and -# their shared libraries will still be added to the Makefile, and -# their names will be collected in the Make variable SHAREDMODS. This -# is used to build modules as shared libraries. (They can be -# installed using "make sharedinstall", which is implied by the -# toplevel "make install" target.) (For compatibility, -# *noconfig* has the same effect as *shared*.) -# -# NOTE: As a standard policy, as many modules as can be supported by a -# platform should be listed below. The distribution comes with all -# modules enabled that are supported by most platforms and don't -# require you to download sources from elsewhere. -# -# NOTE: Avoid editing this file directly. Local changes should go into -# Modules/Setup.local file. To enable all modules for testing, run -# -# sed -n -E 's/^#([a-z_\*].*)$/\1/p' Modules/Setup > Modules/Setup.local - - -# Some special rules to define PYTHONPATH. -# Edit the definitions below to indicate which options you are using. -# Don't add any whitespace or comments! - -# Directories where library files get installed. -# DESTLIB is for Python modules; MACHDESTLIB for shared libraries. -DESTLIB=$(LIBDEST) -MACHDESTLIB=$(BINLIBDEST) - -# NOTE: all the paths are now relative to the prefix that is computed -# at run time! - -# Standard path -- don't edit. -# No leading colon since this is the first entry. -# Empty since this is now just the runtime prefix. -DESTPATH= - -# Site specific path components -- should begin with : if non-empty -SITEPATH=:site-packages - -# Standard path components for test modules -TESTPATH= - -COREPYTHONPATH=$(DESTPATH)$(SITEPATH)$(TESTPATH) -PYTHONPATH=$(COREPYTHONPATH) - - -# --- -# Built-in modules required to get a functioning interpreter are listed in -# Modules/Setup.bootstrap. - -# --- -# The rest of the modules listed in this file are all commented out by -# default. Usually they can be detected and built as dynamically -# loaded modules by setup.py. If you're on a platform that doesn't -# support dynamic loading, want to compile modules statically into the -# Python binary, or need to specify some odd set of compiler switches, -# you can uncomment the appropriate lines below. - -# Uncommenting the following line tells makesetup that all following -# modules are to be built as shared libraries (see above for more -# detail; also note that *static* or *disabled* cancels this effect): - -#*shared* - -# Modules that should always be present (POSIX and Windows): - -_asyncio _asynciomodule.c -_bisect _bisectmodule.c -_contextvars _contextvarsmodule.c -_csv _csv.c -_datetime _datetimemodule.c -# _decimal _decimal/_decimal.c -_heapq _heapqmodule.c -_json _json.c -_lsprof _lsprof.c rotatingtree.c -_multiprocessing -I$(srcdir)/Modules/_multiprocessing _multiprocessing/multiprocessing.c _multiprocessing/semaphore.c -_opcode _opcode.c -_pickle _pickle.c -_queue _queuemodule.c -_random _randommodule.c -_socket socketmodule.c -_statistics _statisticsmodule.c -_struct _struct.c -_typing _typingmodule.c -_zoneinfo _zoneinfo.c -array arraymodule.c -#audioop audioop.c -binascii binascii.c -cmath cmathmodule.c -math mathmodule.c -mmap mmapmodule.c -select selectmodule.c - -# XML -_elementtree _elementtree.c -pyexpat pyexpat.c - -# hashing builtins -_blake2 _blake2/blake2module.c _blake2/blake2b_impl.c _blake2/blake2s_impl.c -_md5 md5module.c -_sha1 sha1module.c -_sha256 sha256module.c -_sha512 sha512module.c -_sha3 _sha3/sha3module.c - -# text encodings and unicode -_codecs_cn cjkcodecs/_codecs_cn.c -_codecs_hk cjkcodecs/_codecs_hk.c -_codecs_iso2022 cjkcodecs/_codecs_iso2022.c -_codecs_jp cjkcodecs/_codecs_jp.c -_codecs_kr cjkcodecs/_codecs_kr.c -_codecs_tw cjkcodecs/_codecs_tw.c -_multibytecodec cjkcodecs/multibytecodec.c -unicodedata unicodedata.c - -# Modules with some UNIX dependencies - -_posixsubprocess _posixsubprocess.c -_posixshmem -I$(srcdir)/Modules/_multiprocessing _multiprocessing/posixshmem.c # -lrt # _posixshmem -fcntl fcntlmodule.c -#grp grpmodule.c -#ossaudiodev ossaudiodev.c -resource resource.c -#spwd spwdmodule.c -#syslog syslogmodule.c -termios termios.c - -# Modules with UNIX dependencies that require external libraries - -#_crypt _cryptmodule.c -lcrypt -#nis nismodule.c -I/usr/include/tirpc -lnsl -ltirpc - -# Modules that require external libraries. - -_bz2 _bz2module.c -lbz2 -_ctypes _ctypes/_ctypes.c _ctypes/callbacks.c _ctypes/callproc.c _ctypes/stgdict.c _ctypes/cfield.c -I$(prefix)/include -L$(prefix)/lib -ldl -lffi -DHAVE_FFI_PREP_CIF_VAR -DHAVE_FFI_PREP_CLOSURE_LOC -DHAVE_FFI_CLOSURE_ALLOC -# The _dbm module supports NDBM, GDBM with compat module, and Berkeley DB. -#_dbm _dbmmodule.c -lgdbm_compat -DUSE_GDBM_COMPAT -#_gdbm _gdbmmodule.c -lgdbm -_lzma _lzmamodule.c -llzma -#_uuid _uuidmodule.c -luuid -zlib zlibmodule.c -lz - -_sqlite3 _sqlite/blob.c _sqlite/connection.c _sqlite/cursor.c _sqlite/microprotocols.c _sqlite/module.c _sqlite/prepare_protocol.c _sqlite/row.c _sqlite/statement.c _sqlite/util.c -lsqlite3 - -# The readline module also supports libeditline (-leditline). -# Some systems may require -ltermcap or -ltermlib. -#readline readline.c -lreadline -ltermcap - -# OpenSSL bindings -#_ssl _ssl.c $(OPENSSL_INCLUDES) $(OPENSSL_LDFLAGS) $(OPENSSL_LIBS) -#_hashlib _hashopenssl.c $(OPENSSL_INCLUDES) $(OPENSSL_LDFLAGS) -lcrypto - -# To statically link OpenSSL: - _ssl _ssl.c -I$(prefix)/include -I$(prefix)/include/openssl \ - -L$(prefix)/lib -lintl $(LDFLAGS) -lssl -lcrypto - _hashlib _hashopenssl.c -I$(prefix)/include -I$(prefix)/include/openssl \ - -L$(prefix)/lib - -# The _tkinter module. -# -# The command for _tkinter is long and site specific. Please -# uncomment and/or edit those parts as indicated. If you don't have a -# specific extension (e.g. Tix or BLT), leave the corresponding line -# commented out. (Leave the trailing backslashes in! If you -# experience strange errors, you may want to join all uncommented -# lines and remove the backslashes -- the backslash interpretation is -# done by the shell's "read" command and it may not be implemented on -# every system. - -# *** Always uncomment this (leave the leading underscore in!): -#_tkinter _tkinter.c tkappinit.c -DWITH_APPINIT $(TCLTK_INCLUDES) $(TCLTK_LIBS) \ -# *** Uncomment and edit to reflect where your Tcl/Tk libraries are: -# -L/usr/local/lib \ -# *** Uncomment and edit to reflect where your Tcl/Tk headers are: -# -I/usr/local/include \ -# *** Uncomment and edit to reflect where your X11 header files are: -# -I/usr/X11R6/include \ -# *** Or uncomment this for Solaris: -# -I/usr/openwin/include \ -# *** Uncomment and edit for Tix extension only: -# -DWITH_TIX -ltix8.1.8.2 \ -# *** Uncomment and edit for BLT extension only: -# -DWITH_BLT -I/usr/local/blt/blt8.0-unoff/include -lBLT8.0 \ -# *** Uncomment and edit for PIL (TkImaging) extension only: -# (See http://www.pythonware.com/products/pil/ for more info) -# -DWITH_PIL -I../Extensions/Imaging/libImaging tkImaging.c \ -# *** Uncomment and edit for TOGL extension only: -# -DWITH_TOGL togl.c \ -# *** Uncomment and edit to reflect where your X11 libraries are: -# -L/usr/X11R6/lib \ -# *** Or uncomment this for Solaris: -# -L/usr/openwin/lib \ -# *** Uncomment these for TOGL extension only: -# -lGL -lGLU -lXext -lXmu \ -# *** Uncomment for AIX: -# -lld \ -# *** Always uncomment this; X11 libraries to link with: -# -lX11 - -# Some system have -lcurses -#_curses -lncurses -lncursesw -ltermcap _cursesmodule.c -#_curses_panel -lpanel -lncurses _curses_panel.c - -# macOS specific module, needs SystemConfiguration and CoreFoundation framework -# _scproxy _scproxy.c -$(OSX_SCPROXY) - -# Examples - -#xx xxmodule.c -#xxlimited xxlimited.c -#xxlimited_35 xxlimited_35.c -xxsubtype xxsubtype.c # Required for the test suite to pass! - -# Testing - -#_xxsubinterpreters _xxsubinterpretersmodule.c -#_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c -#_testbuffer _testbuffer.c -#_testinternalcapi _testinternalcapi.c - -# Some testing modules MUST be built as shared libraries. - -#*shared* -#_ctypes_test _ctypes/_ctypes_test.c -#_testcapi _testcapimodule.c -#_testimportmultiple _testimportmultiple.c -#_testmultiphase _testmultiphase.c - -# --- -# Uncommenting the following line tells makesetup that all following modules -# are not built (see above for more detail). -# -#*disabled* -# -# _tkinter _curses -# _codecs_jp _codecs_kr _codecs_tw diff --git a/tools/depends/target/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION b/tools/depends/target/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION index 1eca9e1bc075c..1bf06499f3f8f 100644 --- a/tools/depends/target/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION +++ b/tools/depends/target/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION @@ -1,4 +1,4 @@ LIBNAME=setuptools -VERSION=65.5.0 +VERSION=72.1.0 ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz -SHA512=b3ed6546bfa45c96f9b69fd7f014a87b52e6d8a6591340bf980bd4de98e33dbe0990b089940c348f2ad20a27590b82de84aec44c8ba1dce0510a3835653930d3 +SHA512=d0a34f16dfa6bb9a6df39076cd43528cf854d343f6f801c448ea0ebab2a259aec3d03571e2a26709df6082ed2fcb6c43b86448be556fd559b6af41831b4f38e0 diff --git a/tools/depends/target/samba-gplv3/08-py312-distutils.patch b/tools/depends/target/samba-gplv3/08-py312-distutils.patch new file mode 100644 index 0000000000000..6ddb5f02a1a64 --- /dev/null +++ b/tools/depends/target/samba-gplv3/08-py312-distutils.patch @@ -0,0 +1,11 @@ +--- a/third_party/waf/waflib/Tools/python.py ++++ b/third_party/waf/waflib/Tools/python.py +@@ -53,7 +53,7 @@ + Piece of Python code used in :py:class:`waflib.Tools.python.pyo` and :py:class:`waflib.Tools.python.pyc` for byte-compiling python files + """ + +-DISTUTILS_IMP = ['from distutils.sysconfig import get_config_var, get_python_lib'] ++DISTUTILS_IMP = ['import setuptools\nfrom distutils.sysconfig import get_config_var, get_python_lib'] + + @before_method('process_source') + @feature('py') diff --git a/tools/depends/target/samba-gplv3/Makefile b/tools/depends/target/samba-gplv3/Makefile index 20a77dfe42ae0..b089dbd5634f8 100644 --- a/tools/depends/target/samba-gplv3/Makefile +++ b/tools/depends/target/samba-gplv3/Makefile @@ -3,6 +3,7 @@ DEPS= ../../Makefile.include Makefile SAMBA-GPLV3-VERSION ../../download-files.i 01-fix-dependencies.patch 02-cross_compile.patch \ 03-builtin-heimdal.patch 04-built-static.patch \ 05-apple-disable-zlib-pkgconfig.patch 06-apple-fix-st_atim.patch \ + 08-py312-distutils.patch \ samba_android.patch \ no_fork_and_exec.patch \ crt_extensions.patch \ @@ -89,6 +90,7 @@ endif ifeq ($(TARGET_PLATFORM),webos) cd $(PLATFORM); patch -p1 -i ../webos-no-readline.patch endif + cd $(PLATFORM); patch -p1 -i ../08-py312-distutils.patch cd $(PLATFORM); $(CONFIGURE) $(LIBDYLIB): $(PLATFORM) From e017c01da0c2134a5aac6f27602f5db3479d1c61 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 14 Aug 2024 23:47:58 +0200 Subject: [PATCH 374/651] [PVR] Fix CAddonTimer ctor, firstDay member init. Make CaddonChannel ctor explicit. --- xbmc/pvr/addons/PVRClient.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 88772c6c6b57f..875786675c29e 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -81,7 +81,7 @@ class CAddonChannelGroup : public PVR_CHANNEL_GROUP class CAddonChannel : public PVR_CHANNEL { public: - CAddonChannel(const CPVRChannel& channel, const std::string& newChannelName = "") + explicit CAddonChannel(const CPVRChannel& channel, const std::string& newChannelName = "") : m_channelName(newChannelName.empty() ? channel.ClientChannelName() : newChannelName), m_mimeType(channel.MimeType()), m_iconPath(channel.ClientIconPath()) @@ -194,8 +194,8 @@ class CAddonTimer : public PVR_TIMER timer.StartAsUTC().GetAsTime(start); time_t end; timer.EndAsUTC().GetAsTime(end); - time_t firstDay; - timer.FirstDayAsUTC().GetAsTime(firstDay); + time_t first; + timer.FirstDayAsUTC().GetAsTime(first); const std::shared_ptr epgTag{timer.GetEpgInfoTag()}; const int timeCorrection{ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection}; @@ -219,7 +219,7 @@ class CAddonTimer : public PVR_TIMER endTime = end - timeCorrection; bStartAnyTime = timer.IsStartAnyTime(); bEndAnyTime = timer.IsEndAnyTime(); - firstDay = firstDay - timeCorrection; + firstDay = first - timeCorrection; iEpgUid = epgTag ? epgTag->UniqueBroadcastID() : PVR_TIMER_NO_EPG_UID; strSummary = m_summary.c_str(); iMarginStart = timer.MarginStart(); From 87d905b62f8da7da9a3a4f20cf5a01567eb0b332 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:13:31 +0200 Subject: [PATCH 375/651] [addons] PVR Addon-API C++ binding: Fix getters for dynamic arrays. Move c-struct init to DynamicCStructHdl default ctor. --- .../kodi-dev-kit/include/kodi/AddonBase.h | 5 +- .../kodi/addon-instance/pvr/ChannelGroups.h | 14 +++-- .../kodi/addon-instance/pvr/Channels.h | 60 +++++++++++++----- .../include/kodi/addon-instance/pvr/EPG.h | 59 ++++++++++++----- .../include/kodi/addon-instance/pvr/General.h | 15 +++-- .../kodi/addon-instance/pvr/Providers.h | 19 ++++-- .../kodi/addon-instance/pvr/Recordings.h | 63 ++++++++++++++----- .../include/kodi/addon-instance/pvr/Timers.h | 41 ++++++------ 8 files changed, 189 insertions(+), 87 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h index c64ce05a1104d..54119f62aaa29 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h @@ -284,7 +284,10 @@ template class DynamicCStructHdl { public: - DynamicCStructHdl() : m_cStructure(new C_STRUCT()), m_owner(true) {} + DynamicCStructHdl() : m_cStructure(new C_STRUCT()), m_owner(true) + { + memset(m_cStructure, 0, sizeof(C_STRUCT)); + } DynamicCStructHdl(const CPP_CLASS& cppClass) : m_cStructure(new C_STRUCT(*cppClass.m_cStructure)), m_owner(true) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h index 44fbed3c79316..82f26f9aac073 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h @@ -40,7 +40,7 @@ class PVRChannelGroup : public DynamicCStructHdlstrGroupName; } + std::string GetGroupName() const + { + return m_cStructure->strGroupName ? m_cStructure->strGroupName : ""; + } /// @brief **required**\n /// **true** If this is a radio channel group, **false** otherwise. @@ -158,7 +161,7 @@ class PVRChannelGroupMember public: /*! \cond PRIVATE */ - PVRChannelGroupMember() { memset(m_cStructure, 0, sizeof(PVR_CHANNEL_GROUP_MEMBER)); } + PVRChannelGroupMember() = default; PVRChannelGroupMember(const PVRChannelGroupMember& member) : DynamicCStructHdl(member) {} /*! \endcond */ @@ -186,7 +189,10 @@ class PVRChannelGroupMember } /// @brief To get with @ref SetGroupName changed values. - std::string GetGroupName() const { return m_cStructure->strGroupName; } + std::string GetGroupName() const + { + return m_cStructure->strGroupName ? m_cStructure->strGroupName : ""; + } /// @brief **required**\n /// Unique id of the member. diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h index cbbb4c3abdcca..8ca90882a8e05 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h @@ -41,11 +41,7 @@ class PVRChannel : public DynamicCStructHdl public: /*! \cond PRIVATE */ - PVRChannel() - { - memset(m_cStructure, 0, sizeof(PVR_CHANNEL)); - m_cStructure->iClientProviderUid = PVR_PROVIDER_INVALID_UID; - } + PVRChannel() { m_cStructure->iClientProviderUid = PVR_PROVIDER_INVALID_UID; } PVRChannel(const PVRChannel& channel) : DynamicCStructHdl(channel) {} /*! \endcond */ @@ -114,7 +110,10 @@ class PVRChannel : public DynamicCStructHdl } /// @brief To get with @ref SetChannelName changed values. - std::string GetChannelName() const { return m_cStructure->strChannelName; } + std::string GetChannelName() const + { + return m_cStructure->strChannelName ? m_cStructure->strChannelName : ""; + } /// @brief **optional**\n /// Input format mime type. @@ -128,7 +127,10 @@ class PVRChannel : public DynamicCStructHdl } /// @brief To get with @ref SetMimeType changed values. - std::string GetMimeType() const { return m_cStructure->strMimeType; } + std::string GetMimeType() const + { + return m_cStructure->strMimeType ? m_cStructure->strMimeType : ""; + } /// @brief **optional**\n /// The encryption ID or CaID of this channel (Conditional access systems). @@ -153,7 +155,10 @@ class PVRChannel : public DynamicCStructHdl } /// @brief To get with @ref SetIconPath changed values. - std::string GetIconPath() const { return m_cStructure->strIconPath; } + std::string GetIconPath() const + { + return m_cStructure->strIconPath ? m_cStructure->strIconPath : ""; + } /// @brief **optional**\n /// **true** if this channel is marked as hidden. @@ -300,7 +305,10 @@ class PVRSignalStatus : public DynamicCStructHdlstrAdapterName; } + std::string GetAdapterName() const + { + return m_cStructure->strAdapterName ? m_cStructure->strAdapterName : ""; + } /// @brief **optional**\n /// Status of the adapter that's being used. @@ -310,7 +318,10 @@ class PVRSignalStatus : public DynamicCStructHdlstrAdapterStatus; } + std::string GetAdapterStatus() const + { + return m_cStructure->strAdapterStatus ? m_cStructure->strAdapterStatus : ""; + } /// @brief **optional**\n /// Name of the current service. @@ -320,7 +331,10 @@ class PVRSignalStatus : public DynamicCStructHdlstrServiceName; } + std::string GetServiceName() const + { + return m_cStructure->strServiceName ? m_cStructure->strServiceName : ""; + } /// @brief **optional**\n /// Name of the current service's provider. @@ -330,7 +344,10 @@ class PVRSignalStatus : public DynamicCStructHdlstrProviderName; } + std::string GetProviderName() const + { + return m_cStructure->strProviderName ? m_cStructure->strProviderName : ""; + } /// @brief **optional**\n /// Name of the current mux. @@ -340,7 +357,10 @@ class PVRSignalStatus : public DynamicCStructHdlstrMuxName; } + std::string GetMuxName() const + { + return m_cStructure->strMuxName ? m_cStructure->strMuxName : ""; + } /// @brief **optional**\n /// Signal/noise ratio. @@ -514,7 +534,10 @@ class PVRDescrambleInfo : public DynamicCStructHdlstrCardSystem; } + std::string GetCardSystem() const + { + return m_cStructure->strCardSystem ? m_cStructure->strCardSystem : ""; + } /// @brief **optional**\n /// Empty string if not available. @@ -524,7 +547,7 @@ class PVRDescrambleInfo : public DynamicCStructHdlstrReader; } + std::string GetReader() const { return m_cStructure->strReader ? m_cStructure->strReader : ""; } /// @brief **optional**\n /// Empty string if not available. @@ -534,7 +557,7 @@ class PVRDescrambleInfo : public DynamicCStructHdlstrFrom; } + std::string GetFrom() const { return m_cStructure->strFrom ? m_cStructure->strFrom : ""; } /// @brief **optional**\n /// Empty string if not available. @@ -544,7 +567,10 @@ class PVRDescrambleInfo : public DynamicCStructHdlstrProtocol; } + std::string GetProtocol() const + { + return m_cStructure->strProtocol ? m_cStructure->strProtocol : ""; + } ///@} static void AllocResources(const PVR_DESCRAMBLE_INFO* source, PVR_DESCRAMBLE_INFO* target) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h index 41d9258cc9fa7..8bb4f7354e18a 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h @@ -44,7 +44,6 @@ class PVREPGTag : public DynamicCStructHdl /*! \cond PRIVATE */ PVREPGTag() { - memset(m_cStructure, 0, sizeof(EPG_TAG)); m_cStructure->iSeriesNumber = EPG_TAG_INVALID_SERIES_EPISODE; m_cStructure->iEpisodeNumber = EPG_TAG_INVALID_SERIES_EPISODE; m_cStructure->iEpisodePartNumber = EPG_TAG_INVALID_SERIES_EPISODE; @@ -118,7 +117,7 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref SetTitle changed values. - std::string GetTitle() const { return m_cStructure->strTitle; } + std::string GetTitle() const { return m_cStructure->strTitle ? m_cStructure->strTitle : ""; } /// @brief **required**\n /// Start time in UTC. @@ -146,7 +145,10 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref SetPlotOutline changed values. - std::string GetPlotOutline() const { return m_cStructure->strPlotOutline; } + std::string GetPlotOutline() const + { + return m_cStructure->strPlotOutline ? m_cStructure->strPlotOutline : ""; + } /// @brief **optional**\n /// Plot name. @@ -156,7 +158,7 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref GetPlot changed values. - std::string GetPlot() const { return m_cStructure->strPlot; } + std::string GetPlot() const { return m_cStructure->strPlot ? m_cStructure->strPlot : ""; } /// @brief **optional**\n /// Original title. @@ -166,7 +168,10 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref SetOriginalTitle changed values - std::string GetOriginalTitle() const { return m_cStructure->strOriginalTitle; } + std::string GetOriginalTitle() const + { + return m_cStructure->strOriginalTitle ? m_cStructure->strOriginalTitle : ""; + } /// @brief **optional**\n /// Cast name(s). @@ -178,7 +183,7 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref SetCast changed values - std::string GetCast() const { return m_cStructure->strCast; } + std::string GetCast() const { return m_cStructure->strCast ? m_cStructure->strCast : ""; } /// @brief **optional**\n /// Director name(s). @@ -190,7 +195,10 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref SetDirector changed values. - std::string GetDirector() const { return m_cStructure->strDirector; } + std::string GetDirector() const + { + return m_cStructure->strDirector ? m_cStructure->strDirector : ""; + } /// @brief **optional**\n /// Writer name(s). @@ -202,7 +210,7 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref SetDirector changed values - std::string GetWriter() const { return m_cStructure->strWriter; } + std::string GetWriter() const { return m_cStructure->strWriter ? m_cStructure->strWriter : ""; } /// @brief **optional**\n /// Year. @@ -219,7 +227,10 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref SetIMDBNumber changed values. - std::string GetIMDBNumber() const { return m_cStructure->strIMDBNumber; } + std::string GetIMDBNumber() const + { + return m_cStructure->strIMDBNumber ? m_cStructure->strIMDBNumber : ""; + } /// @brief **optional**\n /// Icon path. @@ -229,7 +240,10 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref SetIconPath changed values. - std::string GetIconPath() const { return m_cStructure->strIconPath; } + std::string GetIconPath() const + { + return m_cStructure->strIconPath ? m_cStructure->strIconPath : ""; + } /// @brief **optional**\n /// Genre type. @@ -327,7 +341,10 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref SetGenreDescription changed values. - std::string GetGenreDescription() const { return m_cStructure->strGenreDescription; } + std::string GetGenreDescription() const + { + return m_cStructure->strGenreDescription ? m_cStructure->strGenreDescription : ""; + } /// @brief **optional**\n /// First aired in UTC. @@ -337,7 +354,10 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref SetFirstAired changed values. - std::string GetFirstAired() const { return m_cStructure->strFirstAired; } + std::string GetFirstAired() const + { + return m_cStructure->strFirstAired ? m_cStructure->strFirstAired : ""; + } /// @brief **optional**\n /// Parental rating. @@ -354,7 +374,10 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref SetParentalRatingCode changed values. - std::string GetParentalRatingCode() const { return m_cStructure->strParentalRatingCode; } + std::string GetParentalRatingCode() const + { + return m_cStructure->strParentalRatingCode ? m_cStructure->strParentalRatingCode : ""; + } /// @brief **optional**\n /// Star rating. @@ -395,7 +418,10 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref SetEpisodeName changed values. - std::string GetEpisodeName() const { return m_cStructure->strEpisodeName; } + std::string GetEpisodeName() const + { + return m_cStructure->strEpisodeName ? m_cStructure->strEpisodeName : ""; + } /// @brief **optional**\n /// Bit field of independent flags associated with the EPG entry. @@ -419,7 +445,10 @@ class PVREPGTag : public DynamicCStructHdl } /// @brief To get with @ref SetSeriesLink changed values. - std::string GetSeriesLink() const { return m_cStructure->strSeriesLink; } + std::string GetSeriesLink() const + { + return m_cStructure->strSeriesLink ? m_cStructure->strSeriesLink : ""; + } ///@} diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h index dfb7cfa1accf3..8067316df1e05 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h @@ -55,8 +55,6 @@ class PVRTypeIntValue : public DynamicCStructHdlstrDescription; } + std::string GetDescription() const + { + return m_cStructure->strDescription ? m_cStructure->strDescription : ""; + } ///@} static PVR_ATTRIBUTE_INT_VALUE* AllocAndCopyData(const std::vector& source) @@ -171,7 +172,7 @@ class PVRCapabilities : public DynamicCStructHdlstrName; } + std::string GetName() const { return m_cStructure->strName ? m_cStructure->strName : ""; } /// @brief To set with the used property value. void SetValue(const std::string& value) @@ -580,7 +579,7 @@ class PVRStreamProperty : public DynamicCStructHdlstrValue; } + std::string GetValue() const { return m_cStructure->strValue ? m_cStructure->strValue : ""; } ///@} static void AllocResources(const PVR_NAMED_VALUE* source, PVR_NAMED_VALUE* target) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Providers.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Providers.h index a76f44ca9f424..508eb2d294637 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Providers.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Providers.h @@ -42,7 +42,7 @@ class PVRProvider : public DynamicCStructHdl public: /*! \cond PRIVATE */ - PVRProvider() { memset(m_cStructure, 0, sizeof(PVR_PROVIDER)); } + PVRProvider() = default; PVRProvider(const PVRProvider& provider) : DynamicCStructHdl(provider) {} /*! \endcond */ @@ -78,7 +78,7 @@ class PVRProvider : public DynamicCStructHdl } /// @brief To get with @ref SetName changed values. - std::string GetName() const { return m_cStructure->strName; } + std::string GetName() const { return m_cStructure->strName ? m_cStructure->strName : ""; } /// @brief **optional**\n /// Provider type. @@ -107,7 +107,10 @@ class PVRProvider : public DynamicCStructHdl } /// @brief To get with @ref SetIconPath changed values. - std::string GetIconPath() const { return m_cStructure->strIconPath; } + std::string GetIconPath() const + { + return m_cStructure->strIconPath ? m_cStructure->strIconPath : ""; + } ///@} /// @brief **optional**\n @@ -124,7 +127,10 @@ class PVRProvider : public DynamicCStructHdl /// @brief To get with @ref SetCountries changed values. std::vector GetCountries() const { - return tools::StringUtils::Split(m_cStructure->strCountries, PROVIDER_STRING_TOKEN_SEPARATOR); + if (m_cStructure->strCountries) + return tools::StringUtils::Split(m_cStructure->strCountries, PROVIDER_STRING_TOKEN_SEPARATOR); + else + return {}; } ///@} @@ -142,7 +148,10 @@ class PVRProvider : public DynamicCStructHdl /// @brief To get with @ref SetLanguages changed values. std::vector GetLanguages() const { - return tools::StringUtils::Split(m_cStructure->strLanguages, PROVIDER_STRING_TOKEN_SEPARATOR); + if (m_cStructure->strLanguages) + return tools::StringUtils::Split(m_cStructure->strLanguages, PROVIDER_STRING_TOKEN_SEPARATOR); + else + return {}; } ///@} diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h index 9e27c513f71a0..e1ec4637ca6ff 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h @@ -45,7 +45,6 @@ class PVRRecording : public DynamicCStructHdl { m_cStructure->iSeriesNumber = PVR_RECORDING_INVALID_SERIES_EPISODE; m_cStructure->iEpisodeNumber = PVR_RECORDING_INVALID_SERIES_EPISODE; - m_cStructure->recordingTime = 0; m_cStructure->iDuration = PVR_RECORDING_VALUE_NOT_AVAILABLE; m_cStructure->iPriority = PVR_RECORDING_VALUE_NOT_AVAILABLE; m_cStructure->iLifetime = PVR_RECORDING_VALUE_NOT_AVAILABLE; @@ -53,11 +52,8 @@ class PVRRecording : public DynamicCStructHdl m_cStructure->iGenreSubType = PVR_RECORDING_VALUE_NOT_AVAILABLE; m_cStructure->iPlayCount = PVR_RECORDING_VALUE_NOT_AVAILABLE; m_cStructure->iLastPlayedPosition = PVR_RECORDING_VALUE_NOT_AVAILABLE; - m_cStructure->bIsDeleted = false; - m_cStructure->iEpgEventId = 0; m_cStructure->iChannelUid = PVR_RECORDING_VALUE_NOT_AVAILABLE; m_cStructure->channelType = PVR_RECORDING_CHANNEL_TYPE_UNKNOWN; - m_cStructure->iFlags = 0; m_cStructure->sizeInBytes = PVR_RECORDING_VALUE_NOT_AVAILABLE; } PVRRecording(const PVRRecording& recording) : DynamicCStructHdl(recording) {} @@ -112,7 +108,10 @@ class PVRRecording : public DynamicCStructHdl } /// @brief To get with @ref SetRecordingId changed values. - std::string GetRecordingId() const { return m_cStructure->strRecordingId; } + std::string GetRecordingId() const + { + return m_cStructure->strRecordingId ? m_cStructure->strRecordingId : ""; + } /// @brief **required**\n /// The title of this recording. @@ -122,7 +121,7 @@ class PVRRecording : public DynamicCStructHdl } /// @brief To get with @ref SetTitle changed values. - std::string GetTitle() const { return m_cStructure->strTitle; } + std::string GetTitle() const { return m_cStructure->strTitle ? m_cStructure->strTitle : ""; } /// @brief **optional**\n /// Episode name (also known as subtitle). @@ -132,7 +131,10 @@ class PVRRecording : public DynamicCStructHdl } /// @brief To get with @ref SetEpisodeName changed values. - std::string GetEpisodeName() const { return m_cStructure->strEpisodeName; } + std::string GetEpisodeName() const + { + return m_cStructure->strEpisodeName ? m_cStructure->strEpisodeName : ""; + } /// @brief **optional**\n /// Series number (usually called season). @@ -171,7 +173,10 @@ class PVRRecording : public DynamicCStructHdl } /// @brief To get with @ref SetDirectory changed values. - std::string GetDirectory() const { return m_cStructure->strDirectory; } + std::string GetDirectory() const + { + return m_cStructure->strDirectory ? m_cStructure->strDirectory : ""; + } /// @brief **optional**\n /// Plot outline name. @@ -181,7 +186,10 @@ class PVRRecording : public DynamicCStructHdl } /// @brief To get with @ref SetPlotOutline changed values. - std::string GetPlotOutline() const { return m_cStructure->strPlotOutline; } + std::string GetPlotOutline() const + { + return m_cStructure->strPlotOutline ? m_cStructure->strPlotOutline : ""; + } /// @brief **optional**\n /// Plot name. @@ -191,7 +199,7 @@ class PVRRecording : public DynamicCStructHdl } /// @brief To get with @ref SetPlot changed values. - std::string GetPlot() const { return m_cStructure->strPlot; } + std::string GetPlot() const { return m_cStructure->strPlot ? m_cStructure->strPlot : ""; } /// @brief **optional**\n /// Channel name. @@ -201,7 +209,10 @@ class PVRRecording : public DynamicCStructHdl } /// @brief To get with @ref SetChannelName changed values. - std::string GetChannelName() const { return m_cStructure->strChannelName; } + std::string GetChannelName() const + { + return m_cStructure->strChannelName ? m_cStructure->strChannelName : ""; + } /// @brief **optional**\n /// Channel logo (icon) path. @@ -211,7 +222,10 @@ class PVRRecording : public DynamicCStructHdl } /// @brief To get with @ref SetIconPath changed values. - std::string GetIconPath() const { return m_cStructure->strIconPath; } + std::string GetIconPath() const + { + return m_cStructure->strIconPath ? m_cStructure->strIconPath : ""; + } /// @brief **optional**\n /// Thumbnail path. @@ -221,7 +235,10 @@ class PVRRecording : public DynamicCStructHdl } /// @brief To get with @ref SetThumbnailPath changed values. - std::string GetThumbnailPath() const { return m_cStructure->strThumbnailPath; } + std::string GetThumbnailPath() const + { + return m_cStructure->strThumbnailPath ? m_cStructure->strThumbnailPath : ""; + } /// @brief **optional**\n /// Fanart path. @@ -231,7 +248,10 @@ class PVRRecording : public DynamicCStructHdl } /// @brief To get with @ref SetFanartPath changed values. - std::string GetFanartPath() const { return m_cStructure->strFanartPath; } + std::string GetFanartPath() const + { + return m_cStructure->strFanartPath ? m_cStructure->strFanartPath : ""; + } /// @brief **optional**\n /// Start time of the recording. @@ -354,7 +374,10 @@ class PVRRecording : public DynamicCStructHdl } /// @brief To get with @ref SetGenreDescription changed values. - std::string GetGenreDescription() const { return m_cStructure->strGenreDescription; } + std::string GetGenreDescription() const + { + return m_cStructure->strGenreDescription ? m_cStructure->strGenreDescription : ""; + } /// @brief **optional**\n /// Play count of this recording on the client. @@ -436,7 +459,10 @@ class PVRRecording : public DynamicCStructHdl } /// @brief To get with @ref SetFirstAired changed values - std::string GetFirstAired() const { return m_cStructure->strFirstAired; } + std::string GetFirstAired() const + { + return m_cStructure->strFirstAired ? m_cStructure->strFirstAired : ""; + } /// @brief **optional**\n /// Bit field of independent flags associated with the recording. @@ -481,7 +507,10 @@ class PVRRecording : public DynamicCStructHdl } /// @brief To get with @ref SetProviderName changed values. - std::string GetProviderName() const { return m_cStructure->strProviderName; } + std::string GetProviderName() const + { + return m_cStructure->strProviderName ? m_cStructure->strProviderName : ""; + } static void AllocResources(const PVR_RECORDING* source, PVR_RECORDING* target) { diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h index 2dcf8029fadfc..4435baf930456 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h @@ -43,26 +43,13 @@ class PVRTimer : public DynamicCStructHdl /*! \cond PRIVATE */ PVRTimer() { - m_cStructure->iClientIndex = 0; m_cStructure->state = PVR_TIMER_STATE_NEW; m_cStructure->iTimerType = PVR_TIMER_TYPE_NONE; - m_cStructure->iParentClientIndex = 0; m_cStructure->iClientChannelUid = PVR_TIMER_VALUE_NOT_AVAILABLE; - m_cStructure->startTime = 0; - m_cStructure->endTime = 0; - m_cStructure->bStartAnyTime = false; - m_cStructure->bEndAnyTime = false; - m_cStructure->bFullTextEpgSearch = false; m_cStructure->iPriority = PVR_TIMER_VALUE_NOT_AVAILABLE; m_cStructure->iLifetime = PVR_TIMER_VALUE_NOT_AVAILABLE; m_cStructure->iMaxRecordings = PVR_TIMER_VALUE_NOT_AVAILABLE; - m_cStructure->iRecordingGroup = 0; - m_cStructure->firstDay = 0; m_cStructure->iWeekdays = PVR_WEEKDAY_NONE; - m_cStructure->iPreventDuplicateEpisodes = 0; - m_cStructure->iEpgUid = 0; - m_cStructure->iMarginStart = 0; - m_cStructure->iMarginEnd = 0; m_cStructure->iGenreType = PVR_TIMER_VALUE_NOT_AVAILABLE; m_cStructure->iGenreSubType = PVR_TIMER_VALUE_NOT_AVAILABLE; } @@ -171,7 +158,7 @@ class PVRTimer : public DynamicCStructHdl } /// @brief To get with @ref SetTitle changed values. - std::string GetTitle() const { return m_cStructure->strTitle; } + std::string GetTitle() const { return m_cStructure->strTitle ? m_cStructure->strTitle : ""; } /// @brief **optional**\n /// For timers scheduled by a repeating timer. @@ -245,7 +232,10 @@ class PVRTimer : public DynamicCStructHdl } /// @brief To get with @ref SetEPGSearchString changed values - std::string GetEPGSearchString() const { return m_cStructure->strEpgSearchString; } + std::string GetEPGSearchString() const + { + return m_cStructure->strEpgSearchString ? m_cStructure->strEpgSearchString : ""; + } /// @brief **optional**\n /// Indicates, whether @ref SetEPGSearchString() is to match against the epg @@ -266,7 +256,10 @@ class PVRTimer : public DynamicCStructHdl } /// @brief To get with @ref SetDirectory changed values. - std::string GetDirectory() const { return m_cStructure->strDirectory; } + std::string GetDirectory() const + { + return m_cStructure->strDirectory ? m_cStructure->strDirectory : ""; + } /// @brief **optional**\n /// The summary for this timer. @@ -276,7 +269,10 @@ class PVRTimer : public DynamicCStructHdl } /// @brief To get with @ref SetDirectory changed values. - std::string GetSummary() const { return m_cStructure->strSummary; } + std::string GetSummary() const + { + return m_cStructure->strSummary ? m_cStructure->strSummary : ""; + } /// @brief **optional**\n /// The priority of this timer. @@ -461,7 +457,10 @@ class PVRTimer : public DynamicCStructHdl } /// @brief To get with @ref SetSeriesLink changed values. - std::string GetSeriesLink() const { return m_cStructure->strSeriesLink; } + std::string GetSeriesLink() const + { + return m_cStructure->strSeriesLink ? m_cStructure->strSeriesLink : ""; + } ///@} static void AllocResources(const PVR_TIMER* source, PVR_TIMER* target) @@ -550,7 +549,6 @@ class PVRTimerType : public DynamicCStructHdl /*! \cond PRIVATE */ PVRTimerType() { - memset(m_cStructure, 0, sizeof(PVR_TIMER_TYPE)); m_cStructure->iPrioritiesDefault = -1; m_cStructure->iLifetimesDefault = -1; m_cStructure->iPreventDuplicateEpisodesDefault = -1; @@ -628,7 +626,10 @@ class PVRTimerType : public DynamicCStructHdl } /// @brief To get with @ref SetDescription changed values. - std::string GetDescription() const { return m_cStructure->strDescription; } + std::string GetDescription() const + { + return m_cStructure->strDescription ? m_cStructure->strDescription : ""; + } //---------------------------------------------------------------------------- From e46c19e5b85c5c501d9d7d4faca5bc58aef11f76 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:25:20 +0200 Subject: [PATCH 376/651] [addons][PVR] PVR Addon-API: Add EPG_TAG::strParentalRatingIcon, EPG_TAG::strParentalRatingSource. Changed EPG_TAG::iParentalRating type to unsigned int. Fix documentation. --- .../include/kodi/addon-instance/pvr/EPG.h | 46 +++++++++++++++++-- .../kodi/c-api/addon-instance/pvr/pvr_epg.h | 4 +- xbmc/pvr/addons/PVRClient.cpp | 8 +++- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h index 8bb4f7354e18a..f0610402cfce3 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h @@ -75,8 +75,10 @@ class PVREPGTag : public DynamicCStructHdl /// | **Genre sub type** | `int` | @ref PVREPGTag::SetGenreSubType "SetGenreSubType" | @ref PVREPGTag::GetGenreSubType "GetGenreSubType" | *optional* /// | **Genre description** | `std::string` | @ref PVREPGTag::SetGenreDescription "SetGenreDescription" | @ref PVREPGTag::GetGenreDescription "GetGenreDescription" | *optional* /// | **First aired** | `time_t` | @ref PVREPGTag::SetFirstAired "SetFirstAired" | @ref PVREPGTag::GetFirstAired "GetFirstAired" | *optional* - /// | **Parental rating** | `int` | @ref PVREPGTag::SetParentalRating "SetParentalRating" | @ref PVREPGTag::GetParentalRating "GetParentalRating" | *optional* - /// | **Parental rating code** | `int` | @ref PVREPGTag::SetParentalRatingCode "SetParentalRatingCode" | @ref PVREPGTag::GetParentalRatingCode "GetParentalRatingCode" | *optional* + /// | **Parental rating** | `unsigned int` | @ref PVREPGTag::SetParentalRating "SetParentalRating" | @ref PVREPGTag::GetParentalRating "GetParentalRating" | *optional* + /// | **Parental rating code** | `std::string` | @ref PVREPGTag::SetParentalRatingCode "SetParentalRatingCode" | @ref PVREPGTag::GetParentalRatingCode "GetParentalRatingCode" | *optional* + /// | **Parental rating icon** | `std::string` | @ref PVREPGTag::SetParentalRatingIcon "SetParentalRatingIcon" | @ref PVREPGTag::GetParentalRatingIcon "GetParentalRatingIcon" | *optional* + /// | **Parental rating source** | `std::string` | @ref PVREPGTag::SetParentalRatingSource "SetParentalRatingSource" | @ref PVREPGTag::GetParentalRatingSource "GetParentalRatingSource" | *optional* /// | **Star rating** | `int` | @ref PVREPGTag::SetStarRating "SetStarRating" | @ref PVREPGTag::GetStarRating "GetStarRating" | *optional* /// | **Series number** | `int` | @ref PVREPGTag::SetSeriesNumber "SetSeriesNumber" | @ref PVREPGTag::GetSeriesNumber "GetSeriesNumber" | *optional* /// | **Episode number** | `int` | @ref PVREPGTag::SetEpisodeNumber "SetEpisodeNumber" | @ref PVREPGTag::GetEpisodeNumber "GetEpisodeNumber" | *optional* @@ -361,12 +363,15 @@ class PVREPGTag : public DynamicCStructHdl /// @brief **optional**\n /// Parental rating. - void SetParentalRating(int parentalRating) { m_cStructure->iParentalRating = parentalRating; } + void SetParentalRating(unsigned int parentalRating) + { + m_cStructure->iParentalRating = parentalRating; + } /// @brief To get with @ref SetParentalRatinge changed values. - int GetParentalRating() const { return m_cStructure->iParentalRating; } + unsigned int GetParentalRating() const { return m_cStructure->iParentalRating; } - /// @brief **required**\n + /// @brief **optional**\n /// This event's parental rating code. void SetParentalRatingCode(const std::string& parentalRatingCode) { @@ -379,6 +384,33 @@ class PVREPGTag : public DynamicCStructHdl return m_cStructure->strParentalRatingCode ? m_cStructure->strParentalRatingCode : ""; } + /// @brief **optional**\n + /// This event's parental rating icon. + void SetParentalRatingIcon(const std::string& parentalRatingIcon) + { + ReallocAndCopyString(&m_cStructure->strParentalRatingIcon, parentalRatingIcon.c_str()); + } + + /// @brief To get with @ref SetParentalRatingIcon changed values. + std::string GetParentalRatingIcon() const + { + return m_cStructure->strParentalRatingIcon ? m_cStructure->strParentalRatingIcon : ""; + } + + /// @brief **optional**\n + /// The event's parental rating source. + void SetParentalRatingSource(const std::string& parentalRatingSource) + { + //m_parentalRatingSource = parentalRatingSource; + ReallocAndCopyString(&m_cStructure->strParentalRatingSource, parentalRatingSource.c_str()); + } + + /// @brief To get with @ref SetParentalRatingSource changed values. + std::string GetParentalRatingSource() const + { + return m_cStructure->strParentalRatingSource ? m_cStructure->strParentalRatingSource : ""; + } + /// @brief **optional**\n /// Star rating. void SetStarRating(int starRating) { m_cStructure->iStarRating = starRating; } @@ -465,6 +497,8 @@ class PVREPGTag : public DynamicCStructHdl target->strIconPath = AllocAndCopyString(source->strIconPath); target->strGenreDescription = AllocAndCopyString(source->strGenreDescription); target->strParentalRatingCode = AllocAndCopyString(source->strParentalRatingCode); + target->strParentalRatingIcon = AllocAndCopyString(source->strParentalRatingIcon); + target->strParentalRatingSource = AllocAndCopyString(source->strParentalRatingSource); target->strEpisodeName = AllocAndCopyString(source->strEpisodeName); target->strSeriesLink = AllocAndCopyString(source->strSeriesLink); target->strFirstAired = AllocAndCopyString(source->strFirstAired); @@ -483,6 +517,8 @@ class PVREPGTag : public DynamicCStructHdl FreeString(target->strIconPath); FreeString(target->strGenreDescription); FreeString(target->strParentalRatingCode); + FreeString(target->strParentalRatingIcon); + FreeString(target->strParentalRatingSource); FreeString(target->strEpisodeName); FreeString(target->strSeriesLink); FreeString(target->strFirstAired); diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h index 415a59342c38b..d41d017a93d93 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h @@ -639,8 +639,10 @@ extern "C" int iGenreSubType; const char* strGenreDescription; const char* strFirstAired; - int iParentalRating; + unsigned int iParentalRating; const char* strParentalRatingCode; + const char* strParentalRatingIcon; + const char* strParentalRatingSource; int iStarRating; int iSeriesNumber; int iEpisodeNumber; diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 88772c6c6b57f..0b5a83af5f66e 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -812,7 +812,9 @@ class CAddonEpgTag : public EPG_TAG m_strIconPath(kodiTag->ClientIconPath()), m_strSeriesLink(kodiTag->SeriesLink()), m_strGenreDescription(kodiTag->GenreDescription()), - m_strParentalRatingCode(kodiTag->ParentalRatingCode()) + m_strParentalRatingCode(kodiTag->ParentalRatingCode()), + m_strParentalRatingIcon(""), //! @todo + m_strParentalRatingSource("") //! @todo { time_t t; kodiTag->StartAsUTC().GetAsTime(t); @@ -849,6 +851,8 @@ class CAddonEpgTag : public EPG_TAG strGenreDescription = m_strGenreDescription.c_str(); strFirstAired = m_strFirstAired.c_str(); strParentalRatingCode = m_strParentalRatingCode.c_str(); + strParentalRatingIcon = m_strParentalRatingIcon.c_str(); + strParentalRatingSource = m_strParentalRatingSource.c_str(); } virtual ~CAddonEpgTag() = default; @@ -868,6 +872,8 @@ class CAddonEpgTag : public EPG_TAG std::string m_strGenreDescription; std::string m_strFirstAired; std::string m_strParentalRatingCode; + std::string m_strParentalRatingIcon; + std::string m_strParentalRatingSource; }; PVR_ERROR CPVRClient::IsRecordable(const std::shared_ptr& tag, From 01775f45c389fc71fe945e10c3b8ad43293ecae6 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:34:21 +0200 Subject: [PATCH 377/651] [addons][PVR] PVR Addon-API: Add PVR_RECORDING::iParentalRating, PVR_RECORDING::strParentalRatingCode, PVR_RECORDING::strParentalRatingIcon, PVR_RECORDING::strParentalRatingSource. --- .../kodi/addon-instance/pvr/Recordings.h | 59 +++++++++++++++++++ .../c-api/addon-instance/pvr/pvr_recordings.h | 4 ++ xbmc/pvr/addons/PVRClient.cpp | 11 +++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h index e1ec4637ca6ff..0e92263ec29f4 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h @@ -96,6 +96,10 @@ class PVRRecording : public DynamicCStructHdl /// | **Size in bytes** | `std::string` | @ref PVRRecording::SetSizeInBytes "SetSizeInBytes" | @ref PVRRecording::GetSizeInBytes "GetSizeInBytes" | *optional* /// | **Client provider unique identifier** | `int` | @ref PVRChannel::SetClientProviderUid "SetClientProviderUid" | @ref PVRTimer::GetClientProviderUid "GetClientProviderUid" | *optional* /// | **Provider name** | `std::string` | @ref PVRChannel::SetProviderName "SetProviderlName" | @ref PVRChannel::GetProviderName "GetProviderName" | *optional* + /// | **Parental rating age** | `unsigned int` | @ref PVRRecording::SetParentalRating "SetParentalRating" | @ref PVRRecording::GetParentalRating "GetParentalRating" | *optional* + /// | **Parental rating code** | `std::string` | @ref PVRRecording::SetParentalRatingCode "SetParentalRatingCode" | @ref PVRRecording::GetParentalRatingCode "GetParentalRatingCode" | *optional* + /// | **Parental rating icon** | `std::string` | @ref PVRRecording::SetParentalRatingIcon "SetParentalRatingIcon" | @ref PVRRecording::GetParentalRatingIcon "GetParentalRatingIcon" | *optional* + /// | **Parental rating source** | `std::string` | @ref PVRRecording::SetParentalRatingSource "SetParentalRatingSource" | @ref PVRRecording::GetParentalRatingSource "GetParentalRatingSource" | *optional* /// @addtogroup cpp_kodi_addon_pvr_Defs_Recording_PVRRecording ///@{ @@ -512,6 +516,55 @@ class PVRRecording : public DynamicCStructHdl return m_cStructure->strProviderName ? m_cStructure->strProviderName : ""; } + /// @brief **optional**\n + /// Age rating for the recording. + void SetParentalRating(unsigned int iParentalRating) + { + m_cStructure->iParentalRating = iParentalRating; + } + + /// @brief To get with @ref SetParentalRating changed values + unsigned int GetParentalRating() const { return m_cStructure->iParentalRating; } + + /// @brief **optional**\n + /// Parental rating code for this recording. + void SetParentalRatingCode(const std::string& ratingCode) + { + ReallocAndCopyString(&m_cStructure->strParentalRatingCode, ratingCode.c_str()); + } + + /// @brief To get with @ref SetParentalRatingCode changed values. + std::string GetParentalRatingCode() const + { + return m_cStructure->strParentalRatingCode ? m_cStructure->strParentalRatingCode : ""; + } + + /// @brief **optional**\n + /// Parental rating icon for this recording. + void SetParentalRatingIcon(const std::string& ratingIcon) + { + ReallocAndCopyString(&m_cStructure->strParentalRatingIcon, ratingIcon.c_str()); + } + + /// @brief To get with @ref SetParentalRatingIcon changed values. + std::string GetParentalRatingIcon() const + { + return m_cStructure->strParentalRatingIcon ? m_cStructure->strParentalRatingIcon : ""; + } + + /// @brief **optional**\n + /// Parental rating source for this recording. + void SetParentalRatingSource(const std::string& ratingSource) + { + ReallocAndCopyString(&m_cStructure->strParentalRatingSource, ratingSource.c_str()); + } + + /// @brief To get with @ref SetParentalRatingSource changed values. + std::string GetParentalRatingSource() const + { + return m_cStructure->strParentalRatingSource ? m_cStructure->strParentalRatingSource : ""; + } + static void AllocResources(const PVR_RECORDING* source, PVR_RECORDING* target) { target->strRecordingId = AllocAndCopyString(source->strRecordingId); @@ -527,6 +580,9 @@ class PVRRecording : public DynamicCStructHdl target->strFanartPath = AllocAndCopyString(source->strFanartPath); target->strFirstAired = AllocAndCopyString(source->strFirstAired); target->strProviderName = AllocAndCopyString(source->strProviderName); + target->strParentalRatingCode = AllocAndCopyString(source->strParentalRatingCode); + target->strParentalRatingIcon = AllocAndCopyString(source->strParentalRatingIcon); + target->strParentalRatingSource = AllocAndCopyString(source->strParentalRatingSource); } static void FreeResources(PVR_RECORDING* target) @@ -544,6 +600,9 @@ class PVRRecording : public DynamicCStructHdl FreeString(target->strFanartPath); FreeString(target->strFirstAired); FreeString(target->strProviderName); + FreeString(target->strParentalRatingCode); + FreeString(target->strParentalRatingIcon); + FreeString(target->strParentalRatingSource); } private: diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h index 8c57f33e2ab73..ae9d2a52fa1b4 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h @@ -139,6 +139,10 @@ extern "C" int64_t sizeInBytes; int iClientProviderUid; const char* strProviderName; + unsigned int iParentalRating; + const char* strParentalRatingCode; + const char* strParentalRatingIcon; + const char* strParentalRatingSource; } PVR_RECORDING; #ifdef __cplusplus diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 0b5a83af5f66e..e7490b481f0f8 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -122,7 +122,10 @@ class CAddonRecording : public PVR_RECORDING m_thumbnailPath(recording.ClientThumbnailPath()), m_fanartPath(recording.ClientFanartPath()), m_firstAired(recording.FirstAired().IsValid() ? recording.FirstAired().GetAsW3CDate() : ""), - m_providerName(recording.ProviderName()) + m_providerName(recording.ProviderName()), + m_parentalRatingCode(""), //! @todo + m_parentalRatingIcon(""), //! @todo + m_parentalRatingSource("") //! @todo { time_t recTime; recording.RecordingTimeAsUTC().GetAsTime(recTime); @@ -161,6 +164,9 @@ class CAddonRecording : public PVR_RECORDING sizeInBytes = recording.GetSizeInBytes(); strProviderName = m_providerName.c_str(); iClientProviderUid = recording.ClientProviderUniqueId(); + strParentalRatingCode = m_parentalRatingCode.c_str(); + strParentalRatingIcon = m_parentalRatingIcon.c_str(); + strParentalRatingSource = m_parentalRatingSource.c_str(); } virtual ~CAddonRecording() = default; @@ -178,6 +184,9 @@ class CAddonRecording : public PVR_RECORDING const std::string m_fanartPath; const std::string m_firstAired; const std::string m_providerName; + const std::string m_parentalRatingCode; + const std::string m_parentalRatingIcon; + const std::string m_parentalRatingSource; }; class CAddonTimer : public PVR_TIMER From 9b9606366fee0f35393f1b5fbc732a677ebf5310 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 15 Aug 2024 22:29:15 +0200 Subject: [PATCH 378/651] [PVR] PVR Addon-API: Add PVR_RECORDING::iEpisodePartNumber. --- .../include/kodi/addon-instance/pvr/Recordings.h | 12 ++++++++++++ .../kodi/c-api/addon-instance/pvr/pvr_recordings.h | 1 + xbmc/pvr/addons/PVRClient.cpp | 1 + 3 files changed, 14 insertions(+) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h index 0e92263ec29f4..5a97cc8eddde9 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h @@ -45,6 +45,7 @@ class PVRRecording : public DynamicCStructHdl { m_cStructure->iSeriesNumber = PVR_RECORDING_INVALID_SERIES_EPISODE; m_cStructure->iEpisodeNumber = PVR_RECORDING_INVALID_SERIES_EPISODE; + m_cStructure->iEpisodePartNumber = PVR_RECORDING_INVALID_SERIES_EPISODE; m_cStructure->iDuration = PVR_RECORDING_VALUE_NOT_AVAILABLE; m_cStructure->iPriority = PVR_RECORDING_VALUE_NOT_AVAILABLE; m_cStructure->iLifetime = PVR_RECORDING_VALUE_NOT_AVAILABLE; @@ -70,6 +71,7 @@ class PVRRecording : public DynamicCStructHdl /// | **Episode name** | `std::string` | @ref PVRRecording::SetEpisodeName "SetEpisodeName" | @ref PVRRecording::GetEpisodeName "GetEpisodeName" | *optional* /// | **Series number** | `int` | @ref PVRRecording::SetSeriesNumber "SetSeriesNumber" | @ref PVRRecording::GetSeriesNumber "GetSeriesNumber" | *optional* /// | **Episode number** | `int` | @ref PVRRecording::SetEpisodeNumber "SetEpisodeNumber" | @ref PVRRecording::GetEpisodeNumber "GetEpisodeNumber" | *optional* + /// | **Episode part number** | `int` | @ref PVRRecording::SetEpisodePartNumber "SetEpisodePartNumber" | @ref PVRRecording::GetEpisodePartNumber "GetEpisodePartNumber" | *optional* /// | **Year** | `int` | @ref PVRRecording::SetYear "SetYear" | @ref PVRRecording::GetYear "GetYear" | *optional* /// | **Directory** | `std::string` | @ref PVRRecording::SetDirectory "SetDirectory" | @ref PVRRecording::GetDirectory "GetDirectory" | *optional* /// | **Plot outline** | `std::string` | @ref PVRRecording::SetPlotOutline "SetPlotOutline" | @ref PVRRecording::GetPlotOutline "GetPlotOutline" | *optional* @@ -158,6 +160,16 @@ class PVRRecording : public DynamicCStructHdl /// @brief To get with @ref SetEpisodeNumber changed values. int GetEpisodeNumber() const { return m_cStructure->iEpisodeNumber; } + /// @brief **optional**\n + /// Episode part number. + void SetEpisodePartNumber(int episodePartNumber) + { + m_cStructure->iEpisodePartNumber = episodePartNumber; + } + + /// @brief To get with @ref SetEpisodePartNumber changed values. + int GetEpisodePartNumber() const { return m_cStructure->iEpisodePartNumber; } + /// @brief **optional**\n /// Year of first release (use to identify a specific movie re-make) / first /// airing for TV shows. diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h index ae9d2a52fa1b4..d43c40724fef8 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h @@ -113,6 +113,7 @@ extern "C" const char* strEpisodeName; int iSeriesNumber; int iEpisodeNumber; + int iEpisodePartNumber; int iYear; const char* strDirectory; const char* strPlotOutline; diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index e7490b481f0f8..c3de4714de0d6 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -135,6 +135,7 @@ class CAddonRecording : public PVR_RECORDING strEpisodeName = m_episodeName.c_str(); iSeriesNumber = recording.m_iSeason; iEpisodeNumber = recording.m_iEpisode; + iEpisodePartNumber = PVR_RECORDING_INVALID_SERIES_EPISODE; //! @todo iYear = recording.GetYear(); strDirectory = m_directory.c_str(); strPlotOutline = m_plotOutline.c_str(); From 684a5a7cd3408a2f891d433e21cd39e301ec777c Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:10:51 +0200 Subject: [PATCH 379/651] [addons][PVR] PVR Addon-API: Add parameter 'source' to function GetChannelStreamProperties. Add function StreamClosed. Add stream property PVR_STREAM_PROPERTY_LIVEPLAYBACKASEPG. --- .../include/kodi/addon-instance/PVR.h | 29 ++++++++++++++++-- .../include/kodi/addon-instance/pvr/General.h | 2 ++ .../include/kodi/c-api/addon-instance/pvr.h | 2 ++ .../c-api/addon-instance/pvr/pvr_general.h | 30 +++++++++++++++++++ xbmc/pvr/addons/PVRClient.cpp | 4 +-- 5 files changed, 63 insertions(+), 4 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h index 2f0a1bea3bc97..83ca8b6ccb859 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h @@ -291,7 +291,8 @@ namespace addon /// PVR_ERROR GetProviders(std::vector& providers) override; /// PVR_ERROR GetChannelsAmount(int& amount) override; /// PVR_ERROR GetChannels(bool radio, std::vector& channels) override; -/// PVR_ERROR GetChannelStreamProperties(const kodi::addon::PVRChannel& channel, +/// PVR_ERROR GetChannelStreamProperties(const kodi::addon::PVRChannel& channel, +/// PVR_SOURCE source, /// std::vector& properties) override; /// /// private: @@ -351,6 +352,7 @@ namespace addon /// } /// /// PVR_ERROR CMyPVRClient::GetChannelStreamProperties(const kodi::addon::PVRChannel& channel, +/// PVR_SOURCE source, /// std::vector& properties) /// { /// if (channel.GetUniqueId() == 123) @@ -968,6 +970,9 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance /// @brief Get the stream properties for a channel from the backend. /// /// @param[in] channel The channel to get the stream properties for. + /// @param[in] source PVR_SOURCE_EPG_AS_LIVE if this call resulted from + /// PVR_STREAM_PROPERTY_EPGPLAYBACKASLIVE being set from GetEPGTagStreamProperties(), DEFAULT + /// otherwise /// @param[out] properties the properties required to play the stream. /// @return @ref PVR_ERROR_NO_ERROR if the stream is available. /// @@ -988,6 +993,7 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance /// ~~~~~~~~~~~~~{.cpp} /// ... /// PVR_ERROR CMyPVRInstance::GetChannelStreamProperties(const kodi::addon::PVRChannel& channel, + /// PVR_SOURCE source, /// std::vector& properties) /// { /// ... @@ -1002,6 +1008,7 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance /// virtual PVR_ERROR GetChannelStreamProperties( const kodi::addon::PVRChannel& channel, + PVR_SOURCE source, std::vector& properties) { return PVR_ERROR_NOT_IMPLEMENTED; @@ -2415,6 +2422,17 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance } //---------------------------------------------------------------------------- + //============================================================================ + /// @brief The currently playing stream has been closed + /// + /// @remarks Called if both @ref PVRCapabilities::SetHandlesInputStream() or + /// @ref PVRCapabilities::SetHandlesDemuxing() are set to false. Allows add-ons + /// to do any cleanup required prior to a stream being opened. + /// @return @ref PVR_ERROR_NO_ERROR if the properties have been fetched successfully. + /// + virtual PVR_ERROR StreamClosed() { return PVR_ERROR_NOT_IMPLEMENTED; } + //---------------------------------------------------------------------------- + //============================================================================ /// @brief Read the next packet from the demultiplexer, if there is one. /// @@ -2804,6 +2822,7 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance instance->pvr->toAddon->SeekLiveStream = ADDON_SeekLiveStream; instance->pvr->toAddon->LengthLiveStream = ADDON_LengthLiveStream; instance->pvr->toAddon->GetStreamProperties = ADDON_GetStreamProperties; + instance->pvr->toAddon->StreamClosed = ADDON_StreamClosed; instance->pvr->toAddon->GetStreamReadChunkSize = ADDON_GetStreamReadChunkSize; instance->pvr->toAddon->IsRealTimeStream = ADDON_IsRealTimeStream; //--==----==----==----==----==----==----==----==----==----==----==----==----== @@ -2927,13 +2946,14 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance inline static PVR_ERROR ADDON_GetChannelStreamProperties(const AddonInstance_PVR* instance, const PVR_CHANNEL* channel, + PVR_SOURCE source, PVR_NAMED_VALUE*** properties, unsigned int* propertiesCount) { *propertiesCount = 0; std::vector propertiesList; PVR_ERROR error = static_cast(instance->toAddon->addonInstance) - ->GetChannelStreamProperties(channel, propertiesList); + ->GetChannelStreamProperties(channel, source, propertiesList); if (error == PVR_ERROR_NO_ERROR && !propertiesList.empty()) { *properties = AllocAndCopyPointerArray(propertiesList, @@ -3438,6 +3458,11 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance return err; } + inline static PVR_ERROR ADDON_StreamClosed(const AddonInstance_PVR* instance) + { + return static_cast(instance->toAddon->addonInstance)->StreamClosed(); + } + inline static PVR_ERROR ADDON_GetStreamReadChunkSize(const AddonInstance_PVR* instance, int* chunksize) { diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h index 8067316df1e05..c2860829a4c35 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h @@ -497,6 +497,7 @@ class PVRCapabilities : public DynamicCStructHdl& properties) /// { /// ... @@ -513,6 +514,7 @@ class PVRCapabilities : public DynamicCStructHdl& properties) /// { /// ... diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h index d32df12c197c0..e123ff414c693 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h @@ -163,6 +163,7 @@ extern "C" enum PVR_ERROR(__cdecl* GetChannels)(const struct AddonInstance_PVR*, PVR_HANDLE, bool); enum PVR_ERROR(__cdecl* GetChannelStreamProperties)(const struct AddonInstance_PVR*, const struct PVR_CHANNEL*, + enum PVR_SOURCE, struct PVR_NAMED_VALUE***, unsigned int*); enum PVR_ERROR(__cdecl* GetSignalStatus)(const struct AddonInstance_PVR*, @@ -305,6 +306,7 @@ extern "C" // Stream demux interface functions enum PVR_ERROR(__cdecl* GetStreamProperties)(const struct AddonInstance_PVR*, struct PVR_STREAM_PROPERTIES*); + enum PVR_ERROR(__cdecl* StreamClosed)(const struct AddonInstance_PVR*); struct DEMUX_PACKET*(__cdecl* DemuxRead)(const struct AddonInstance_PVR*); void(__cdecl* DemuxReset)(const struct AddonInstance_PVR*); void(__cdecl* DemuxAbort)(const struct AddonInstance_PVR*); diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h index a0dff4242836a..326c6f02cb341 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h @@ -112,6 +112,27 @@ extern "C" ///@} //---------------------------------------------------------------------------- + //============================================================================ + /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVR_SOURCE enum PVR_SOURCE + /// @ingroup cpp_kodi_addon_pvr_Defs_General + /// @brief **PVR add-on playback source**\n + /// Used in call to GetChannelStreamProperties() to indicate where the playback + /// call initiated. + /// + /// - @ref kodi::addon::CInstancePVRClient::GetChannelStreamProperties() + /// + ///@{ + typedef enum PVR_SOURCE + { + /// @brief __0__ : Regular live playback + DEFAULT = 0, + + /// @brief __1__ : From EPG, but playing back as live + PVR_SOURCE_EPG_AS_LIVE = 1, + } PVR_SOURCE; + ///@} + //---------------------------------------------------------------------------- + //============================================================================ /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVR_STREAM_PROPERTY definition PVR_STREAM_PROPERTY /// @ingroup cpp_kodi_addon_pvr_Defs_General_Inputstream @@ -134,6 +155,7 @@ extern "C" /// ... /// /// PVR_ERROR CMyPVRInstance::GetChannelStreamProperties(const kodi::addon::PVRChannel& channel, + /// PVR_SOURCE source, /// std::vector& properties) /// { /// ... @@ -250,6 +272,14 @@ extern "C" /// #define PVR_STREAM_PROPERTY_EPGPLAYBACKASLIVE "epgplaybackaslive" + /// @brief "true" to denote that if the stream is from a channel but should + /// be played as an EPG tag. + /// + /// It should be played as an epg/catchup stream. Otherwise if it's a channel it will + /// played as live stream. + /// +#define PVR_STREAM_PROPERTY_LIVEPLAYBACKASEPG "liveplaybackasepg" + /// @brief Special value for @ref PVR_STREAM_PROPERTY_INPUTSTREAM to use /// ffmpeg to directly play a stream URL. #define PVR_STREAM_PROPERTY_VALUE_INPUTSTREAMFFMPEG STREAM_PROPERTY_VALUE_INPUTSTREAMFFMPEG diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index c3de4714de0d6..c8709b9232774 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -1530,8 +1530,8 @@ PVR_ERROR CPVRClient::GetChannelStreamProperties(const std::shared_ptrtoAddon->GetChannelStreamProperties(addon, &addonChannel, &property_array, &size)}; + const PVR_ERROR error{addon->toAddon->GetChannelStreamProperties( + addon, &addonChannel, PVR_SOURCE::DEFAULT, &property_array, &size)}; if (error == PVR_ERROR_NO_ERROR) WriteStreamProperties(property_array, size, props); From e31d9b1d1652e6a165775dea901503ece44483fb Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:24:20 +0200 Subject: [PATCH 380/651] [addons] Bump PVR Addon API version to 9.0.0, including min version. --- xbmc/addons/kodi-dev-kit/include/kodi/versions.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h index 4b8a49e9b7476..622a6920e0115 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h @@ -130,8 +130,8 @@ #define ADDON_INSTANCE_VERSION_PERIPHERAL_DEPENDS "addon-instance/Peripheral.h" \ "addon-instance/PeripheralUtils.h" -#define ADDON_INSTANCE_VERSION_PVR "8.4.0" -#define ADDON_INSTANCE_VERSION_PVR_MIN "8.4.0" +#define ADDON_INSTANCE_VERSION_PVR "9.0.0" +#define ADDON_INSTANCE_VERSION_PVR_MIN "9.0.0" #define ADDON_INSTANCE_VERSION_PVR_XML_ID "kodi.binary.instance.pvr" #define ADDON_INSTANCE_VERSION_PVR_DEPENDS "c-api/addon-instance/pvr.h" \ "c-api/addon-instance/pvr/pvr_channel_groups.h" \ From 49926b07819f6d638fdec6b99fdc303975090aaa Mon Sep 17 00:00:00 2001 From: fuzzard Date: Fri, 16 Aug 2024 15:19:42 +1000 Subject: [PATCH 381/651] [cmake][addons] Dont quote variable passed to externalproject_add Alwin partially fixed this in https://github.com/xbmc/xbmc/commit/2c1ef3dce244e4918b07f452d1c28a3b3b6d71a3 Reason he probably didnt see this was most of our addons use a sha256 file. game.libretro.mrboom and game.libretro.2048 do not, and therefore fail to build with errors such as: CMake Error at /usr/share/cmake-3.30/Modules/ExternalProject/shared_internal_commands.cmake:1127 (message): At least one entry of URL is a path (invalid in a list) Call Stack (most recent call first): /usr/share/cmake-3.30/Modules/ExternalProject.cmake:3035 (_ep_add_download_command) /home/xxx/repos/xbmc/cmake/scripts/common/HandleDepends.cmake:272 (externalproject_add) CMakeLists.txt:455 (add_addon_depends) -- Configuring incomplete, errors occurred! --- cmake/scripts/common/HandleDepends.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/scripts/common/HandleDepends.cmake b/cmake/scripts/common/HandleDepends.cmake index dc022bab64108..de994b80e2e91 100644 --- a/cmake/scripts/common/HandleDepends.cmake +++ b/cmake/scripts/common/HandleDepends.cmake @@ -271,7 +271,7 @@ function(add_addon_depends addon searchpath) externalproject_add(${id} URL ${url} - "${URL_HASH_COMMAND}" + ${URL_HASH_COMMAND} DOWNLOAD_DIR ${DOWNLOAD_DIR} CONFIGURE_COMMAND ${CONFIGURE_COMMAND} ${EXTERNALPROJECT_SETUP}) From 0c1a5b4adcec0aa02d448b3fc978b26c0cb157dd Mon Sep 17 00:00:00 2001 From: fritsch Date: Thu, 1 Oct 2020 21:32:38 +0200 Subject: [PATCH 382/651] ActiveAE: Include LFE mixing possibility We have user without AVR running fullrange speakers. They want the bass of a 5.1 or 7.1 layout being mixed into FL and FR. This new setting allows to do so. --- .../resources/strings.po | 33 ++++++++++++++++++- system/settings/settings.xml | 12 +++++++ .../AudioEngine/Engines/ActiveAE/ActiveAE.cpp | 18 +++++----- .../AudioEngine/Engines/ActiveAE/ActiveAE.h | 1 + .../Engines/ActiveAE/ActiveAEBuffer.cpp | 19 ++++++----- .../Engines/ActiveAE/ActiveAEBuffer.h | 9 +++-- .../ActiveAE/ActiveAEResampleFFMPEG.cpp | 14 ++++++-- .../Engines/ActiveAE/ActiveAEResampleFFMPEG.h | 11 +++++-- .../Engines/ActiveAE/ActiveAESettings.cpp | 1 + .../Engines/ActiveAE/ActiveAESink.cpp | 4 +-- .../Engines/ActiveAE/ActiveAEStream.cpp | 20 +++++------ .../Engines/ActiveAE/ActiveAEStream.h | 8 +++-- .../cores/AudioEngine/Interfaces/AEResample.h | 11 +++++-- xbmc/cores/paplayer/VideoPlayerCodec.cpp | 9 ++--- xbmc/settings/Settings.h | 1 + 15 files changed, 123 insertions(+), 48 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index b12f00ede95ae..abc6949849d8e 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -17918,8 +17918,39 @@ msgctxt "#34113" msgid "To keep certain AVRs powered we send an inaudible random noise signal. You can disable this setting if you are using headphone or analog output." msgstr "" +#. Indicates if Audio Engine should include lfe when downmixing +#: system/settings/settings.xml +msgctxt "#34114" +msgid "Include LFE when downmixing" +msgstr "" + +#. Description of setting with label #34114 "Include LFE when downmixing" +#: system/settings/settings.xml +msgctxt "#34115" +msgid "If enabled, this setting will include lfe channel into the mixing when there is no dedicated LFE output channel available. This only makes sense for full range speakers." +msgstr "" + +#. Description of setting with label #34114 "Include LFE when downmixing" +#: system/settings/settings.xml +msgctxt "#34116" +msgid "Off" +msgstr "" + +#. Description of setting with label #34114 "Include LFE when downmixing" +#: system/settings/settings.xml +msgctxt "#34117" +msgid "50%" +msgstr "" + +#. Description of setting with label #34114 "Include LFE when downmixing" +#: system/settings/settings.xml +msgctxt "#34118" +msgid "100%" +msgstr "" + + #empty strings from id 34114 to 34119 -#34114-34119 reserved for future use +#34119 reserved for future use #: system/settings/settings.xml msgctxt "#34120" diff --git a/system/settings/settings.xml b/system/settings/settings.xml index e5bee132ab8b0..6e323fb7ffe39 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -3259,6 +3259,18 @@ true + + 2 + 0 + + + + + + + + + diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp index e7ae140becbfd..d68de6c6f6d55 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp @@ -1369,7 +1369,8 @@ void CActiveAE::Configure(AEAudioFormat *desiredFmt) (*it)->m_inputBuffers->m_format, outputFormat, m_settings.resampleQuality); (*it)->m_processingBuffers->ForceResampler((*it)->m_forceResampler); - (*it)->m_processingBuffers->Create(MAX_CACHE_LEVEL*1000, false, m_settings.stereoupmix, m_settings.normalizelevels); + (*it)->m_processingBuffers->Create(MAX_CACHE_LEVEL * 1000, false, m_settings.stereoupmix, + m_settings.normalizelevels, m_settings.mixSubLevel); } if (m_mode == MODE_TRANSCODE || m_streams.size() > 1) (*it)->m_processingBuffers->FillBuffer(); @@ -1641,7 +1642,9 @@ void CActiveAE::ChangeResamplers() std::list::iterator it; for(it=m_streams.begin(); it!=m_streams.end(); ++it) { - (*it)->m_processingBuffers->ConfigureResampler(m_settings.normalizelevels, m_settings.stereoupmix, m_settings.resampleQuality); + (*it)->m_processingBuffers->ConfigureResampler( + m_settings.normalizelevels, m_settings.stereoupmix, m_settings.resampleQuality, + m_settings.mixSubLevel); } } @@ -2666,6 +2669,7 @@ void CActiveAE::LoadSettings() m_settings.atempoThreshold = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_ATEMPOTHRESHOLD) / 100.0; m_settings.streamNoise = settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_STREAMNOISE); m_settings.silenceTimeoutMinutes = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_STREAMSILENCE); + m_settings.mixSubLevel = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_MIXSUBLEVEL) / 100.0; } void CActiveAE::ValidateOutputDevices(bool saveChanges) @@ -3310,13 +3314,9 @@ bool CActiveAE::ResampleSound(CActiveAESound *sound) std::unique_ptr resampler = CAEResampleFactory::Create(AERESAMPLEFACTORY_QUICK_RESAMPLE); - resampler->Init(dst_config, orig_config, - false, - true, - M_SQRT1_2, - outChannels.Count() > 0 ? &outChannels : nullptr, - m_settings.resampleQuality, - false); + resampler->Init(dst_config, orig_config, false, true, M_SQRT1_2, + outChannels.Count() > 0 ? &outChannels : nullptr, m_settings.resampleQuality, + false, 0.0f); dst_samples = resampler->CalcDstSampleCount(sound->GetSound(true)->nb_samples, m_internalFormat.m_sampleRate, diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h index 7c43d2037ce4e..cf74ee52b0151 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h @@ -63,6 +63,7 @@ struct AudioSettings double atempoThreshold; bool streamNoise; int silenceTimeoutMinutes; + float mixSubLevel; }; class CActiveAEControlProtocol : public Protocol diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp index 8e4d957cea6cd..5ab47edda4aa2 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp @@ -144,12 +144,14 @@ CActiveAEBufferPoolResample::~CActiveAEBufferPoolResample() Flush(); } -bool CActiveAEBufferPoolResample::Create(unsigned int totaltime, bool remap, bool upmix, bool normalize) +bool CActiveAEBufferPoolResample::Create( + unsigned int totaltime, bool remap, bool upmix, bool normalize, float sublevel) { CActiveAEBufferPool::Create(totaltime); m_remap = remap; m_stereoUpmix = upmix; + m_mixSubLevel = sublevel; m_normalize = true; if ((m_format.m_channelLayout.Count() < m_inputFormat.m_channelLayout.Count() && !normalize)) @@ -184,13 +186,9 @@ void CActiveAEBufferPoolResample::ChangeResampler() srcConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_inputFormat.m_dataFormat); srcConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_inputFormat.m_dataFormat); - m_resampler->Init(dstConfig, srcConfig, - m_stereoUpmix, - m_normalize, - m_centerMixLevel, - m_remap ? &m_format.m_channelLayout : nullptr, - m_resampleQuality, - m_forceResampler); + m_resampler->Init(dstConfig, srcConfig, m_stereoUpmix, m_normalize, m_centerMixLevel, + m_remap ? &m_format.m_channelLayout : nullptr, m_resampleQuality, + m_forceResampler, m_mixSubLevel); m_changeResampler = false; } @@ -350,7 +348,10 @@ bool CActiveAEBufferPoolResample::ResampleBuffers(int64_t timestamp) return busy; } -void CActiveAEBufferPoolResample::ConfigureResampler(bool normalizelevels, bool stereoupmix, AEQuality quality) +void CActiveAEBufferPoolResample::ConfigureResampler(bool normalizelevels, + bool stereoupmix, + AEQuality quality, + float sublevel) { bool normalize = true; if ((m_format.m_channelLayout.Count() < m_inputFormat.m_channelLayout.Count()) && !normalizelevels) diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h index 70e51bbd6f984..9feff2c558153 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h @@ -78,9 +78,13 @@ class CActiveAEBufferPoolResample : public CActiveAEBufferPool CActiveAEBufferPoolResample(const AEAudioFormat& inputFormat, const AEAudioFormat& outputFormat, AEQuality quality); ~CActiveAEBufferPoolResample() override; using CActiveAEBufferPool::Create; - bool Create(unsigned int totaltime, bool remap, bool upmix, bool normalize = true); + bool Create( + unsigned int totaltime, bool remap, bool upmix, bool normalize = true, float sublevel = 0.0); bool ResampleBuffers(int64_t timestamp = 0); - void ConfigureResampler(bool normalizelevels, bool stereoupmix, AEQuality quality); + void ConfigureResampler(bool normalizelevels, + bool stereoupmix, + AEQuality quality, + float sublevel); float GetDelay(); void Flush(); void SetDrain(bool drain); @@ -107,6 +111,7 @@ class CActiveAEBufferPoolResample : public CActiveAEBufferPool double m_centerMixLevel = M_SQRT1_2; bool m_fillPackets = false; bool m_normalize = true; + float m_mixSubLevel = 0.0f; bool m_changeResampler = false; bool m_forceResampler = false; AEQuality m_resampleQuality; diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp index e897cbd3ea801..70e946a1160ef 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp @@ -29,8 +29,15 @@ CActiveAEResampleFFMPEG::~CActiveAEResampleFFMPEG() swr_free(&m_pContext); } -bool CActiveAEResampleFFMPEG::Init(SampleConfig dstConfig, SampleConfig srcConfig, bool upmix, bool normalize, double centerMix, - CAEChannelInfo *remapLayout, AEQuality quality, bool force_resample) +bool CActiveAEResampleFFMPEG::Init(SampleConfig dstConfig, + SampleConfig srcConfig, + bool upmix, + bool normalize, + double centerMix, + CAEChannelInfo* remapLayout, + AEQuality quality, + bool force_resample, + float sublevel) { m_dst_chan_layout = dstConfig.channel_layout; m_dst_channels = dstConfig.channels; @@ -78,6 +85,9 @@ bool CActiveAEResampleFFMPEG::Init(SampleConfig dstConfig, SampleConfig srcConfi return false; } + if (sublevel > 0.0f) + av_opt_set_double(m_pContext, "lfe_mix_level", static_cast(sublevel), 0); + if(quality == AE_QUALITY_HIGH) { av_opt_set_double(m_pContext, "cutoff", 1.0, 0); diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h index 5fbce373ad15b..b6a9a033760c2 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h @@ -27,8 +27,15 @@ class CActiveAEResampleFFMPEG : public IAEResample const char *GetName() override { return "ActiveAEResampleFFMPEG"; } CActiveAEResampleFFMPEG(); ~CActiveAEResampleFFMPEG() override; - bool Init(SampleConfig dstConfig, SampleConfig srcConfig, bool upmix, bool normalize, double centerMix, - CAEChannelInfo *remapLayout, AEQuality quality, bool force_resample) override; + bool Init(SampleConfig dstConfig, + SampleConfig srcConfig, + bool upmix, + bool normalize, + double centerMix, + CAEChannelInfo* remapLayout, + AEQuality quality, + bool force_resample, + float sublevel) override; int Resample(uint8_t **dst_buffer, int dst_samples, uint8_t **src_buffer, int src_samples, double ratio) override; int64_t GetDelay(int64_t base) override; int GetBufferedSamples() override; diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.cpp index 5f9a1fbc75cca..b7b20ecf3fd15 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.cpp @@ -52,6 +52,7 @@ CActiveAESettings::CActiveAESettings(CActiveAE &ae) : m_audioEngine(ae) settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE); settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_STREAMSILENCE); settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_STREAMNOISE); + settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_MIXSUBLEVEL); settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_MAINTAINORIGINALVOLUME); settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_DTSHDCOREFALLBACK); settings->GetSettingsManager()->RegisterCallback(this, settingSet); diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp index 700abfcee57c9..f0cce1885b641 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp @@ -1192,8 +1192,8 @@ void CActiveAESink::GenerateNoise() srcConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_sinkFormat.m_dataFormat); srcConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_sinkFormat.m_dataFormat); - resampler->Init(dstConfig, srcConfig, - false, false, M_SQRT1_2, nullptr, AE_QUALITY_UNKNOWN, false); + resampler->Init(dstConfig, srcConfig, false, false, M_SQRT1_2, nullptr, AE_QUALITY_UNKNOWN, false, + 0.0); resampler->Resample(m_sampleOfSilence.pkt->data, m_sampleOfSilence.pkt->max_nb_samples, (uint8_t**)&noise, m_sampleOfSilence.pkt->max_nb_samples, 1.0); diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp index bdf65d60ae1bf..bf00b58840273 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp @@ -143,13 +143,9 @@ void CActiveAEStream::InitRemapper() srcConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_format.m_dataFormat); srcConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_format.m_dataFormat); - m_remapper->Init(dstConfig, srcConfig, - false, - false, - M_SQRT1_2, - &remapLayout, + m_remapper->Init(dstConfig, srcConfig, false, false, M_SQRT1_2, &remapLayout, AE_QUALITY_LOW, // not used for remapping - false); + false, 0.0f); // extra sound packet, we can't resample to the same buffer m_remapBuffer = @@ -602,9 +598,10 @@ bool CActiveAEStreamBuffers::HasInputLevel(int level) return false; } -bool CActiveAEStreamBuffers::Create(unsigned int totaltime, bool remap, bool upmix, bool normalize) +bool CActiveAEStreamBuffers::Create( + unsigned int totaltime, bool remap, bool upmix, bool normalize, float sublevel) { - if (!m_resampleBuffers->Create(totaltime, remap, upmix, normalize)) + if (!m_resampleBuffers->Create(totaltime, remap, upmix, normalize, sublevel)) return false; if (!m_atempoBuffers->Create(totaltime)) @@ -654,9 +651,12 @@ bool CActiveAEStreamBuffers::ProcessBuffers() return busy; } -void CActiveAEStreamBuffers::ConfigureResampler(bool normalizelevels, bool stereoupmix, AEQuality quality) +void CActiveAEStreamBuffers::ConfigureResampler(bool normalizelevels, + bool stereoupmix, + AEQuality quality, + float sublevel) { - m_resampleBuffers->ConfigureResampler(normalizelevels, stereoupmix, quality); + m_resampleBuffers->ConfigureResampler(normalizelevels, stereoupmix, quality, sublevel); } float CActiveAEStreamBuffers::GetDelay() diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h index 56b9e51f369ff..e29109fc26192 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h @@ -96,10 +96,14 @@ class CActiveAEStreamBuffers public: CActiveAEStreamBuffers(const AEAudioFormat& inputFormat, const AEAudioFormat& outputFormat, AEQuality quality); virtual ~CActiveAEStreamBuffers(); - bool Create(unsigned int totaltime, bool remap, bool upmix, bool normalize = true); + bool Create( + unsigned int totaltime, bool remap, bool upmix, bool normalize = true, float sublevel = 0.0f); void SetExtraData(int profile, enum AVMatrixEncoding matrix_encoding, enum AVAudioServiceType audio_service_type); bool ProcessBuffers(); - void ConfigureResampler(bool normalizelevels, bool stereoupmix, AEQuality quality); + void ConfigureResampler(bool normalizelevels, + bool stereoupmix, + AEQuality quality, + float sublevel); bool HasInputLevel(int level); float GetDelay(); void Flush(); diff --git a/xbmc/cores/AudioEngine/Interfaces/AEResample.h b/xbmc/cores/AudioEngine/Interfaces/AEResample.h index 8b27b32b8629f..620ce56195982 100644 --- a/xbmc/cores/AudioEngine/Interfaces/AEResample.h +++ b/xbmc/cores/AudioEngine/Interfaces/AEResample.h @@ -20,8 +20,15 @@ class IAEResample virtual const char *GetName() = 0; IAEResample() = default; virtual ~IAEResample() = default; - virtual bool Init(SampleConfig dstConfig, SampleConfig srcConfig, bool upmix, bool normalize, double centerMix, - CAEChannelInfo *remapLayout, AEQuality quality, bool force_resample) = 0; + virtual bool Init(SampleConfig dstConfig, + SampleConfig srcConfig, + bool upmix, + bool normalize, + double centerMix, + CAEChannelInfo* remapLayout, + AEQuality quality, + bool force_resample, + float sublevel) = 0; virtual int Resample(uint8_t **dst_buffer, int dst_samples, uint8_t **src_buffer, int src_samples, double ratio) = 0; virtual int64_t GetDelay(int64_t base) = 0; virtual int GetBufferedSamples() = 0; diff --git a/xbmc/cores/paplayer/VideoPlayerCodec.cpp b/xbmc/cores/paplayer/VideoPlayerCodec.cpp index 0a2468f8ff9dc..13037229080b9 100644 --- a/xbmc/cores/paplayer/VideoPlayerCodec.cpp +++ b/xbmc/cores/paplayer/VideoPlayerCodec.cpp @@ -253,13 +253,8 @@ bool VideoPlayerCodec::Init(const CFileItem &file, unsigned int filecache) srcConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_srcFormat.m_dataFormat); srcConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_srcFormat.m_dataFormat); - m_pResampler->Init(dstConfig, srcConfig, - false, - false, - M_SQRT1_2, - NULL, - AE_QUALITY_UNKNOWN, - false); + m_pResampler->Init(dstConfig, srcConfig, false, false, M_SQRT1_2, NULL, AE_QUALITY_UNKNOWN, + false, 0.0f); m_planes = AE_IS_PLANAR(m_srcFormat.m_dataFormat) ? m_channels : 1; m_format = m_srcFormat; diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h index dfc87db9f286b..00ab9fb34fbb6 100644 --- a/xbmc/settings/Settings.h +++ b/xbmc/settings/Settings.h @@ -397,6 +397,7 @@ class CSettings : public CSettingsBase, public CSettingCreator, public CSettingC static constexpr auto SETTING_AUDIOOUTPUT_ATEMPOTHRESHOLD = "audiooutput.atempothreshold"; static constexpr auto SETTING_AUDIOOUTPUT_STREAMSILENCE = "audiooutput.streamsilence"; static constexpr auto SETTING_AUDIOOUTPUT_STREAMNOISE = "audiooutput.streamnoise"; + static constexpr auto SETTING_AUDIOOUTPUT_MIXSUBLEVEL = "audiooutput.mixsublevel"; static constexpr auto SETTING_AUDIOOUTPUT_GUISOUNDMODE = "audiooutput.guisoundmode"; static constexpr auto SETTING_AUDIOOUTPUT_GUISOUNDVOLUME = "audiooutput.guisoundvolume"; static constexpr auto SETTING_AUDIOOUTPUT_PASSTHROUGH = "audiooutput.passthrough"; From 0a03c6dec8fc59e82cd2aa8d8aa47b112e46109e Mon Sep 17 00:00:00 2001 From: sarbes Date: Fri, 16 Aug 2024 17:17:20 +0200 Subject: [PATCH 383/651] Fix dimming screensaver if rendering front to back --- xbmc/windows/GUIWindowScreensaverDim.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xbmc/windows/GUIWindowScreensaverDim.cpp b/xbmc/windows/GUIWindowScreensaverDim.cpp index 7bc360f18def0..2a3fdb147396f 100644 --- a/xbmc/windows/GUIWindowScreensaverDim.cpp +++ b/xbmc/windows/GUIWindowScreensaverDim.cpp @@ -68,6 +68,11 @@ void CGUIWindowScreensaverDim::Process(unsigned int currentTime, CDirtyRegionLis void CGUIWindowScreensaverDim::Render() { + RENDER_ORDER renderOrder = CServiceBroker::GetWinSystem()->GetGfxContext().GetRenderOrder(); + if (renderOrder == RENDER_ORDER_FRONT_TO_BACK) + return; + else if (renderOrder == RENDER_ORDER_BACK_TO_FRONT) + CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderOrder(RENDER_ORDER_ALL_BACK_TO_FRONT); // draw a translucent black quad - fading is handled by the window animation KODI::UTILS::COLOR::Color color = (static_cast(m_dimLevel * 2.55f) & 0xff) << 24; @@ -75,4 +80,5 @@ void CGUIWindowScreensaverDim::Render() CRect rect(0, 0, (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()); CGUITexture::DrawQuad(rect, color); CGUIDialog::Render(); + CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderOrder(renderOrder); } From 69e0bac59dd1a2c68c49c9e1fcc20a16896e15a8 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Fri, 16 Aug 2024 17:24:23 +0200 Subject: [PATCH 384/651] [cmake][webOS] Exclude python config files from packaging --- cmake/scripts/webos/Install.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/scripts/webos/Install.cmake b/cmake/scripts/webos/Install.cmake index 03dd7ce183399..6af5b209731ea 100644 --- a/cmake/scripts/webos/Install.cmake +++ b/cmake/scripts/webos/Install.cmake @@ -39,7 +39,8 @@ file(WRITE ${CMAKE_BINARY_DIR}/install.cmake " file(INSTALL ${APP_INSTALL_DIRS} DESTINATION ${APP_PACKAGE_DIR}) file(CREATE_LINK python${PYTHON_VERSION} python3 SYMBOLIC) file(INSTALL python3 DESTINATION ${APP_PACKAGE_DIR}/lib) - file(INSTALL ${DEPENDS_PATH}/lib/python${PYTHON_VERSION} DESTINATION ${APP_PACKAGE_DIR}/lib FOLLOW_SYMLINK_CHAIN) + file(INSTALL ${DEPENDS_PATH}/lib/python${PYTHON_VERSION} DESTINATION ${APP_PACKAGE_DIR}/lib FOLLOW_SYMLINK_CHAIN + PATTERN config-${PYTHON_VERSION}-arm-linux-gnueabi EXCLUDE) file(INSTALL ${APP_TOOLCHAIN_FILES} DESTINATION ${APP_PACKAGE_DIR}/lib FOLLOW_SYMLINK_CHAIN) file(STRINGS ${CMAKE_BINARY_DIR}/missing_libs.txt missing_libs) From 34e7dd3d166eaa8d0d57c4552129b05281e27142 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Fri, 16 Aug 2024 17:30:21 +0200 Subject: [PATCH 385/651] [cmake] Fix waylandpp scanner hint from pkg-config --- cmake/modules/buildtools/FindWaylandPPScanner.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/modules/buildtools/FindWaylandPPScanner.cmake b/cmake/modules/buildtools/FindWaylandPPScanner.cmake index ff80dbd3f4c27..52d9ba6899b7d 100644 --- a/cmake/modules/buildtools/FindWaylandPPScanner.cmake +++ b/cmake/modules/buildtools/FindWaylandPPScanner.cmake @@ -13,9 +13,10 @@ if(NOT wayland::waylandppscanner) if(PC_WAYLANDPP_SCANNER_FOUND) pkg_get_variable(PC_WAYLANDPP_SCANNER wayland-scanner++ wayland_scannerpp) + get_filename_component(PC_WAYLANDPP_SCANNER_DIR ${PC_WAYLANDPP_SCANNER} DIRECTORY) endif() - find_program(WAYLANDPP_SCANNER wayland-scanner++ HINTS ${PC_WAYLANDPP_SCANNER}) + find_program(WAYLANDPP_SCANNER wayland-scanner++ HINTS ${PC_WAYLANDPP_SCANNER_DIR}) if(WAYLANDPP_SCANNER) From fedc8c4975e940ed7216aeebed502fbb5b0f815a Mon Sep 17 00:00:00 2001 From: sarbes Date: Fri, 16 Aug 2024 18:22:20 +0200 Subject: [PATCH 386/651] Fix toggle button --- xbmc/guilib/GUIToggleButtonControl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/guilib/GUIToggleButtonControl.cpp b/xbmc/guilib/GUIToggleButtonControl.cpp index 650dc3df695a1..fff49051ae2e5 100644 --- a/xbmc/guilib/GUIToggleButtonControl.cpp +++ b/xbmc/guilib/GUIToggleButtonControl.cpp @@ -38,6 +38,7 @@ void CGUIToggleButtonControl::Process(unsigned int currentTime, CDirtyRegionList m_selectButton.SetPulseOnSelect(m_pulseOnSelect); ProcessToggle(currentTime); m_selectButton.DoProcess(currentTime, dirtyregions); + CGUIControl::Process(currentTime, dirtyregions); } else CGUIButtonControl::Process(currentTime, dirtyregions); From 172af71be18a4891c0817e9be8c3398ed69ebb7a Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Wed, 14 Aug 2024 08:50:31 +0100 Subject: [PATCH 387/651] [MUSIC] Fix ambiguous field in SQL if sorting by artist sortname --- xbmc/music/MusicDatabase.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index 33896766baf3b..99d5a76ffc735 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -13234,6 +13234,13 @@ int CMusicDatabase::GetOrderFilter(const std::string& type, } } + // Get the right tableview as if we are using strArtistSort the column name is ambiguous + std::string table; + if (StringUtils::StartsWithNoCase(type, "album")) + table = "albumview."; + else if (StringUtils::StartsWithNoCase(type, "song")) + table = "songview."; + // Convert field names into order by statement elements for (auto& name : orderfields) { @@ -13242,7 +13249,8 @@ int CMusicDatabase::GetOrderFilter(const std::string& type, if (StringUtils::EndsWith(name, "strArtists") || StringUtils::EndsWith(name, "strArtist")) { if (StringUtils::EndsWith(name, "strArtists")) - sortSQL = SortnameBuildSQL("artistsortname", sorting.sortAttributes, name, "strArtistSort"); + sortSQL = SortnameBuildSQL("artistsortname", sorting.sortAttributes, name, + table + "strArtistSort"); else sortSQL = SortnameBuildSQL("artistsortname", sorting.sortAttributes, name, "strSortName"); if (!sortSQL.empty()) From 830c9a54dc9f226f3478f0815b2e0a32fc5db95b Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 14 Aug 2024 23:48:24 +0200 Subject: [PATCH 388/651] [PVR] Misc cppcheck warning fixes. --- xbmc/pvr/addons/PVRClients.cpp | 5 ----- xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp | 2 -- xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp | 8 +++++++- xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp | 3 +-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/xbmc/pvr/addons/PVRClients.cpp b/xbmc/pvr/addons/PVRClients.cpp index 9308a62a66709..5863360f4815f 100644 --- a/xbmc/pvr/addons/PVRClients.cpp +++ b/xbmc/pvr/addons/PVRClients.cpp @@ -114,11 +114,6 @@ void CPVRClients::UpdateClients( else { client = std::make_shared(addon, instanceId, clientId); - if (!client) - { - CLog::LogF(LOGERROR, "Severe error, incorrect add-on type"); - continue; - } } // determine actual enabled state of instance diff --git a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp index d785219a78d61..fccedb6c68b99 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp @@ -840,8 +840,6 @@ void CGUIDialogPVRChannelManager::Update() for (const auto& member : groupMembers) { channelFile = std::make_shared(member); - if (!channelFile) - continue; const std::shared_ptr channel(channelFile->GetPVRChannelInfoTag()); channelFile->SetProperty(PROPERTY_CHANNEL_ENABLED, !channel->IsHidden()); diff --git a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp index 32bc313d67a12..be65ed1ef52bc 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp @@ -264,6 +264,9 @@ bool CGUIDialogPVRGroupManager::ActionButtonUngroupedChannels(const CGUIMessage& if (m_viewUngroupedChannels.HasControl(iControl)) // list/thumb control { + if (!m_selectedGroup) + return bReturn; + m_iSelectedUngroupedChannel = m_viewUngroupedChannels.GetSelectedItem(); if (m_selectedGroup->SupportsMemberAdd()) { @@ -304,6 +307,9 @@ bool CGUIDialogPVRGroupManager::ActionButtonGroupMembers(const CGUIMessage& mess if (m_viewGroupMembers.HasControl(iControl)) // list/thumb control { + if (!m_selectedGroup) + return bReturn; + m_iSelectedGroupMember = m_viewGroupMembers.GetSelectedItem(); if (m_selectedGroup->SupportsMemberRemove()) { @@ -311,7 +317,7 @@ bool CGUIDialogPVRGroupManager::ActionButtonGroupMembers(const CGUIMessage& mess if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK) { - if (m_selectedGroup && m_groupMembers->GetFileCount() > 0) + if (m_groupMembers->GetFileCount() > 0) { const auto itemChannel = m_groupMembers->Get(m_iSelectedGroupMember); ClearGroupThumbnails(*itemChannel); diff --git a/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp b/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp index cb3054371d1dd..95ca4923e66ce 100644 --- a/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp +++ b/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp @@ -246,8 +246,7 @@ void CPVRGUIChannelNavigator::SwitchToCurrentChannel() item = std::make_unique(m_currentChannel); } - if (item) - CServiceBroker::GetPVRManager().Get().SwitchToChannel(*item, false); + CServiceBroker::GetPVRManager().Get().SwitchToChannel(*item, false); } bool CPVRGUIChannelNavigator::IsPreview() const From b9aa46487356c000f7120b9186e6e6dfa70e46f7 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 11 Aug 2024 16:27:14 +0200 Subject: [PATCH 389/651] [PVR][video] Route marked watched and increment play count of PVR recordings through PVR recordings GUI component (to prepare future user interaction). --- xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp | 18 ++++++++++++++++++ xbmc/pvr/guilib/PVRGUIActionsRecordings.h | 15 +++++++++++++++ xbmc/utils/SaveFileStateJob.cpp | 8 +++++++- xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp | 6 +++--- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp b/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp index 316b8a896bc4c..c0172c67ef380 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp @@ -26,6 +26,7 @@ #include "pvr/dialogs/GUIDialogPVRRecordingInfo.h" #include "pvr/dialogs/GUIDialogPVRRecordingSettings.h" #include "pvr/recordings/PVRRecording.h" +#include "pvr/recordings/PVRRecordings.h" #include "settings/Settings.h" #include "threads/IRunnable.h" #include "utils/Variant.h" @@ -351,3 +352,20 @@ bool CPVRGUIActionsRecordings::ShowRecordingSettings( return pDlgInfo->IsConfirmed(); } + +bool CPVRGUIActionsRecordings::IncrementPlayCount(const CFileItem& item) const +{ + if (!item.IsPVRRecording()) + return false; + + return item.GetPVRRecordingInfoTag()->IncrementPlayCount(); +} + +bool CPVRGUIActionsRecordings::MarkWatched(const CFileItem& item, bool watched) const +{ + if (!item.IsPVRRecording()) + return false; + + return CServiceBroker::GetPVRManager().Recordings()->MarkWatched(item.GetPVRRecordingInfoTag(), + watched); +} diff --git a/xbmc/pvr/guilib/PVRGUIActionsRecordings.h b/xbmc/pvr/guilib/PVRGUIActionsRecordings.h index 795a2c73b3117..5a2cf9e546532 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsRecordings.h +++ b/xbmc/pvr/guilib/PVRGUIActionsRecordings.h @@ -73,6 +73,21 @@ class CPVRGUIActionsRecordings : public IPVRComponent */ bool UndeleteRecording(const CFileItem& item) const; + /*! + * @brief Increment the play count of a recording. + * @param item containing a recording for which the play count shall be incremented. + * @return true, if the recording's play count was incremented successfully, false otherwise. + */ + bool IncrementPlayCount(const CFileItem& item) const; + + /*! + * @brief Mark a recording watched or unwatched. + * @param item containing a recording to be marked watched or unwatched. + * @param watched Whether to mark the recording watched or unwatched. + * @return true, if the recording's watched state was changed successfully, false otherwise. + */ + bool MarkWatched(const CFileItem& item, bool watched) const; + private: CPVRGUIActionsRecordings(const CPVRGUIActionsRecordings&) = delete; CPVRGUIActionsRecordings const& operator=(CPVRGUIActionsRecordings const&) = delete; diff --git a/xbmc/utils/SaveFileStateJob.cpp b/xbmc/utils/SaveFileStateJob.cpp index b71620769d57a..27653ce1c14dc 100644 --- a/xbmc/utils/SaveFileStateJob.cpp +++ b/xbmc/utils/SaveFileStateJob.cpp @@ -26,6 +26,8 @@ #include "music/MusicFileItemClassify.h" #include "music/tags/MusicInfoTag.h" #include "network/upnp/UPnP.h" +#include "pvr/PVRManager.h" +#include "pvr/guilib/PVRGUIActionsRecordings.h" #include "utils/Variant.h" #include "video/Bookmark.h" #include "video/VideoDatabase.h" @@ -119,7 +121,11 @@ void CSaveFileState::DoWork(CFileItem& item, if (item.HasVideoInfoTag()) { - item.GetVideoInfoTag()->IncrementPlayCount(); + if (item.IsPVRRecording()) + CServiceBroker::GetPVRManager().Get().IncrementPlayCount( + item); + else + item.GetVideoInfoTag()->IncrementPlayCount(); if (newLastPlayed.IsValid()) item.GetVideoInfoTag()->m_lastPlayed = newLastPlayed; diff --git a/xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp b/xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp index ef2e9253ad176..3d4e33554574b 100644 --- a/xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp +++ b/xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp @@ -18,7 +18,7 @@ #endif #include "profiles/ProfileManager.h" #include "pvr/PVRManager.h" -#include "pvr/recordings/PVRRecordings.h" +#include "pvr/guilib/PVRGUIActionsRecordings.h" #include "settings/SettingsComponent.h" #include "utils/URIUtils.h" #include "video/VideoDatabase.h" @@ -70,8 +70,8 @@ bool CVideoLibraryMarkWatchedJob::Work(CVideoDatabase &db) continue; #endif - if (item->HasPVRRecordingInfoTag() && - CServiceBroker::GetPVRManager().Recordings()->MarkWatched(item->GetPVRRecordingInfoTag(), m_mark)) + if (item->IsPVRRecording() && + CServiceBroker::GetPVRManager().Get().MarkWatched(*item, m_mark)) { CDateTime newLastPlayed; if (m_mark) From 36153c6696e192e50d077b35eba8a9d60003ab94 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 11 Aug 2024 18:27:17 +0200 Subject: [PATCH 390/651] [PVR] Introduce 'Delete after watching' for recordings. --- .../resources/strings.po | 45 +++++++++- system/settings/settings.xml | 12 +++ xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp | 87 ++++++++++++++++++- xbmc/pvr/guilib/PVRGUIActionsRecordings.h | 16 +++- xbmc/settings/Settings.h | 1 + 5 files changed, 154 insertions(+), 7 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index b12f00ede95ae..8646e8d48a69b 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -4004,7 +4004,50 @@ msgctxt "#859" msgid "{0:s} [All channels]" msgstr "" -#empty strings from id 860 to 996 +#. Label for setting 'Delete after watching' and delete confirmation dialog box. +#: system/settings/settings.xml +#: xbmc/pvr/guilib/PVRGUIActionsRecordings..cpp +msgctxt "#860" +msgid "Delete after watching" +msgstr "" + +#. Help text for setting 'Delete after watching'. +#: system/settings/settings.xml +msgctxt "#861" +msgid "Configure whether recordings should be deleted after watching." +msgstr "" + +#. Value for setting 'Delete after watching'. Keep after watching. +#: system/settings/settings.xml +msgctxt "#862" +msgid "No" +msgstr "" + +#. Value for setting 'Delete after watching'. Confirm delete. +#: system/settings/settings.xml +msgctxt "#863" +msgid "Ask" +msgstr "" + +#. Value for setting 'Delete after watching'. Delete after watching. +#: system/settings/settings.xml +msgctxt "#864" +msgid "Yes" +msgstr "" + +#. Text for delete after watch confirmation dialog box. +#: xbmc/pvr/guilib/PVRGUIActionsRecordings..cpp +msgctxt "#865" +msgid "Do you want to delete this recording?" +msgstr "" + +#. Text for auto delete after watch toast message. +#: xbmc/pvr/guilib/PVRGUIActionsRecordings..cpp +msgctxt "#866" +msgid "Recording deleted: '{0:s}'" +msgstr "" + +#empty strings from id 867 to 996 #: xbmc/windows/GUIMediaWindow.cpp msgctxt "#997" diff --git a/system/settings/settings.xml b/system/settings/settings.xml index e5bee132ab8b0..fda87e5b0f113 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -2015,6 +2015,18 @@ true + + 2 + 0 + + + + + + + + + true 4 diff --git a/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp b/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp index c0172c67ef380..0366556764442 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp @@ -17,8 +17,11 @@ #include "filesystem/IDirectory.h" #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" #include "guilib/WindowIDs.h" +#include "messaging/helpers/DialogHelper.h" #include "messaging/helpers/DialogOKHelper.h" +#include "pvr/PVREventLogJob.h" #include "pvr/PVRItem.h" #include "pvr/PVRManager.h" #include "pvr/addons/PVRClient.h" @@ -29,6 +32,7 @@ #include "pvr/recordings/PVRRecordings.h" #include "settings/Settings.h" #include "threads/IRunnable.h" +#include "utils/StringUtils.h" #include "utils/Variant.h" #include "utils/log.h" @@ -41,6 +45,14 @@ using namespace KODI::MESSAGING; namespace { +enum PVRRECORD_DELETE_AFTER_WATCH +{ + // Values must match those defined in settings.xml -> pvrrecord.deleteafterwatch + NO = 0, + ASK = 1, + YES = 2, +}; + class AsyncRecordingAction : private IRunnable { public: @@ -183,6 +195,11 @@ class AsyncSetRecordingLifetime : public AsyncRecordingAction } // unnamed namespace +CPVRGUIActionsRecordings::CPVRGUIActionsRecordings() + : m_settings({CSettings::SETTING_PVRRECORD_DELETEAFTERWATCH}) +{ +} + bool CPVRGUIActionsRecordings::ShowRecordingInfo(const CFileItem& item) const { if (!item.IsPVRRecording()) @@ -353,12 +370,69 @@ bool CPVRGUIActionsRecordings::ShowRecordingSettings( return pDlgInfo->IsConfirmed(); } +bool CPVRGUIActionsRecordings::ProcessDeleteAfterWatch(const CFileItem& item) const +{ + bool deleteRecording{false}; + + const int action{m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_DELETEAFTERWATCH)}; + switch (action) + { + case PVRRECORD_DELETE_AFTER_WATCH::NO: + deleteRecording = false; + break; + + case PVRRECORD_DELETE_AFTER_WATCH::ASK: + deleteRecording = (HELPERS::ShowYesNoDialogLines( + CVariant{860}, // "Delete after watching" + CVariant{865}, // "Do you want to delete this recording?" + CVariant{""}, CVariant{item.GetPVRRecordingInfoTag()->GetTitle()}) == + HELPERS::DialogResponse::CHOICE_YES); + break; + + case PVRRECORD_DELETE_AFTER_WATCH::YES: + deleteRecording = true; + break; + + default: + CLog::LogF(LOGERROR, "Unhandled delete after watch action! Defaulting to 'no delete'."); + deleteRecording = false; + break; + } + + if (deleteRecording) + { + if (AsyncDeleteRecording().Execute(item)) + { + CPVREventLogJob* job = new CPVREventLogJob; + job->AddEvent(true, // display a toast, and log event + EventLevel::Information, // info, no error + g_localizeStrings.Get(860), // "Delete after watching" + StringUtils::Format(g_localizeStrings.Get(866), // Recording deleted: + item.GetPVRRecordingInfoTag()->GetTitle()), + item.GetPVRRecordingInfoTag()->IconPath()); + CServiceBroker::GetJobManager()->AddJob(job, nullptr); + } + else + { + HELPERS::ShowOKDialogText(CVariant{257}, // "Error" + CVariant{19111}); // "PVR backend error." + return false; + } + } + return true; +} + bool CPVRGUIActionsRecordings::IncrementPlayCount(const CFileItem& item) const { if (!item.IsPVRRecording()) return false; - return item.GetPVRRecordingInfoTag()->IncrementPlayCount(); + if (item.GetPVRRecordingInfoTag()->IncrementPlayCount()) + { + // Item must now be watched (because play count > 0). + return ProcessDeleteAfterWatch(item); + } + return false; } bool CPVRGUIActionsRecordings::MarkWatched(const CFileItem& item, bool watched) const @@ -366,6 +440,13 @@ bool CPVRGUIActionsRecordings::MarkWatched(const CFileItem& item, bool watched) if (!item.IsPVRRecording()) return false; - return CServiceBroker::GetPVRManager().Recordings()->MarkWatched(item.GetPVRRecordingInfoTag(), - watched); + if (CServiceBroker::GetPVRManager().Recordings()->MarkWatched(item.GetPVRRecordingInfoTag(), + watched)) + { + if (watched) + return ProcessDeleteAfterWatch(item); + + return true; + } + return false; } diff --git a/xbmc/pvr/guilib/PVRGUIActionsRecordings.h b/xbmc/pvr/guilib/PVRGUIActionsRecordings.h index 5a2cf9e546532..cc2e0f5d5b7fe 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsRecordings.h +++ b/xbmc/pvr/guilib/PVRGUIActionsRecordings.h @@ -9,6 +9,7 @@ #pragma once #include "pvr/IPVRComponent.h" +#include "pvr/settings/PVRSettings.h" #include <memory> @@ -21,7 +22,7 @@ class CPVRRecording; class CPVRGUIActionsRecordings : public IPVRComponent { public: - CPVRGUIActionsRecordings() = default; + CPVRGUIActionsRecordings(); ~CPVRGUIActionsRecordings() override = default; /*! @@ -74,14 +75,14 @@ class CPVRGUIActionsRecordings : public IPVRComponent bool UndeleteRecording(const CFileItem& item) const; /*! - * @brief Increment the play count of a recording. + * @brief Increment the play count of a recording. Process "Delete after watching" action. * @param item containing a recording for which the play count shall be incremented. * @return true, if the recording's play count was incremented successfully, false otherwise. */ bool IncrementPlayCount(const CFileItem& item) const; /*! - * @brief Mark a recording watched or unwatched. + * @brief Mark a recording watched or unwatched. Process "Delete after watching" action. * @param item containing a recording to be marked watched or unwatched. * @param watched Whether to mark the recording watched or unwatched. * @return true, if the recording's watched state was changed successfully, false otherwise. @@ -118,6 +119,15 @@ class CPVRGUIActionsRecordings : public IPVRComponent * @return true, if the dialog was ended successfully, false otherwise. */ bool ShowRecordingSettings(const std::shared_ptr<CPVRRecording>& recording) const; + + /*! + * @brief Process action according to "Delete after watching" setting value. + * @param item The watched recording. + * @return true on success, false otherwise. + */ + bool ProcessDeleteAfterWatch(const CFileItem& item) const; + + CPVRSettings m_settings; }; namespace GUI diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h index dfc87db9f286b..671cfb257c5bb 100644 --- a/xbmc/settings/Settings.h +++ b/xbmc/settings/Settings.h @@ -233,6 +233,7 @@ class CSettings : public CSettingsBase, public CSettingCreator, public CSettingC static constexpr auto SETTING_PVRRECORD_MARGINSTART = "pvrrecord.marginstart"; static constexpr auto SETTING_PVRRECORD_MARGINEND = "pvrrecord.marginend"; static constexpr auto SETTING_PVRRECORD_TIMERNOTIFICATIONS = "pvrrecord.timernotifications"; + static constexpr auto SETTING_PVRRECORD_DELETEAFTERWATCH = "pvrrecord.deleteafterwatch"; static constexpr auto SETTING_PVRRECORD_GROUPRECORDINGS = "pvrrecord.grouprecordings"; static constexpr auto SETTING_PVRREMINDERS_AUTOCLOSEDELAY = "pvrreminders.autoclosedelay"; static constexpr auto SETTING_PVRREMINDERS_AUTORECORD = "pvrreminders.autorecord"; From 1fea92fdf8ae1bc2aef3510c8866970770938967 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 16 Aug 2024 23:35:18 +0200 Subject: [PATCH 391/651] [PVR] clang-format PVRClient.cpp --- xbmc/pvr/addons/PVRClient.cpp | 926 +++++++++++++++++++--------------- 1 file changed, 510 insertions(+), 416 deletions(-) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index c58e4f26719e1..3eb32c7590610 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -530,9 +530,8 @@ bool CPVRClient::GetAddonProperties() /* get the capabilities */ PVR_ERROR retVal = DoAddonCall( __func__, - [&addonCapabilities](const AddonInstance* addon) { - return addon->toAddon->GetCapabilities(addon, &addonCapabilities); - }, + [&addonCapabilities](const AddonInstance* addon) + { return addon->toAddon->GetCapabilities(addon, &addonCapabilities); }, true, false); if (retVal == PVR_ERROR_NO_ERROR) @@ -688,17 +687,20 @@ PVR_ERROR CPVRClient::GetDriveSpace(uint64_t& iTotal, uint64_t& iUsed) const iTotal = 0; iUsed = 0; - return DoAddonCall(__func__, [&iTotal, &iUsed](const AddonInstance* addon) { - uint64_t iTotalSpace = 0; - uint64_t iUsedSpace = 0; - PVR_ERROR error = addon->toAddon->GetDriveSpace(addon, &iTotalSpace, &iUsedSpace); - if (error == PVR_ERROR_NO_ERROR) - { - iTotal = iTotalSpace; - iUsed = iUsedSpace; - } - return error; - }); + return DoAddonCall(__func__, + [&iTotal, &iUsed](const AddonInstance* addon) + { + uint64_t iTotalSpace = 0; + uint64_t iUsedSpace = 0; + PVR_ERROR error = + addon->toAddon->GetDriveSpace(addon, &iTotalSpace, &iUsedSpace); + if (error == PVR_ERROR_NO_ERROR) + { + iTotal = iTotalSpace; + iUsed = iUsedSpace; + } + return error; + }); } PVR_ERROR CPVRClient::StartChannelScan() @@ -764,7 +766,8 @@ PVR_ERROR CPVRClient::GetEPGForChannel(int iChannelUid, { return DoAddonCall( __func__, - [this, iChannelUid, epg, start, end](const AddonInstance* addon) { + [this, iChannelUid, epg, start, end](const AddonInstance* addon) + { PVR_HANDLE_STRUCT handle = {}; handle.callerAddress = this; handle.dataAddress = epg; @@ -783,9 +786,8 @@ PVR_ERROR CPVRClient::SetEPGMaxPastDays(int iPastDays) { return DoAddonCall( __func__, - [iPastDays](const AddonInstance* addon) { - return addon->toAddon->SetEPGMaxPastDays(addon, iPastDays); - }, + [iPastDays](const AddonInstance* addon) + { return addon->toAddon->SetEPGMaxPastDays(addon, iPastDays); }, m_clientCapabilities.SupportsEPG()); } @@ -793,9 +795,8 @@ PVR_ERROR CPVRClient::SetEPGMaxFutureDays(int iFutureDays) { return DoAddonCall( __func__, - [iFutureDays](const AddonInstance* addon) { - return addon->toAddon->SetEPGMaxFutureDays(addon, iFutureDays); - }, + [iFutureDays](const AddonInstance* addon) + { return addon->toAddon->SetEPGMaxFutureDays(addon, iFutureDays); }, m_clientCapabilities.SupportsEPG()); } @@ -891,7 +892,8 @@ PVR_ERROR CPVRClient::IsRecordable(const std::shared_ptr<const CPVREpgInfoTag>& { return DoAddonCall( __func__, - [tag, &bIsRecordable](const AddonInstance* addon) { + [tag, &bIsRecordable](const AddonInstance* addon) + { CAddonEpgTag addonTag(tag); return addon->toAddon->IsEPGTagRecordable(addon, &addonTag, &bIsRecordable); }, @@ -903,7 +905,8 @@ PVR_ERROR CPVRClient::IsPlayable(const std::shared_ptr<const CPVREpgInfoTag>& ta { return DoAddonCall( __func__, - [tag, &bIsPlayable](const AddonInstance* addon) { + [tag, &bIsPlayable](const AddonInstance* addon) + { CAddonEpgTag addonTag(tag); return addon->toAddon->IsEPGTagPlayable(addon, &addonTag, &bIsPlayable); }, @@ -924,19 +927,21 @@ void CPVRClient::WriteStreamProperties(PVR_NAMED_VALUE** properties, PVR_ERROR CPVRClient::GetEpgTagStreamProperties(const std::shared_ptr<const CPVREpgInfoTag>& tag, CPVRStreamProperties& props) const { - return DoAddonCall(__func__, [&tag, &props](const AddonInstance* addon) { - CAddonEpgTag addonTag(tag); + return DoAddonCall(__func__, + [&tag, &props](const AddonInstance* addon) + { + CAddonEpgTag addonTag(tag); - PVR_NAMED_VALUE** property_array{nullptr}; - unsigned int size{0}; - const PVR_ERROR error{ - addon->toAddon->GetEPGTagStreamProperties(addon, &addonTag, &property_array, &size)}; - if (error == PVR_ERROR_NO_ERROR) - WriteStreamProperties(property_array, size, props); + PVR_NAMED_VALUE** property_array{nullptr}; + unsigned int size{0}; + const PVR_ERROR error{addon->toAddon->GetEPGTagStreamProperties( + addon, &addonTag, &property_array, &size)}; + if (error == PVR_ERROR_NO_ERROR) + WriteStreamProperties(property_array, size, props); - addon->toAddon->FreeProperties(addon, property_array, size); - return error; - }); + addon->toAddon->FreeProperties(addon, property_array, size); + return error; + }); } PVR_ERROR CPVRClient::GetEpgTagEdl(const std::shared_ptr<const CPVREpgInfoTag>& epgTag, @@ -945,7 +950,8 @@ PVR_ERROR CPVRClient::GetEpgTagEdl(const std::shared_ptr<const CPVREpgInfoTag>& edls.clear(); return DoAddonCall( __func__, - [&epgTag, &edls](const AddonInstance* addon) { + [&epgTag, &edls](const AddonInstance* addon) + { CAddonEpgTag addonTag(epgTag); PVR_EDL_ENTRY** edl_array{nullptr}; @@ -968,9 +974,8 @@ PVR_ERROR CPVRClient::GetChannelGroupsAmount(int& iGroups) const iGroups = -1; return DoAddonCall( __func__, - [&iGroups](const AddonInstance* addon) { - return addon->toAddon->GetChannelGroupsAmount(addon, &iGroups); - }, + [&iGroups](const AddonInstance* addon) + { return addon->toAddon->GetChannelGroupsAmount(addon, &iGroups); }, m_clientCapabilities.SupportsChannelGroups()); } @@ -1015,43 +1020,45 @@ PVR_ERROR CPVRClient::GetChannelGroupMembers( PVR_ERROR CPVRClient::GetProvidersAmount(int& iProviders) const { iProviders = -1; - return DoAddonCall(__func__, [&iProviders](const AddonInstance* addon) { - return addon->toAddon->GetProvidersAmount(addon, &iProviders); - }); + return DoAddonCall(__func__, [&iProviders](const AddonInstance* addon) + { return addon->toAddon->GetProvidersAmount(addon, &iProviders); }); } PVR_ERROR CPVRClient::GetProviders(CPVRProvidersContainer& providers) const { - return DoAddonCall(__func__, - [this, &providers](const AddonInstance* addon) { - PVR_HANDLE_STRUCT handle = {}; - handle.callerAddress = this; - handle.dataAddress = &providers; - return addon->toAddon->GetProviders(addon, &handle); - }, - m_clientCapabilities.SupportsProviders()); + return DoAddonCall( + __func__, + [this, &providers](const AddonInstance* addon) + { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = &providers; + return addon->toAddon->GetProviders(addon, &handle); + }, + m_clientCapabilities.SupportsProviders()); } PVR_ERROR CPVRClient::GetChannelsAmount(int& iChannels) const { iChannels = -1; - return DoAddonCall(__func__, [&iChannels](const AddonInstance* addon) { - return addon->toAddon->GetChannelsAmount(addon, &iChannels); - }); + return DoAddonCall(__func__, [&iChannels](const AddonInstance* addon) + { return addon->toAddon->GetChannelsAmount(addon, &iChannels); }); } PVR_ERROR CPVRClient::GetChannels(bool radio, std::vector<std::shared_ptr<CPVRChannel>>& channels) const { - return DoAddonCall(__func__, - [this, radio, &channels](const AddonInstance* addon) { - PVR_HANDLE_STRUCT handle = {}; - handle.callerAddress = this; - handle.dataAddress = &channels; - return addon->toAddon->GetChannels(addon, &handle, radio); - }, - (radio && m_clientCapabilities.SupportsRadio()) || - (!radio && m_clientCapabilities.SupportsTV())); + return DoAddonCall( + __func__, + [this, radio, &channels](const AddonInstance* addon) + { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = &channels; + return addon->toAddon->GetChannels(addon, &handle, radio); + }, + (radio && m_clientCapabilities.SupportsRadio()) || + (!radio && m_clientCapabilities.SupportsTV())); } PVR_ERROR CPVRClient::GetRecordingsAmount(bool deleted, int& iRecordings) const @@ -1059,24 +1066,25 @@ PVR_ERROR CPVRClient::GetRecordingsAmount(bool deleted, int& iRecordings) const iRecordings = -1; return DoAddonCall( __func__, - [deleted, &iRecordings](const AddonInstance* addon) { - return addon->toAddon->GetRecordingsAmount(addon, deleted, &iRecordings); - }, + [deleted, &iRecordings](const AddonInstance* addon) + { return addon->toAddon->GetRecordingsAmount(addon, deleted, &iRecordings); }, m_clientCapabilities.SupportsRecordings() && (!deleted || m_clientCapabilities.SupportsRecordingsUndelete())); } PVR_ERROR CPVRClient::GetRecordings(CPVRRecordings* results, bool deleted) const { - return DoAddonCall(__func__, - [this, results, deleted](const AddonInstance* addon) { - PVR_HANDLE_STRUCT handle = {}; - handle.callerAddress = this; - handle.dataAddress = results; - return addon->toAddon->GetRecordings(addon, &handle, deleted); - }, - m_clientCapabilities.SupportsRecordings() && - (!deleted || m_clientCapabilities.SupportsRecordingsUndelete())); + return DoAddonCall( + __func__, + [this, results, deleted](const AddonInstance* addon) + { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = results; + return addon->toAddon->GetRecordings(addon, &handle, deleted); + }, + m_clientCapabilities.SupportsRecordings() && + (!deleted || m_clientCapabilities.SupportsRecordingsUndelete())); } PVR_ERROR CPVRClient::DeleteRecording(const CPVRRecording& recording) @@ -1107,9 +1115,8 @@ PVR_ERROR CPVRClient::DeleteAllRecordingsFromTrash() { return DoAddonCall( __func__, - [](const AddonInstance* addon) { - return addon->toAddon->DeleteAllRecordingsFromTrash(addon); - }, + [](const AddonInstance* addon) + { return addon->toAddon->DeleteAllRecordingsFromTrash(addon); }, m_clientCapabilities.SupportsRecordingsUndelete()); } @@ -1218,22 +1225,23 @@ PVR_ERROR CPVRClient::GetTimersAmount(int& iTimers) const iTimers = -1; return DoAddonCall( __func__, - [&iTimers](const AddonInstance* addon) { - return addon->toAddon->GetTimersAmount(addon, &iTimers); - }, + [&iTimers](const AddonInstance* addon) + { return addon->toAddon->GetTimersAmount(addon, &iTimers); }, m_clientCapabilities.SupportsTimers()); } PVR_ERROR CPVRClient::GetTimers(CPVRTimersContainer* results) const { - return DoAddonCall(__func__, - [this, results](const AddonInstance* addon) { - PVR_HANDLE_STRUCT handle = {}; - handle.callerAddress = this; - handle.dataAddress = results; - return addon->toAddon->GetTimers(addon, &handle); - }, - m_clientCapabilities.SupportsTimers()); + return DoAddonCall( + __func__, + [this, results](const AddonInstance* addon) + { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = results; + return addon->toAddon->GetTimers(addon, &handle); + }, + m_clientCapabilities.SupportsTimers()); } PVR_ERROR CPVRClient::AddTimer(const CPVRTimerInfoTag& timer) @@ -1393,9 +1401,8 @@ PVR_ERROR CPVRClient::UpdateTimerTypes() for (const auto& type : timerTypes) { const auto it = std::find_if(m_timertypes.cbegin(), m_timertypes.cend(), - [&type](const std::shared_ptr<const CPVRTimerType>& entry) { - return entry->GetTypeId() == type->GetTypeId(); - }); + [&type](const std::shared_ptr<const CPVRTimerType>& entry) + { return entry->GetTypeId() == type->GetTypeId(); }); if (it == m_timertypes.cend()) { newTimerTypes.emplace_back(type); @@ -1416,74 +1423,89 @@ PVR_ERROR CPVRClient::GetStreamReadChunkSize(int& iChunkSize) const { return DoAddonCall( __func__, - [&iChunkSize](const AddonInstance* addon) { - return addon->toAddon->GetStreamReadChunkSize(addon, &iChunkSize); - }, + [&iChunkSize](const AddonInstance* addon) + { return addon->toAddon->GetStreamReadChunkSize(addon, &iChunkSize); }, m_clientCapabilities.SupportsRecordings() || m_clientCapabilities.HandlesInputStream()); } PVR_ERROR CPVRClient::ReadLiveStream(void* lpBuf, int64_t uiBufSize, int& iRead) { iRead = -1; - return DoAddonCall(__func__, [&lpBuf, uiBufSize, &iRead](const AddonInstance* addon) { - iRead = addon->toAddon->ReadLiveStream(addon, static_cast<unsigned char*>(lpBuf), - static_cast<int>(uiBufSize)); - return (iRead == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [&lpBuf, uiBufSize, &iRead](const AddonInstance* addon) + { + iRead = addon->toAddon->ReadLiveStream( + addon, static_cast<unsigned char*>(lpBuf), static_cast<int>(uiBufSize)); + return (iRead == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::ReadRecordedStream(void* lpBuf, int64_t uiBufSize, int& iRead) { iRead = -1; - return DoAddonCall(__func__, [&lpBuf, uiBufSize, &iRead](const AddonInstance* addon) { - iRead = addon->toAddon->ReadRecordedStream(addon, static_cast<unsigned char*>(lpBuf), - static_cast<int>(uiBufSize)); - return (iRead == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [&lpBuf, uiBufSize, &iRead](const AddonInstance* addon) + { + iRead = addon->toAddon->ReadRecordedStream( + addon, static_cast<unsigned char*>(lpBuf), static_cast<int>(uiBufSize)); + return (iRead == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::SeekLiveStream(int64_t iFilePosition, int iWhence, int64_t& iPosition) { iPosition = -1; - return DoAddonCall(__func__, [iFilePosition, iWhence, &iPosition](const AddonInstance* addon) { - iPosition = addon->toAddon->SeekLiveStream(addon, iFilePosition, iWhence); - return (iPosition == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [iFilePosition, iWhence, &iPosition](const AddonInstance* addon) + { + iPosition = addon->toAddon->SeekLiveStream(addon, iFilePosition, iWhence); + return (iPosition == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::SeekRecordedStream(int64_t iFilePosition, int iWhence, int64_t& iPosition) { iPosition = -1; - return DoAddonCall(__func__, [iFilePosition, iWhence, &iPosition](const AddonInstance* addon) { - iPosition = addon->toAddon->SeekRecordedStream(addon, iFilePosition, iWhence); - return (iPosition == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [iFilePosition, iWhence, &iPosition](const AddonInstance* addon) + { + iPosition = + addon->toAddon->SeekRecordedStream(addon, iFilePosition, iWhence); + return (iPosition == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::SeekTime(double time, bool backwards, double* startpts) { - return DoAddonCall(__func__, [time, backwards, &startpts](const AddonInstance* addon) { - return addon->toAddon->SeekTime(addon, time, backwards, startpts) ? PVR_ERROR_NO_ERROR - : PVR_ERROR_NOT_IMPLEMENTED; - }); + return DoAddonCall(__func__, + [time, backwards, &startpts](const AddonInstance* addon) + { + return addon->toAddon->SeekTime(addon, time, backwards, startpts) + ? PVR_ERROR_NO_ERROR + : PVR_ERROR_NOT_IMPLEMENTED; + }); } PVR_ERROR CPVRClient::GetLiveStreamLength(int64_t& iLength) const { iLength = -1; - return DoAddonCall(__func__, [&iLength](const AddonInstance* addon) { - iLength = addon->toAddon->LengthLiveStream(addon); - return (iLength == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [&iLength](const AddonInstance* addon) + { + iLength = addon->toAddon->LengthLiveStream(addon); + return (iLength == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::GetRecordedStreamLength(int64_t& iLength) const { iLength = -1; - return DoAddonCall(__func__, [&iLength](const AddonInstance* addon) { - iLength = addon->toAddon->LengthRecordedStream(addon); - return (iLength == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [&iLength](const AddonInstance* addon) + { + iLength = addon->toAddon->LengthRecordedStream(addon); + return (iLength == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::SignalQuality(int channelUid, CPVRSignalStatus& qualityinfo) const @@ -1522,57 +1544,63 @@ PVR_ERROR CPVRClient::GetDescrambleInfo(int channelUid, CPVRDescrambleInfo& desc PVR_ERROR CPVRClient::GetChannelStreamProperties(const std::shared_ptr<const CPVRChannel>& channel, CPVRStreamProperties& props) const { - return DoAddonCall(__func__, [this, &channel, &props](const AddonInstance* addon) { - if (!CanPlayChannel(channel)) - return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon + return DoAddonCall( + __func__, + [this, &channel, &props](const AddonInstance* addon) + { + if (!CanPlayChannel(channel)) + return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon - const CAddonChannel addonChannel{*channel}; + const CAddonChannel addonChannel{*channel}; - PVR_NAMED_VALUE** property_array{nullptr}; - unsigned int size{0}; - const PVR_ERROR error{addon->toAddon->GetChannelStreamProperties( - addon, &addonChannel, PVR_SOURCE::DEFAULT, &property_array, &size)}; - if (error == PVR_ERROR_NO_ERROR) - WriteStreamProperties(property_array, size, props); + PVR_NAMED_VALUE** property_array{nullptr}; + unsigned int size{0}; + const PVR_ERROR error{addon->toAddon->GetChannelStreamProperties( + addon, &addonChannel, PVR_SOURCE::DEFAULT, &property_array, &size)}; + if (error == PVR_ERROR_NO_ERROR) + WriteStreamProperties(property_array, size, props); - addon->toAddon->FreeProperties(addon, property_array, size); - return error; - }); + addon->toAddon->FreeProperties(addon, property_array, size); + return error; + }); } PVR_ERROR CPVRClient::GetRecordingStreamProperties( const std::shared_ptr<const CPVRRecording>& recording, CPVRStreamProperties& props) const { - return DoAddonCall(__func__, [this, &recording, &props](const AddonInstance* addon) { - if (!m_clientCapabilities.SupportsRecordings()) - return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon + return DoAddonCall( + __func__, + [this, &recording, &props](const AddonInstance* addon) + { + if (!m_clientCapabilities.SupportsRecordings()) + return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon - const CAddonRecording addonRecording(*recording); + const CAddonRecording addonRecording(*recording); - PVR_NAMED_VALUE** property_array{nullptr}; - unsigned int size{0}; - const PVR_ERROR error{addon->toAddon->GetRecordingStreamProperties(addon, &addonRecording, - &property_array, &size)}; - if (error == PVR_ERROR_NO_ERROR) - WriteStreamProperties(property_array, size, props); + PVR_NAMED_VALUE** property_array{nullptr}; + unsigned int size{0}; + const PVR_ERROR error{addon->toAddon->GetRecordingStreamProperties(addon, &addonRecording, + &property_array, &size)}; + if (error == PVR_ERROR_NO_ERROR) + WriteStreamProperties(property_array, size, props); - addon->toAddon->FreeProperties(addon, property_array, size); - return error; - }); + addon->toAddon->FreeProperties(addon, property_array, size); + return error; + }); } PVR_ERROR CPVRClient::GetStreamProperties(PVR_STREAM_PROPERTIES* props) const { - return DoAddonCall(__func__, [&props](const AddonInstance* addon) { - return addon->toAddon->GetStreamProperties(addon, props); - }); + return DoAddonCall(__func__, [&props](const AddonInstance* addon) + { return addon->toAddon->GetStreamProperties(addon, props); }); } PVR_ERROR CPVRClient::DemuxReset() { return DoAddonCall( __func__, - [](const AddonInstance* addon) { + [](const AddonInstance* addon) + { addon->toAddon->DemuxReset(addon); return PVR_ERROR_NO_ERROR; }, @@ -1583,7 +1611,8 @@ PVR_ERROR CPVRClient::DemuxAbort() { return DoAddonCall( __func__, - [](const AddonInstance* addon) { + [](const AddonInstance* addon) + { addon->toAddon->DemuxAbort(addon); return PVR_ERROR_NO_ERROR; }, @@ -1594,7 +1623,8 @@ PVR_ERROR CPVRClient::DemuxFlush() { return DoAddonCall( __func__, - [](const AddonInstance* addon) { + [](const AddonInstance* addon) + { addon->toAddon->DemuxFlush(addon); return PVR_ERROR_NO_ERROR; }, @@ -1605,7 +1635,8 @@ PVR_ERROR CPVRClient::DemuxRead(DemuxPacket*& packet) { return DoAddonCall( __func__, - [&packet](const AddonInstance* addon) { + [&packet](const AddonInstance* addon) + { packet = static_cast<DemuxPacket*>(addon->toAddon->DemuxRead(addon)); return packet ? PVR_ERROR_NO_ERROR : PVR_ERROR_NOT_IMPLEMENTED; }, @@ -1704,23 +1735,27 @@ PVR_ERROR CPVRClient::OpenLiveStream(const std::shared_ptr<const CPVRChannel>& c if (!channel) return PVR_ERROR_INVALID_PARAMETERS; - return DoAddonCall(__func__, [this, channel](const AddonInstance* addon) { - CloseLiveStream(); - - if (!CanPlayChannel(channel)) - { - CLog::LogFC(LOGDEBUG, LOGPVR, "Add-on {} can not play channel '{}'", GetID(), - channel->ChannelName()); - return PVR_ERROR_SERVER_ERROR; - } - else - { - CLog::LogFC(LOGDEBUG, LOGPVR, "Opening live stream for channel '{}'", channel->ChannelName()); - const CAddonChannel addonChannel{*channel}; - return addon->toAddon->OpenLiveStream(addon, &addonChannel) ? PVR_ERROR_NO_ERROR - : PVR_ERROR_NOT_IMPLEMENTED; - } - }); + return DoAddonCall(__func__, + [this, channel](const AddonInstance* addon) + { + CloseLiveStream(); + + if (!CanPlayChannel(channel)) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Add-on {} can not play channel '{}'", + GetID(), channel->ChannelName()); + return PVR_ERROR_SERVER_ERROR; + } + else + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Opening live stream for channel '{}'", + channel->ChannelName()); + const CAddonChannel addonChannel{*channel}; + return addon->toAddon->OpenLiveStream(addon, &addonChannel) + ? PVR_ERROR_NO_ERROR + : PVR_ERROR_NOT_IMPLEMENTED; + } + }); } PVR_ERROR CPVRClient::OpenRecordedStream(const std::shared_ptr<const CPVRRecording>& recording) @@ -1730,7 +1765,8 @@ PVR_ERROR CPVRClient::OpenRecordedStream(const std::shared_ptr<const CPVRRecordi return DoAddonCall( __func__, - [this, recording](const AddonInstance* addon) { + [this, recording](const AddonInstance* addon) + { CloseRecordedStream(); const CAddonRecording tag(*recording); @@ -1743,102 +1779,115 @@ PVR_ERROR CPVRClient::OpenRecordedStream(const std::shared_ptr<const CPVRRecordi PVR_ERROR CPVRClient::CloseLiveStream() { - return DoAddonCall(__func__, [](const AddonInstance* addon) { - addon->toAddon->CloseLiveStream(addon); - return PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [](const AddonInstance* addon) + { + addon->toAddon->CloseLiveStream(addon); + return PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::CloseRecordedStream() { - return DoAddonCall(__func__, [](const AddonInstance* addon) { - addon->toAddon->CloseRecordedStream(addon); - return PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [](const AddonInstance* addon) + { + addon->toAddon->CloseRecordedStream(addon); + return PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::PauseStream(bool bPaused) { - return DoAddonCall(__func__, [bPaused](const AddonInstance* addon) { - addon->toAddon->PauseStream(addon, bPaused); - return PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [bPaused](const AddonInstance* addon) + { + addon->toAddon->PauseStream(addon, bPaused); + return PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::SetSpeed(int speed) { - return DoAddonCall(__func__, [speed](const AddonInstance* addon) { - addon->toAddon->SetSpeed(addon, speed); - return PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [speed](const AddonInstance* addon) + { + addon->toAddon->SetSpeed(addon, speed); + return PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::FillBuffer(bool mode) { - return DoAddonCall(__func__, [mode](const AddonInstance* addon) { - addon->toAddon->FillBuffer(addon, mode); - return PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [mode](const AddonInstance* addon) + { + addon->toAddon->FillBuffer(addon, mode); + return PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::CanPauseStream(bool& bCanPause) const { bCanPause = false; - return DoAddonCall(__func__, [&bCanPause](const AddonInstance* addon) { - bCanPause = addon->toAddon->CanPauseStream(addon); - return PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [&bCanPause](const AddonInstance* addon) + { + bCanPause = addon->toAddon->CanPauseStream(addon); + return PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::CanSeekStream(bool& bCanSeek) const { bCanSeek = false; - return DoAddonCall(__func__, [&bCanSeek](const AddonInstance* addon) { - bCanSeek = addon->toAddon->CanSeekStream(addon); - return PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [&bCanSeek](const AddonInstance* addon) + { + bCanSeek = addon->toAddon->CanSeekStream(addon); + return PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::GetStreamTimes(PVR_STREAM_TIMES* times) const { - return DoAddonCall(__func__, [×](const AddonInstance* addon) { - return addon->toAddon->GetStreamTimes(addon, times); - }); + return DoAddonCall(__func__, [×](const AddonInstance* addon) + { return addon->toAddon->GetStreamTimes(addon, times); }); } PVR_ERROR CPVRClient::IsRealTimeStream(bool& bRealTime) const { bRealTime = false; - return DoAddonCall(__func__, [&bRealTime](const AddonInstance* addon) { - bRealTime = addon->toAddon->IsRealTimeStream(addon); - return PVR_ERROR_NO_ERROR; - }); + return DoAddonCall(__func__, + [&bRealTime](const AddonInstance* addon) + { + bRealTime = addon->toAddon->IsRealTimeStream(addon); + return PVR_ERROR_NO_ERROR; + }); } PVR_ERROR CPVRClient::OnSystemSleep() { - return DoAddonCall( - __func__, [](const AddonInstance* addon) { return addon->toAddon->OnSystemSleep(addon); }); + return DoAddonCall(__func__, [](const AddonInstance* addon) + { return addon->toAddon->OnSystemSleep(addon); }); } PVR_ERROR CPVRClient::OnSystemWake() { - return DoAddonCall( - __func__, [](const AddonInstance* addon) { return addon->toAddon->OnSystemWake(addon); }); + return DoAddonCall(__func__, [](const AddonInstance* addon) + { return addon->toAddon->OnSystemWake(addon); }); } PVR_ERROR CPVRClient::OnPowerSavingActivated() { - return DoAddonCall(__func__, [](const AddonInstance* addon) { - return addon->toAddon->OnPowerSavingActivated(addon); - }); + return DoAddonCall(__func__, [](const AddonInstance* addon) + { return addon->toAddon->OnPowerSavingActivated(addon); }); } PVR_ERROR CPVRClient::OnPowerSavingDeactivated() { - return DoAddonCall(__func__, [](const AddonInstance* addon) { - return addon->toAddon->OnPowerSavingDeactivated(addon); - }); + return DoAddonCall(__func__, [](const AddonInstance* addon) + { return addon->toAddon->OnPowerSavingDeactivated(addon); }); } std::shared_ptr<CPVRClientMenuHooks> CPVRClient::GetMenuHooks() const @@ -1852,16 +1901,18 @@ std::shared_ptr<CPVRClientMenuHooks> CPVRClient::GetMenuHooks() const PVR_ERROR CPVRClient::CallEpgTagMenuHook(const CPVRClientMenuHook& hook, const std::shared_ptr<const CPVREpgInfoTag>& tag) { - return DoAddonCall(__func__, [&hook, &tag](const AddonInstance* addon) { - CAddonEpgTag addonTag(tag); + return DoAddonCall(__func__, + [&hook, &tag](const AddonInstance* addon) + { + CAddonEpgTag addonTag(tag); - PVR_MENUHOOK menuHook; - menuHook.category = PVR_MENUHOOK_EPG; - menuHook.iHookId = hook.GetId(); - menuHook.iLocalizedStringId = hook.GetLabelId(); + PVR_MENUHOOK menuHook; + menuHook.category = PVR_MENUHOOK_EPG; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); - return addon->toAddon->CallEPGMenuHook(addon, &menuHook, &addonTag); - }); + return addon->toAddon->CallEPGMenuHook(addon, &menuHook, &addonTag); + }); } PVR_ERROR CPVRClient::CallChannelMenuHook(const CPVRClientMenuHook& hook, @@ -1919,14 +1970,16 @@ PVR_ERROR CPVRClient::CallTimerMenuHook(const CPVRClientMenuHook& hook, PVR_ERROR CPVRClient::CallSettingsMenuHook(const CPVRClientMenuHook& hook) { - return DoAddonCall(__func__, [&hook](const AddonInstance* addon) { - PVR_MENUHOOK menuHook; - menuHook.category = PVR_MENUHOOK_SETTING; - menuHook.iHookId = hook.GetId(); - menuHook.iLocalizedStringId = hook.GetLabelId(); + return DoAddonCall(__func__, + [&hook](const AddonInstance* addon) + { + PVR_MENUHOOK menuHook; + menuHook.category = PVR_MENUHOOK_SETTING; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); - return addon->toAddon->CallSettingsMenuHook(addon, &menuHook); - }); + return addon->toAddon->CallSettingsMenuHook(addon, &menuHook); + }); } void CPVRClient::SetPriority(int iPriority) @@ -1981,71 +2034,79 @@ void CPVRClient::cb_transfer_channel_group(void* kodiInstance, const PVR_HANDLE handle, const PVR_CHANNEL_GROUP* group) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - if (!handle || !group) - { - CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); - return; - } - - if (strlen(group->strGroupName) == 0) - { - CLog::LogF(LOGERROR, "Empty group name"); - return; - } - - // transfer this entry to the groups container - CPVRChannelGroups* kodiGroups = static_cast<CPVRChannelGroups*>(handle->dataAddress); - const auto transferGroup = kodiGroups->GetGroupFactory()->CreateClientGroup( - *group, client->GetID(), kodiGroups->GetGroupAll()); - kodiGroups->UpdateFromClient(transferGroup); - }); + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) + { + if (!handle || !group) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + if (strlen(group->strGroupName) == 0) + { + CLog::LogF(LOGERROR, "Empty group name"); + return; + } + + // transfer this entry to the groups container + CPVRChannelGroups* kodiGroups = + static_cast<CPVRChannelGroups*>(handle->dataAddress); + const auto transferGroup = kodiGroups->GetGroupFactory()->CreateClientGroup( + *group, client->GetID(), kodiGroups->GetGroupAll()); + kodiGroups->UpdateFromClient(transferGroup); + }); } void CPVRClient::cb_transfer_channel_group_member(void* kodiInstance, const PVR_HANDLE handle, const PVR_CHANNEL_GROUP_MEMBER* member) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - if (!handle || !member) - { - CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); - return; - } - - const std::shared_ptr<CPVRChannel> channel = - CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(member->iChannelUniqueId, - client->GetID()); - if (!channel) - { - CLog::LogF(LOGERROR, "Cannot find group '{}' or channel '{}'", member->strGroupName, - member->iChannelUniqueId); - } - else - { - auto* groupMembers = - static_cast<std::vector<std::shared_ptr<CPVRChannelGroupMember>>*>(handle->dataAddress); - groupMembers->emplace_back(std::make_shared<CPVRChannelGroupMember>( - member->strGroupName, client->GetID(), member->iOrder, channel)); - } - }); + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) + { + if (!handle || !member) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + const std::shared_ptr<CPVRChannel> channel = + CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID( + member->iChannelUniqueId, client->GetID()); + if (!channel) + { + CLog::LogF(LOGERROR, "Cannot find group '{}' or channel '{}'", + member->strGroupName, member->iChannelUniqueId); + } + else + { + auto* groupMembers = + static_cast<std::vector<std::shared_ptr<CPVRChannelGroupMember>>*>( + handle->dataAddress); + groupMembers->emplace_back(std::make_shared<CPVRChannelGroupMember>( + member->strGroupName, client->GetID(), member->iOrder, channel)); + } + }); } void CPVRClient::cb_transfer_epg_entry(void* kodiInstance, const PVR_HANDLE handle, const EPG_TAG* epgentry) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - if (!handle || !epgentry) - { - CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); - return; - } + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) + { + if (!handle || !epgentry) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - // transfer this entry to the epg - CPVREpg* epg = static_cast<CPVREpg*>(handle->dataAddress); - epg->UpdateEntry(epgentry, client->GetID()); - }); + // transfer this entry to the epg + CPVREpg* epg = static_cast<CPVREpg*>(handle->dataAddress); + epg->UpdateEntry(epgentry, client->GetID()); + }); } void CPVRClient::cb_transfer_provider_entry(void* kodiInstance, @@ -2076,73 +2137,85 @@ void CPVRClient::cb_transfer_channel_entry(void* kodiInstance, const PVR_HANDLE handle, const PVR_CHANNEL* channel) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - if (!handle || !channel) - { - CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); - return; - } + HandleAddonCallback( + __func__, kodiInstance, + [&](CPVRClient* client) + { + if (!handle || !channel) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - auto* channels = static_cast<std::vector<std::shared_ptr<CPVRChannel>>*>(handle->dataAddress); - channels->emplace_back(std::make_shared<CPVRChannel>(*channel, client->GetID())); - }); + auto* channels = + static_cast<std::vector<std::shared_ptr<CPVRChannel>>*>(handle->dataAddress); + channels->emplace_back(std::make_shared<CPVRChannel>(*channel, client->GetID())); + }); } void CPVRClient::cb_transfer_recording_entry(void* kodiInstance, const PVR_HANDLE handle, const PVR_RECORDING* recording) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - if (!handle || !recording) - { - CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); - return; - } - - // transfer this entry to the recordings container - const std::shared_ptr<CPVRRecording> transferRecording = - std::make_shared<CPVRRecording>(*recording, client->GetID()); - CPVRRecordings* recordings = static_cast<CPVRRecordings*>(handle->dataAddress); - recordings->UpdateFromClient(transferRecording, *client); - }); + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) + { + if (!handle || !recording) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + // transfer this entry to the recordings container + const std::shared_ptr<CPVRRecording> transferRecording = + std::make_shared<CPVRRecording>(*recording, client->GetID()); + CPVRRecordings* recordings = + static_cast<CPVRRecordings*>(handle->dataAddress); + recordings->UpdateFromClient(transferRecording, *client); + }); } void CPVRClient::cb_transfer_timer_entry(void* kodiInstance, const PVR_HANDLE handle, const PVR_TIMER* timer) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - if (!handle || !timer) - { - CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); - return; - } - - // Note: channel can be nullptr here, for instance for epg-based timer rules - // ("record on any channel" condition) - const std::shared_ptr<CPVRChannel> channel = - CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(timer->iClientChannelUid, - client->GetID()); - - // transfer this entry to the timers container - const std::shared_ptr<CPVRTimerInfoTag> transferTimer = - std::make_shared<CPVRTimerInfoTag>(*timer, channel, client->GetID()); - CPVRTimersContainer* timers = static_cast<CPVRTimersContainer*>(handle->dataAddress); - timers->UpdateFromClient(transferTimer); - }); + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) + { + if (!handle || !timer) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + // Note: channel can be nullptr here, for instance for epg-based timer rules + // ("record on any channel" condition) + const std::shared_ptr<CPVRChannel> channel = + CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID( + timer->iClientChannelUid, client->GetID()); + + // transfer this entry to the timers container + const std::shared_ptr<CPVRTimerInfoTag> transferTimer = + std::make_shared<CPVRTimerInfoTag>(*timer, channel, client->GetID()); + CPVRTimersContainer* timers = + static_cast<CPVRTimersContainer*>(handle->dataAddress); + timers->UpdateFromClient(transferTimer); + }); } void CPVRClient::cb_add_menu_hook(void* kodiInstance, const PVR_MENUHOOK* hook) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - if (!hook) - { - CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); - return; - } + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) + { + if (!hook) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - client->GetMenuHooks()->AddHook(*hook); - }); + client->GetMenuHooks()->AddHook(*hook); + }); } void CPVRClient::cb_recording_notification(void* kodiInstance, @@ -2150,88 +2223,103 @@ void CPVRClient::cb_recording_notification(void* kodiInstance, const char* strFileName, bool bOnOff) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - if (!strFileName) - { - CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); - return; - } + HandleAddonCallback( + __func__, kodiInstance, + [&](CPVRClient* client) + { + if (!strFileName) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - const std::string strLine1 = StringUtils::Format(g_localizeStrings.Get(bOnOff ? 19197 : 19198), - client->GetFullClientName()); - std::string strLine2; - if (strName) - strLine2 = strName; - else - strLine2 = strFileName; + const std::string strLine1 = StringUtils::Format( + g_localizeStrings.Get(bOnOff ? 19197 : 19198), client->GetFullClientName()); + std::string strLine2; + if (strName) + strLine2 = strName; + else + strLine2 = strFileName; - // display a notification for 5 seconds - CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, strLine1, strLine2, 5000, - false); - auto eventLog = CServiceBroker::GetEventLog(); - if (eventLog) - eventLog->Add(EventPtr( - new CNotificationEvent(client->GetFullClientName(), strLine1, client->Icon(), strLine2))); + // display a notification for 5 seconds + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, strLine1, strLine2, 5000, + false); + auto eventLog = CServiceBroker::GetEventLog(); + if (eventLog) + eventLog->Add(EventPtr(new CNotificationEvent(client->GetFullClientName(), strLine1, + client->Icon(), strLine2))); - CLog::LogFC(LOGDEBUG, LOGPVR, "Recording {} on client {}. name='{}' filename='{}'", - bOnOff ? "started" : "finished", client->GetID(), strName, strFileName); - }); + CLog::LogFC(LOGDEBUG, LOGPVR, "Recording {} on client {}. name='{}' filename='{}'", + bOnOff ? "started" : "finished", client->GetID(), strName, strFileName); + }); } void CPVRClient::cb_trigger_channel_update(void* kodiInstance) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - // update channels in the next iteration of the pvrmanager's main loop - CServiceBroker::GetPVRManager().TriggerChannelsUpdate(client->GetID()); - }); + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) + { + // update channels in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerChannelsUpdate(client->GetID()); + }); } void CPVRClient::cb_trigger_provider_update(void* kodiInstance) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - /* update the providers table in the next iteration of the pvrmanager's main loop */ - CServiceBroker::GetPVRManager().TriggerProvidersUpdate(client->GetID()); - }); + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) + { + /* update the providers table in the next iteration of the pvrmanager's main loop */ + CServiceBroker::GetPVRManager().TriggerProvidersUpdate(client->GetID()); + }); } void CPVRClient::cb_trigger_timer_update(void* kodiInstance) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - // update timers in the next iteration of the pvrmanager's main loop - CServiceBroker::GetPVRManager().TriggerTimersUpdate(client->GetID()); - }); + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) + { + // update timers in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerTimersUpdate(client->GetID()); + }); } void CPVRClient::cb_trigger_recording_update(void* kodiInstance) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - // update recordings in the next iteration of the pvrmanager's main loop - CServiceBroker::GetPVRManager().TriggerRecordingsUpdate(client->GetID()); - }); + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) + { + // update recordings in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerRecordingsUpdate(client->GetID()); + }); } void CPVRClient::cb_trigger_channel_groups_update(void* kodiInstance) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - // update all channel groups in the next iteration of the pvrmanager's main loop - CServiceBroker::GetPVRManager().TriggerChannelGroupsUpdate(client->GetID()); - }); + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) + { + // update all channel groups in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerChannelGroupsUpdate(client->GetID()); + }); } void CPVRClient::cb_trigger_epg_update(void* kodiInstance, unsigned int iChannelUid) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - CServiceBroker::GetPVRManager().EpgContainer().UpdateRequest(client->GetID(), iChannelUid); - }); + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) { + CServiceBroker::GetPVRManager().EpgContainer().UpdateRequest( + client->GetID(), iChannelUid); + }); } void CPVRClient::cb_free_demux_packet(void* kodiInstance, DEMUX_PACKET* pPacket) { - HandleAddonCallback(__func__, kodiInstance, - [&](CPVRClient* client) { - CDVDDemuxUtils::FreeDemuxPacket(static_cast<DemuxPacket*>(pPacket)); - }, - true); + HandleAddonCallback( + __func__, kodiInstance, + [&](CPVRClient* client) + { CDVDDemuxUtils::FreeDemuxPacket(static_cast<DemuxPacket*>(pPacket)); }, + true); } DEMUX_PACKET* CPVRClient::cb_allocate_demux_packet(void* kodiInstance, int iDataSize) @@ -2250,47 +2338,53 @@ void CPVRClient::cb_connection_state_change(void* kodiInstance, PVR_CONNECTION_STATE newState, const char* strMessage) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - if (!strConnectionString) - { - CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); - return; - } + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) + { + if (!strConnectionString) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } - const PVR_CONNECTION_STATE prevState(client->GetConnectionState()); - if (prevState == newState) - return; + const PVR_CONNECTION_STATE prevState(client->GetConnectionState()); + if (prevState == newState) + return; - CLog::LogFC(LOGDEBUG, LOGPVR, "Connection state for client {} changed from {} to {}", - client->GetID(), prevState, newState); + CLog::LogFC(LOGDEBUG, LOGPVR, + "Connection state for client {} changed from {} to {}", + client->GetID(), prevState, newState); - client->SetConnectionState(newState); + client->SetConnectionState(newState); - std::string msg; - if (strMessage) - msg = strMessage; + std::string msg; + if (strMessage) + msg = strMessage; - CServiceBroker::GetPVRManager().ConnectionStateChange(client, std::string(strConnectionString), - newState, msg); - }); + CServiceBroker::GetPVRManager().ConnectionStateChange( + client, std::string(strConnectionString), newState, msg); + }); } void CPVRClient::cb_epg_event_state_change(void* kodiInstance, EPG_TAG* tag, EPG_EVENT_STATE newState) { - HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { - if (!tag) - { - CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); - return; - } - - // Note: channel data and epg id may not yet be available. Tag will be fully initialized later. - const std::shared_ptr<CPVREpgInfoTag> epgTag = - std::make_shared<CPVREpgInfoTag>(*tag, client->GetID(), nullptr, -1); - CServiceBroker::GetPVRManager().EpgContainer().UpdateFromClient(epgTag, newState); - }); + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) + { + if (!tag) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + // Note: channel data and epg id may not yet be available. Tag will be fully initialized later. + const std::shared_ptr<CPVREpgInfoTag> epgTag = + std::make_shared<CPVREpgInfoTag>(*tag, client->GetID(), nullptr, -1); + CServiceBroker::GetPVRManager().EpgContainer().UpdateFromClient(epgTag, + newState); + }); } class CCodecIds From 2c3c9c4406cab7a0bd46a8d8feaa2dc1721b4974 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 15 Aug 2024 19:50:20 +0200 Subject: [PATCH 392/651] [PVR] CPVRClient cleanup: Move CAddonEpgTag to unnamed namespace and align with the rest of CAddon* implementations. --- xbmc/pvr/addons/PVRClient.cpp | 182 +++++++++++++++++----------------- 1 file changed, 90 insertions(+), 92 deletions(-) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 3eb32c7590610..8864dc684d8a2 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -248,6 +248,91 @@ class CAddonTimer : public PVR_TIMER const std::string m_seriesLink; }; +class CAddonEpgTag : public EPG_TAG +{ +public: + explicit CAddonEpgTag(const CPVREpgInfoTag& tag) + : m_title(tag.Title()), + m_plotOutline(tag.PlotOutline()), + m_plot(tag.Plot()), + m_originalTitle(tag.OriginalTitle()), + m_cast(tag.DeTokenize(tag.Cast())), + m_director(tag.DeTokenize(tag.Directors())), + m_writer(tag.DeTokenize(tag.Writers())), + m_IMDBNumber(tag.IMDBNumber()), + m_episodeName(tag.EpisodeName()), + m_iconPath(tag.ClientIconPath()), + m_seriesLink(tag.SeriesLink()), + m_genreDescription(tag.GenreDescription()), + m_firstAired(GetFirstAired(tag)), + m_parentalRatingCode(tag.ParentalRatingCode()), + m_parentalRatingIcon(""), //! @todo + m_parentalRatingSource("") //! @todo + { + time_t t; + tag.StartAsUTC().GetAsTime(t); + startTime = t; + tag.EndAsUTC().GetAsTime(t); + endTime = t; + + iUniqueBroadcastId = tag.UniqueBroadcastID(); + iUniqueChannelId = tag.UniqueChannelID(); + iParentalRating = tag.ParentalRating(); + iSeriesNumber = tag.SeriesNumber(); + iEpisodeNumber = tag.EpisodeNumber(); + iEpisodePartNumber = tag.EpisodePart(); + iStarRating = tag.StarRating(); + iYear = tag.Year(); + iFlags = tag.Flags(); + iGenreType = tag.GenreType(); + iGenreSubType = tag.GenreSubType(); + strTitle = m_title.c_str(); + strPlotOutline = m_plotOutline.c_str(); + strPlot = m_plot.c_str(); + strOriginalTitle = m_originalTitle.c_str(); + strCast = m_cast.c_str(); + strDirector = m_director.c_str(); + strWriter = m_writer.c_str(); + strIMDBNumber = m_IMDBNumber.c_str(); + strEpisodeName = m_episodeName.c_str(); + strIconPath = m_iconPath.c_str(); + strSeriesLink = m_seriesLink.c_str(); + strGenreDescription = m_genreDescription.c_str(); + strFirstAired = m_firstAired.c_str(); + strParentalRatingCode = m_parentalRatingCode.c_str(); + strParentalRatingIcon = m_parentalRatingIcon.c_str(); + strParentalRatingSource = m_parentalRatingSource.c_str(); + } + + virtual ~CAddonEpgTag() = default; + +private: + static std::string GetFirstAired(const CPVREpgInfoTag& tag) + { + const CDateTime firstAired{tag.FirstAired()}; + if (firstAired.IsValid()) + return firstAired.GetAsW3CDate(); + return {}; + } + + const std::string m_title; + const std::string m_plotOutline; + const std::string m_plot; + const std::string m_originalTitle; + const std::string m_cast; + const std::string m_director; + const std::string m_writer; + const std::string m_IMDBNumber; + const std::string m_episodeName; + const std::string m_iconPath; + const std::string m_seriesLink; + const std::string m_genreDescription; + const std::string m_firstAired; + const std::string m_parentalRatingCode; + const std::string m_parentalRatingIcon; + const std::string m_parentalRatingSource; +}; + EDL::Edit ConvertAddonEdl(const PVR_EDL_ENTRY& entry) { EDL::Edit edit; @@ -800,93 +885,6 @@ PVR_ERROR CPVRClient::SetEPGMaxFutureDays(int iFutureDays) m_clientCapabilities.SupportsEPG()); } -// This class wraps an EPG_TAG (PVR Addon API struct) to ensure that the string members of -// that struct, which are const char pointers, stay valid until the EPG_TAG gets destructed. -// Please note that this struct is also used to transfer huge amount of EPG_TAGs from -// addon to Kodi. Thus, changing the struct to contain char arrays is not recommended, -// because this would lead to huge amount of string copies when transferring epg data -// from addon to Kodi. -class CAddonEpgTag : public EPG_TAG -{ -public: - CAddonEpgTag() = delete; - explicit CAddonEpgTag(const std::shared_ptr<const CPVREpgInfoTag>& kodiTag) - : m_strTitle(kodiTag->Title()), - m_strPlotOutline(kodiTag->PlotOutline()), - m_strPlot(kodiTag->Plot()), - m_strOriginalTitle(kodiTag->OriginalTitle()), - m_strCast(kodiTag->DeTokenize(kodiTag->Cast())), - m_strDirector(kodiTag->DeTokenize(kodiTag->Directors())), - m_strWriter(kodiTag->DeTokenize(kodiTag->Writers())), - m_strIMDBNumber(kodiTag->IMDBNumber()), - m_strEpisodeName(kodiTag->EpisodeName()), - m_strIconPath(kodiTag->ClientIconPath()), - m_strSeriesLink(kodiTag->SeriesLink()), - m_strGenreDescription(kodiTag->GenreDescription()), - m_strParentalRatingCode(kodiTag->ParentalRatingCode()), - m_strParentalRatingIcon(""), //! @todo - m_strParentalRatingSource("") //! @todo - { - time_t t; - kodiTag->StartAsUTC().GetAsTime(t); - startTime = t; - kodiTag->EndAsUTC().GetAsTime(t); - endTime = t; - - const CDateTime firstAired = kodiTag->FirstAired(); - if (firstAired.IsValid()) - m_strFirstAired = firstAired.GetAsW3CDate(); - - iUniqueBroadcastId = kodiTag->UniqueBroadcastID(); - iUniqueChannelId = kodiTag->UniqueChannelID(); - iParentalRating = kodiTag->ParentalRating(); - iSeriesNumber = kodiTag->SeriesNumber(); - iEpisodeNumber = kodiTag->EpisodeNumber(); - iEpisodePartNumber = kodiTag->EpisodePart(); - iStarRating = kodiTag->StarRating(); - iYear = kodiTag->Year(); - iFlags = kodiTag->Flags(); - iGenreType = kodiTag->GenreType(); - iGenreSubType = kodiTag->GenreSubType(); - strTitle = m_strTitle.c_str(); - strPlotOutline = m_strPlotOutline.c_str(); - strPlot = m_strPlot.c_str(); - strOriginalTitle = m_strOriginalTitle.c_str(); - strCast = m_strCast.c_str(); - strDirector = m_strDirector.c_str(); - strWriter = m_strWriter.c_str(); - strIMDBNumber = m_strIMDBNumber.c_str(); - strEpisodeName = m_strEpisodeName.c_str(); - strIconPath = m_strIconPath.c_str(); - strSeriesLink = m_strSeriesLink.c_str(); - strGenreDescription = m_strGenreDescription.c_str(); - strFirstAired = m_strFirstAired.c_str(); - strParentalRatingCode = m_strParentalRatingCode.c_str(); - strParentalRatingIcon = m_strParentalRatingIcon.c_str(); - strParentalRatingSource = m_strParentalRatingSource.c_str(); - } - - virtual ~CAddonEpgTag() = default; - -private: - std::string m_strTitle; - std::string m_strPlotOutline; - std::string m_strPlot; - std::string m_strOriginalTitle; - std::string m_strCast; - std::string m_strDirector; - std::string m_strWriter; - std::string m_strIMDBNumber; - std::string m_strEpisodeName; - std::string m_strIconPath; - std::string m_strSeriesLink; - std::string m_strGenreDescription; - std::string m_strFirstAired; - std::string m_strParentalRatingCode; - std::string m_strParentalRatingIcon; - std::string m_strParentalRatingSource; -}; - PVR_ERROR CPVRClient::IsRecordable(const std::shared_ptr<const CPVREpgInfoTag>& tag, bool& bIsRecordable) const { @@ -894,7 +892,7 @@ PVR_ERROR CPVRClient::IsRecordable(const std::shared_ptr<const CPVREpgInfoTag>& __func__, [tag, &bIsRecordable](const AddonInstance* addon) { - CAddonEpgTag addonTag(tag); + CAddonEpgTag addonTag(*tag); return addon->toAddon->IsEPGTagRecordable(addon, &addonTag, &bIsRecordable); }, m_clientCapabilities.SupportsRecordings() && m_clientCapabilities.SupportsEPG()); @@ -907,7 +905,7 @@ PVR_ERROR CPVRClient::IsPlayable(const std::shared_ptr<const CPVREpgInfoTag>& ta __func__, [tag, &bIsPlayable](const AddonInstance* addon) { - CAddonEpgTag addonTag(tag); + CAddonEpgTag addonTag(*tag); return addon->toAddon->IsEPGTagPlayable(addon, &addonTag, &bIsPlayable); }, m_clientCapabilities.SupportsEPG()); @@ -930,7 +928,7 @@ PVR_ERROR CPVRClient::GetEpgTagStreamProperties(const std::shared_ptr<const CPVR return DoAddonCall(__func__, [&tag, &props](const AddonInstance* addon) { - CAddonEpgTag addonTag(tag); + CAddonEpgTag addonTag(*tag); PVR_NAMED_VALUE** property_array{nullptr}; unsigned int size{0}; @@ -952,7 +950,7 @@ PVR_ERROR CPVRClient::GetEpgTagEdl(const std::shared_ptr<const CPVREpgInfoTag>& __func__, [&epgTag, &edls](const AddonInstance* addon) { - CAddonEpgTag addonTag(epgTag); + CAddonEpgTag addonTag(*epgTag); PVR_EDL_ENTRY** edl_array{nullptr}; unsigned int size{0}; @@ -1904,7 +1902,7 @@ PVR_ERROR CPVRClient::CallEpgTagMenuHook(const CPVRClientMenuHook& hook, return DoAddonCall(__func__, [&hook, &tag](const AddonInstance* addon) { - CAddonEpgTag addonTag(tag); + CAddonEpgTag addonTag(*tag); PVR_MENUHOOK menuHook; menuHook.category = PVR_MENUHOOK_EPG; From 0fd6b2bd0ab7246cec879175f4addcd98b6d31e0 Mon Sep 17 00:00:00 2001 From: MikeSiLVO <mikesilvo164@gmail.com> Date: Sat, 17 Aug 2024 07:23:53 -0400 Subject: [PATCH 393/651] Estuary cleanup (#25584) * Can't align to top * Undefined font (font16) * Don't think you can use align or aligny in a progress control * No control ids in list 1103 * Nothing ondown from PVR osd buttons * Lowercase false * Remove unused variable * Remove unused include --- .../skin.estuary/xml/DialogVideoManager.xml | 1 - addons/skin.estuary/xml/GameOSD.xml | 1 - addons/skin.estuary/xml/Includes.xml | 4 +-- .../skin.estuary/xml/Includes_Animations.xml | 16 +++++----- addons/skin.estuary/xml/Includes_Buttons.xml | 30 ------------------- .../xml/Includes_DialogSelect.xml | 9 +++--- addons/skin.estuary/xml/Includes_PVR.xml | 4 --- addons/skin.estuary/xml/MusicOSD.xml | 8 ++--- addons/skin.estuary/xml/SettingsCategory.xml | 2 +- addons/skin.estuary/xml/Variables.xml | 4 --- addons/skin.estuary/xml/VideoOSD.xml | 11 +++---- 11 files changed, 23 insertions(+), 67 deletions(-) diff --git a/addons/skin.estuary/xml/DialogVideoManager.xml b/addons/skin.estuary/xml/DialogVideoManager.xml index a02f3398a4578..ceb73694a9ad5 100644 --- a/addons/skin.estuary/xml/DialogVideoManager.xml +++ b/addons/skin.estuary/xml/DialogVideoManager.xml @@ -84,7 +84,6 @@ <height>565</height> <onleft condition="Integer.IsGreater(Container(50).NumItems,0)">100</onleft> <itemgap>dialogbuttons_itemgap</itemgap> - <align>top</align> <scrolltime tween="quadratic">200</scrolltime> <include content="DefaultDialogButton"> <param name="id" value="21" /> diff --git a/addons/skin.estuary/xml/GameOSD.xml b/addons/skin.estuary/xml/GameOSD.xml index b2f5fd429e6d7..1d4c0dc8b93db 100644 --- a/addons/skin.estuary/xml/GameOSD.xml +++ b/addons/skin.estuary/xml/GameOSD.xml @@ -77,7 +77,6 @@ <control type="group" id="2000"> <top>80</top> <control type="list" id="1103"> - <defaultcontrol always="true">2101</defaultcontrol> <height>560</height> <orientation>vertical</orientation> <itemlayout condition="!Control.IsVisible(2200)" width="700" height="80"> diff --git a/addons/skin.estuary/xml/Includes.xml b/addons/skin.estuary/xml/Includes.xml index 8dd226ec45710..7675dae0e5e86 100644 --- a/addons/skin.estuary/xml/Includes.xml +++ b/addons/skin.estuary/xml/Includes.xml @@ -106,7 +106,7 @@ <font></font> </include> <include name="RatingCircle"> - <param name="animation">False</param> + <param name="animation">false</param> <definition> <control type="group"> <animation effect="fade" time="0" condition="$PARAM[animation]">VisibleChange</animation> @@ -1299,7 +1299,7 @@ </definition> </include> <include name="BottomBar"> - <param name="info_visible">False</param> + <param name="info_visible">false</param> <definition> <control type="group"> <animation effect="slide" end="0,112" time="300" tween="sine" easing="inout" condition="$EXP[infodialog_active]">conditional</animation> diff --git a/addons/skin.estuary/xml/Includes_Animations.xml b/addons/skin.estuary/xml/Includes_Animations.xml index 1b02def93bcc1..a317b31c9cbbc 100644 --- a/addons/skin.estuary/xml/Includes_Animations.xml +++ b/addons/skin.estuary/xml/Includes_Animations.xml @@ -54,31 +54,31 @@ <include condition="!Skin.HasSetting(no_slide_animations)">Vis_FadeSlide_Right</include> </include> <include name="Animation_TopSlide"> - <animation type="WindowOpen" reversible="False"> + <animation type="WindowOpen" reversible="false"> <effect type="fade" start="0" end="100" time="300"/> <effect type="slide" start="0,-200" end="0,0" time="300" tween="cubic" easing="out" /> </animation> - <animation type="WindowClose" reversible="False"> + <animation type="WindowClose" reversible="false"> <effect type="fade" start="100" end="0" time="300"/> <effect type="slide" start="0,0" end="0,-200" time="300" tween="cubic" easing="out" /> </animation> </include> <include name="Animation_BottomSlide"> - <animation type="WindowOpen" reversible="False"> + <animation type="WindowOpen" reversible="false"> <effect type="fade" start="0" end="100" time="300"/> <effect type="slide" start="0,200" end="0,0" time="300" tween="cubic" easing="out" /> </animation> - <animation type="WindowClose" reversible="False"> + <animation type="WindowClose" reversible="false"> <effect type="fade" start="100" end="0" time="300"/> <effect type="slide" start="0,0" end="0,200" time="300" tween="cubic" easing="out" /> </animation> </include> <include name="Vis_FadeSlide_Right"> - <animation type="Visible" reversible="False"> + <animation type="Visible" reversible="false"> <effect type="fade" start="0" end="100" time="300" tween="sine" easing="out"/> <effect type="slide" start="320" end="0" time="400" tween="cubic" easing="out" /> </animation> - <animation type="Hidden" reversible="False"> + <animation type="Hidden" reversible="false"> <effect type="fade" start="100" end="0" time="300" tween="sine" easing="out" /> <effect type="slide" start="0" end="320" time="300" tween="cubic" easing="out" /> </animation> @@ -88,11 +88,11 @@ <include condition="!Skin.HasSetting(no_slide_animations)">Vis_FadeSlide_Left</include> </include> <include name="Vis_FadeSlide_Left"> - <animation type="Visible" reversible="False"> + <animation type="Visible" reversible="false"> <effect type="fade" start="0" end="100" time="300" tween="sine" easing="out" /> <effect type="slide" start="-320" end="0" time="400" tween="cubic" easing="out" /> </animation> - <animation type="Hidden" reversible="False"> + <animation type="Hidden" reversible="false"> <effect type="fade" start="100" end="0" time="300" tween="sine" easing="out" /> <effect type="slide" start="0" end="-320" time="300" tween="cubic" easing="out" /> </animation> diff --git a/addons/skin.estuary/xml/Includes_Buttons.xml b/addons/skin.estuary/xml/Includes_Buttons.xml index e7deab53c4ff0..f8ace0d34d2db 100644 --- a/addons/skin.estuary/xml/Includes_Buttons.xml +++ b/addons/skin.estuary/xml/Includes_Buttons.xml @@ -126,36 +126,6 @@ </control> </definition> </include> - <include name="DialogToggleButton"> - <param name="width">300</param> - <param name="height">100</param> - <param name="wrapmultiline">false</param> - <param name="font">font25_title</param> - <param name="onclick"></param> - <param name="visible">true</param> - <param name="enable">true</param> - <param name="usealttexture">false</param> - <definition> - <control type="togglebutton" id="$PARAM[id]"> - <width>$PARAM[width]</width> - <height>$PARAM[height]</height> - <label>$PARAM[label]</label> - <font>$PARAM[font]</font> - <textoffsetx>20</textoffsetx> - <onclick>noop</onclick> - <wrapmultiline>$PARAM[wrapmultiline]</wrapmultiline> - <align>center</align> - <aligny>center</aligny> - <texturefocus border="40" colordiffuse="button_focus">buttons/dialogbutton-fo.png</texturefocus> - <texturenofocus border="40">buttons/dialogbutton-nofo.png</texturenofocus> - <alttexturefocus border="40" colordiffuse="button_focus">buttons/dialogbutton-fo.png</alttexturefocus> - <alttexturenofocus border="40" colordiffuse="button_alt_focus">buttons/dialogbutton-fo.png</alttexturenofocus> - <usealttexture>$PARAM[usealttexture]</usealttexture> - <visible>$PARAM[visible]</visible> - <enable>$PARAM[enable]</enable> - </control> - </definition> - </include> <include name="KeyboardButton"> <width>120</width> <height>120</height> diff --git a/addons/skin.estuary/xml/Includes_DialogSelect.xml b/addons/skin.estuary/xml/Includes_DialogSelect.xml index 3ee9240ba3b52..7ca4f0d459923 100644 --- a/addons/skin.estuary/xml/Includes_DialogSelect.xml +++ b/addons/skin.estuary/xml/Includes_DialogSelect.xml @@ -61,7 +61,7 @@ <top>22</top> <width>80</width> <height>80</height> - <aspectratio align="top">keep</aspectratio> + <aspectratio>keep</aspectratio> <aligny>center</aligny> <texture>icons/pvr/timers/recording.png</texture> <visible>ListItem.Property(PVR.IsRecordingTimer)</visible> @@ -71,7 +71,7 @@ <top>22</top> <width>80</width> <height>80</height> - <aspectratio align="top">keep</aspectratio> + <aspectratio>keep</aspectratio> <aligny>center</aligny> <texture>icons/pvr/timers/bell.png</texture> <visible>ListItem.Property(PVR.IsRemindingTimer)</visible> @@ -118,7 +118,7 @@ <top>22</top> <width>80</width> <height>80</height> - <aspectratio align="top">keep</aspectratio> + <aspectratio>keep</aspectratio> <aligny>center</aligny> <texture>icons/pvr/timers/recording.png</texture> <visible>ListItem.Property(PVR.IsRecordingTimer)</visible> @@ -128,7 +128,7 @@ <top>22</top> <width>80</width> <height>80</height> - <aspectratio align="top">keep</aspectratio> + <aspectratio>keep</aspectratio> <aligny>center</aligny> <texture>icons/pvr/timers/bell.png</texture> <visible>ListItem.Property(PVR.IsRemindingTimer)</visible> @@ -342,7 +342,6 @@ <description>Label for Saved with: text</description> <top>14</top> <height>20</height> - <font>font16</font> <shadowcolor>text_shadow</shadowcolor> <label>35255</label> <align>center</align> diff --git a/addons/skin.estuary/xml/Includes_PVR.xml b/addons/skin.estuary/xml/Includes_PVR.xml index c482d2b7086e4..f2a80415259a6 100644 --- a/addons/skin.estuary/xml/Includes_PVR.xml +++ b/addons/skin.estuary/xml/Includes_PVR.xml @@ -108,8 +108,6 @@ <top>66</top> <width>70</width> <height>12</height> - <align>center</align> - <aligny>center</aligny> <colordiffuse>88FFFFFF</colordiffuse> <visible>ListItem.HasEpg + !$PARAM[has_info_icon]</visible> <info>ListItem.Progress</info> @@ -140,8 +138,6 @@ <top>66</top> <width>70</width> <height>12</height> - <align>center</align> - <aligny>center</aligny> <midtexture border="3">progress/texturebg_white.png</midtexture> <visible>ListItem.HasEpg + !$PARAM[has_info_icon]</visible> <info>ListItem.Progress</info> diff --git a/addons/skin.estuary/xml/MusicOSD.xml b/addons/skin.estuary/xml/MusicOSD.xml index 9b97121a19d20..0fd35b92e45fe 100644 --- a/addons/skin.estuary/xml/MusicOSD.xml +++ b/addons/skin.estuary/xml/MusicOSD.xml @@ -260,18 +260,18 @@ <control type="group"> <bottom>0</bottom> <height>120</height> - <animation type="WindowOpen" condition="!Player.ShowInfo" reversible="False"> + <animation type="WindowOpen" condition="!Player.ShowInfo" reversible="false"> <effect type="fade" start="0" end="100" time="300"/> <effect type="slide" start="0,200" end="0,0" time="300" tween="cubic" easing="out" /> </animation> - <animation type="WindowClose" condition="!Player.ShowInfo" reversible="False"> + <animation type="WindowClose" condition="!Player.ShowInfo" reversible="false"> <effect type="fade" start="100" end="0" time="300"/> <effect type="slide" start="0,0" end="0,200" time="300" tween="cubic" easing="out" /> </animation> - <animation type="WindowOpen" condition="Player.ShowInfo" reversible="False"> + <animation type="WindowOpen" condition="Player.ShowInfo" reversible="false"> <effect type="fade" start="0" end="100" time="300"/> </animation> - <animation type="WindowClose" condition="Player.ShowInfo" reversible="False"> + <animation type="WindowClose" condition="Player.ShowInfo" reversible="false"> <effect type="fade" start="100" end="0" time="300"/> </animation> <control type="button" id="87"> diff --git a/addons/skin.estuary/xml/SettingsCategory.xml b/addons/skin.estuary/xml/SettingsCategory.xml index c14b63584beca..cd305a168299a 100644 --- a/addons/skin.estuary/xml/SettingsCategory.xml +++ b/addons/skin.estuary/xml/SettingsCategory.xml @@ -184,7 +184,7 @@ </control> <control type="label" id="2"> <description>breadcrumbs label</description> - <visible>False</visible> + <visible>false</visible> </control> </controls> </window> diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 6a9b291a76059..78568c61b3b4e 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -542,10 +542,6 @@ <variable name="BreadcrumbsGameVar"> <value>$LOCALIZE[15016]</value> </variable> - <variable name="RepeatButtonColordiffuseVar"> - <value condition="Control.HasFocus(704)">button_focus</value> - <value>FFFFFFFF</value> - </variable> <variable name="PVRChannelMgrHeader"> <value condition="!String.IsEmpty(Window.Property(IsRadio))">$LOCALIZE[19199] - $LOCALIZE[19024]</value> <value>$LOCALIZE[19199] - $LOCALIZE[19023]</value> diff --git a/addons/skin.estuary/xml/VideoOSD.xml b/addons/skin.estuary/xml/VideoOSD.xml index 6c25ea3a86c9c..41e9fdc3f364f 100644 --- a/addons/skin.estuary/xml/VideoOSD.xml +++ b/addons/skin.estuary/xml/VideoOSD.xml @@ -141,9 +141,6 @@ <scrolltime tween="sine">200</scrolltime> <orientation>horizontal</orientation> <onup>87</onup> - <ondown condition="Control.HasFocus(70043)">11104</ondown> - <ondown condition="Control.HasFocus(704)">12104</ondown> - <ondown condition="Control.HasFocus(255)">13103</ondown> <onleft>608</onleft> <onright>600</onright> <control type="radiobutton" id="804"> @@ -226,18 +223,18 @@ </control> <control type="group" id="6000"> <top>60</top> - <animation type="WindowOpen" condition="!Window.IsVisible(fullscreeninfo)" reversible="False"> + <animation type="WindowOpen" condition="!Window.IsVisible(fullscreeninfo)" reversible="false"> <effect type="fade" start="0" end="100" time="300"/> <effect type="slide" start="0,200" end="0,0" time="300" tween="cubic" easing="out" /> </animation> - <animation type="WindowClose" condition="!Window.IsVisible(fullscreeninfo)" reversible="False"> + <animation type="WindowClose" condition="!Window.IsVisible(fullscreeninfo)" reversible="false"> <effect type="fade" start="100" end="0" time="300"/> <effect type="slide" start="0,0" end="0,200" time="300" tween="cubic" easing="out" /> </animation> - <animation type="WindowOpen" condition="Window.IsVisible(fullscreeninfo)" reversible="False"> + <animation type="WindowOpen" condition="Window.IsVisible(fullscreeninfo)" reversible="false"> <effect type="fade" start="0" end="100" time="300"/> </animation> - <animation type="WindowClose" condition="Window.IsVisible(fullscreeninfo)" reversible="False"> + <animation type="WindowClose" condition="Window.IsVisible(fullscreeninfo)" reversible="false"> <effect type="fade" start="100" end="0" time="300"/> </animation> <visible>Player.SeekEnabled</visible> From 3c57aae86c0d0f394148e8083ff1d4ce35363314 Mon Sep 17 00:00:00 2001 From: DeltaMikeCharlie <127641886+DeltaMikeCharlie@users.noreply.github.com> Date: Tue, 20 Aug 2024 05:53:32 +1000 Subject: [PATCH 394/651] [PVR][addons][guilib] Add support for PVR client add-ons supplying Parental Rating Icons, extend Rating support for Recordings --- xbmc/GUIInfoManager.cpp | 21 +++++ .../include/kodi/addon-instance/pvr/EPG.h | 2 +- xbmc/guilib/guiinfo/GUIInfoLabels.h | 2 + xbmc/pvr/epg/EpgDatabase.cpp | 90 ++++++++++--------- xbmc/pvr/epg/EpgDatabase.h | 2 +- xbmc/pvr/epg/EpgInfoTag.cpp | 35 +++++--- xbmc/pvr/epg/EpgInfoTag.h | 21 ++++- xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 38 +++++++- xbmc/pvr/recordings/PVRRecording.cpp | 52 ++++++++++- xbmc/pvr/recordings/PVRRecording.h | 28 ++++++ 10 files changed, 229 insertions(+), 62 deletions(-) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index b7689993152e9..e6a377efc2977 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -6794,6 +6794,25 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @skinning_v21 **[New Infolabel]** \link ListItem_ParentalRatingCode `ListItem.ParentalRatingCode`\endlink /// <p> /// } +/// \table_row3{ <b>`ListItem.ParentalRatingIcon`</b>, +/// \anchor ListItem_ParentalRatingIcon +/// _string_, +/// @return The parental rating icon path of the list item (PVR). +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link ListItem_ParentalRatingIcon `ListItem.ParentalRatingIcon`\endlink +/// <p> +/// } +/// \table_row3{ <b>`ListItem.ParentalRatingSource`</b>, +/// \anchor ListItem_ParentalRatingSource +/// _string_, +/// @return The source used to determine the parental rating of the list item (PVR). +/// Values could include the Country alpha-3 code or the name/abbreviation +/// of the authority issuing the rating code. Can be used with +/// the \link ListItem_ParentalRatingCode `ParentalRatingCode`\endlink for skin-derived icons if required. +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link ListItem_ParentalRatingSource `ListItem.ParentalRatingSource`\endlink +/// <p> +/// } /// \table_row3{ <b>`ListItem.CurrentItem`</b>, /// \anchor ListItem_CurrentItem /// _string_, @@ -7214,6 +7233,8 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "property", LISTITEM_PROPERTY }, { "parentalrating", LISTITEM_PARENTAL_RATING }, { "parentalratingcode", LISTITEM_PARENTAL_RATING_CODE }, + { "parentalratingicon", LISTITEM_PARENTAL_RATING_ICON }, + { "parentalratingsource", LISTITEM_PARENTAL_RATING_SOURCE }, { "currentitem", LISTITEM_CURRENTITEM }, { "isnew", LISTITEM_IS_NEW }, { "isboxset", LISTITEM_IS_BOXSET }, diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h index f0610402cfce3..1252bfbbb25bc 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h @@ -368,7 +368,7 @@ class PVREPGTag : public DynamicCStructHdl<PVREPGTag, EPG_TAG> m_cStructure->iParentalRating = parentalRating; } - /// @brief To get with @ref SetParentalRatinge changed values. + /// @brief To get with @ref SetParentalRating changed values. unsigned int GetParentalRating() const { return m_cStructure->iParentalRating; } /// @brief **optional**\n diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index c801a1eb33833..997ff42dff388 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -989,6 +989,8 @@ static constexpr unsigned int SYSTEM_LOCALE = 1012; #define LISTITEM_PVR_INSTANCE_NAME (LISTITEM_START + 218) #define LISTITEM_CHANNEL_LOGO (LISTITEM_START + 219) #define LISTITEM_PVR_GROUP_ORIGIN (LISTITEM_START + 220) +#define LISTITEM_PARENTAL_RATING_ICON (LISTITEM_START + 221) +#define LISTITEM_PARENTAL_RATING_SOURCE (LISTITEM_START + 222) #define LISTITEM_END (LISTITEM_START + 2500) diff --git a/xbmc/pvr/epg/EpgDatabase.cpp b/xbmc/pvr/epg/EpgDatabase.cpp index 8f257ae89690c..c0550f467ab65 100644 --- a/xbmc/pvr/epg/EpgDatabase.cpp +++ b/xbmc/pvr/epg/EpgDatabase.cpp @@ -66,38 +66,38 @@ void CPVREpgDatabase::CreateTables() ); CLog::LogFC(LOGDEBUG, LOGEPG, "Creating table 'epgtags'"); - m_pDS->exec( - "CREATE TABLE epgtags (" - "idBroadcast integer primary key, " - "iBroadcastUid integer, " - "idEpg integer, " - "sTitle varchar(128), " - "sPlotOutline text, " - "sPlot text, " - "sOriginalTitle varchar(128), " - "sCast varchar(255), " - "sDirector varchar(255), " - "sWriter varchar(255), " - "iYear integer, " - "sIMDBNumber varchar(50), " - "sIconPath varchar(255), " - "iStartTime integer, " - "iEndTime integer, " - "iGenreType integer, " - "iGenreSubType integer, " - "sGenre varchar(128), " - "sFirstAired varchar(32), " - "iParentalRating integer, " - "iStarRating integer, " - "iSeriesId integer, " - "iEpisodeId integer, " - "iEpisodePart integer, " - "sEpisodeName varchar(128), " - "iFlags integer, " - "sSeriesLink varchar(255), " - "sParentalRatingCode varchar(64)" - ")" - ); + m_pDS->exec("CREATE TABLE epgtags (" + "idBroadcast integer primary key, " + "iBroadcastUid integer, " + "idEpg integer, " + "sTitle varchar(128), " + "sPlotOutline text, " + "sPlot text, " + "sOriginalTitle varchar(128), " + "sCast varchar(255), " + "sDirector varchar(255), " + "sWriter varchar(255), " + "iYear integer, " + "sIMDBNumber varchar(50), " + "sIconPath varchar(255), " + "iStartTime integer, " + "iEndTime integer, " + "iGenreType integer, " + "iGenreSubType integer, " + "sGenre varchar(128), " + "sFirstAired varchar(32), " + "iParentalRating integer, " + "iStarRating integer, " + "iSeriesId integer, " + "iEpisodeId integer, " + "iEpisodePart integer, " + "sEpisodeName varchar(128), " + "iFlags integer, " + "sSeriesLink varchar(255), " + "sParentalRatingCode varchar(64)," + "sParentalRatingIcon varchar(512)," + "sParentalRatingSource varchar(128)" + ")"); CLog::LogFC(LOGDEBUG, LOGEPG, "Creating table 'lastepgscan'"); m_pDS->exec("CREATE TABLE lastepgscan (" @@ -331,6 +331,12 @@ void CPVREpgDatabase::UpdateTables(int iVersion) m_pDS->exec("ALTER TABLE savedsearches ADD sIconPath varchar(255);"); m_pDS->exec("UPDATE savedsearches SET sIconPath = ''"); } + + if (iVersion < 18) + { + m_pDS->exec("ALTER TABLE epgtags ADD sParentalRatingIcon varchar(512);"); + m_pDS->exec("ALTER TABLE epgtags ADD sParentalRatingSource varchar(128);"); + } } bool CPVREpgDatabase::DeleteEpg() @@ -449,7 +455,7 @@ std::shared_ptr<CPVREpgInfoTag> CPVREpgDatabase::CreateEpgTag( newTag->m_writers = newTag->Tokenize(m_pDS->fv("sWriter").get_asString()); newTag->m_iYear = m_pDS->fv("iYear").get_asInt(); newTag->m_strIMDBNumber = m_pDS->fv("sIMDBNumber").get_asString(); - newTag->m_iParentalRating = m_pDS->fv("iParentalRating").get_asInt(); + newTag->m_parentalRating = m_pDS->fv("iParentalRating").get_asInt(); newTag->m_iStarRating = m_pDS->fv("iStarRating").get_asInt(); newTag->m_iEpisodeNumber = m_pDS->fv("iEpisodeId").get_asInt(); newTag->m_iEpisodePart = m_pDS->fv("iEpisodePart").get_asInt(); @@ -457,7 +463,9 @@ std::shared_ptr<CPVREpgInfoTag> CPVREpgDatabase::CreateEpgTag( newTag->m_iSeriesNumber = m_pDS->fv("iSeriesId").get_asInt(); newTag->m_iFlags = m_pDS->fv("iFlags").get_asInt(); newTag->m_strSeriesLink = m_pDS->fv("sSeriesLink").get_asString(); - newTag->m_strParentalRatingCode = m_pDS->fv("sParentalRatingCode").get_asString(); + newTag->m_parentalRatingCode = m_pDS->fv("sParentalRatingCode").get_asString(); + newTag->m_parentalRatingIcon = m_pDS->fv("sParentalRatingIcon").get_asString(); + newTag->m_parentalRatingSource = m_pDS->fv("sParentalRatingSource").get_asString(); newTag->m_iGenreType = m_pDS->fv("iGenreType").get_asInt(); newTag->m_iGenreSubType = m_pDS->fv("iGenreSubType").get_asInt(); newTag->m_strGenreDescription = m_pDS->fv("sGenre").get_asString(); @@ -1237,9 +1245,9 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag) "sIconPath, iGenreType, iGenreSubType, sGenre, sFirstAired, iParentalRating, iStarRating, " "iSeriesId, " "iEpisodeId, iEpisodePart, sEpisodeName, iFlags, sSeriesLink, sParentalRatingCode, " - "iBroadcastUid) " + "iBroadcastUid, sParentalRatingIcon, sParentalRatingSource) " "VALUES (%u, %u, %u, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %i, '%s', '%s', %i, %i, " - "'%s', '%s', %i, %i, %i, %i, %i, '%s', %i, '%s', '%s', %i);", + "'%s', '%s', %i, %i, %i, %i, %i, '%s', %i, '%s', '%s', %i, '%s', '%s');", tag.EpgID(), static_cast<unsigned int>(iStartTime), static_cast<unsigned int>(iEndTime), tag.Title().c_str(), tag.PlotOutline().c_str(), tag.Plot().c_str(), tag.OriginalTitle().c_str(), tag.DeTokenize(tag.Cast()).c_str(), @@ -1248,7 +1256,8 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag) tag.GenreDescription().c_str(), sFirstAired.c_str(), tag.ParentalRating(), tag.StarRating(), tag.SeriesNumber(), tag.EpisodeNumber(), tag.EpisodePart(), tag.EpisodeName().c_str(), tag.Flags(), tag.SeriesLink().c_str(), tag.ParentalRatingCode().c_str(), - tag.UniqueBroadcastID()); + tag.UniqueBroadcastID(), tag.ParentalRatingIcon().c_str(), + tag.ParentalRatingSource().c_str()); } else { @@ -1259,9 +1268,9 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag) "sIconPath, iGenreType, iGenreSubType, sGenre, sFirstAired, iParentalRating, iStarRating, " "iSeriesId, " "iEpisodeId, iEpisodePart, sEpisodeName, iFlags, sSeriesLink, sParentalRatingCode, " - "iBroadcastUid, idBroadcast) " + "iBroadcastUid, idBroadcast, sParentalRatingIcon, sParentalRatingSource) " "VALUES (%u, %u, %u, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %i, '%s', '%s', %i, %i, " - "'%s', '%s', %i, %i, %i, %i, %i, '%s', %i, '%s', '%s', %i, %i);", + "'%s', '%s', %i, %i, %i, %i, %i, '%s', %i, '%s', '%s', %i, %i, '%s', '%s');", tag.EpgID(), static_cast<unsigned int>(iStartTime), static_cast<unsigned int>(iEndTime), tag.Title().c_str(), tag.PlotOutline().c_str(), tag.Plot().c_str(), tag.OriginalTitle().c_str(), tag.DeTokenize(tag.Cast()).c_str(), @@ -1270,7 +1279,8 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag) tag.GenreDescription().c_str(), sFirstAired.c_str(), tag.ParentalRating(), tag.StarRating(), tag.SeriesNumber(), tag.EpisodeNumber(), tag.EpisodePart(), tag.EpisodeName().c_str(), tag.Flags(), tag.SeriesLink().c_str(), tag.ParentalRatingCode().c_str(), - tag.UniqueBroadcastID(), iBroadcastId); + tag.UniqueBroadcastID(), iBroadcastId, tag.ParentalRatingIcon().c_str(), + tag.ParentalRatingSource().c_str()); } QueueInsertQuery(strQuery); diff --git a/xbmc/pvr/epg/EpgDatabase.h b/xbmc/pvr/epg/EpgDatabase.h index 32673b8e58622..cf114a105053a 100644 --- a/xbmc/pvr/epg/EpgDatabase.h +++ b/xbmc/pvr/epg/EpgDatabase.h @@ -66,7 +66,7 @@ namespace PVR * @brief Get the minimal database version that is required to operate correctly. * @return The minimal database version. */ - int GetSchemaVersion() const override { return 17; } + int GetSchemaVersion() const override { return 18; } /*! * @brief Get the default sqlite database filename. diff --git a/xbmc/pvr/epg/EpgInfoTag.cpp b/xbmc/pvr/epg/EpgInfoTag.cpp index 873fd27b0cd44..9f4f822376e1f 100644 --- a/xbmc/pvr/epg/EpgInfoTag.cpp +++ b/xbmc/pvr/epg/EpgInfoTag.cpp @@ -68,7 +68,7 @@ CPVREpgInfoTag::CPVREpgInfoTag(const EPG_TAG& data, int iEpgID) : m_iGenreType(data.iGenreType), m_iGenreSubType(data.iGenreSubType), - m_iParentalRating(data.iParentalRating), + m_parentalRating(data.iParentalRating), m_iStarRating(data.iStarRating), m_iSeriesNumber(data.iSeriesNumber), m_iEpisodeNumber(data.iEpisodeNumber), @@ -130,7 +130,11 @@ CPVREpgInfoTag::CPVREpgInfoTag(const EPG_TAG& data, if (data.strSeriesLink) m_strSeriesLink = data.strSeriesLink; if (data.strParentalRatingCode) - m_strParentalRatingCode = data.strParentalRatingCode; + m_parentalRatingCode = data.strParentalRatingCode; + if (data.strParentalRatingIcon) + m_parentalRatingIcon = data.strParentalRatingIcon; + if (data.strParentalRatingSource) + m_parentalRatingSource = data.strParentalRatingSource; } void CPVREpgInfoTag::SetChannelData(const std::shared_ptr<CPVREpgChannelData>& data) @@ -167,8 +171,10 @@ void CPVREpgInfoTag::Serialize(CVariant& value) const std::unique_lock<CCriticalSection> lock(m_critSection); value["broadcastid"] = m_iDatabaseID; // Use DB id here as it is unique across PVR clients value["channeluid"] = m_channelData->UniqueClientChannelId(); - value["parentalrating"] = m_iParentalRating; - value["parentalratingcode"] = m_strParentalRatingCode; + value["parentalrating"] = m_parentalRating; + value["parentalratingcode"] = m_parentalRatingCode; + value["parentalratingicon"] = m_parentalRatingIcon; + value["parentalratingsource"] = m_parentalRatingSource; value["rating"] = m_iStarRating; value["title"] = m_strTitle; value["plotoutline"] = m_strPlotOutline; @@ -398,9 +404,9 @@ CDateTime CPVREpgInfoTag::FirstAired() const return m_firstAired; } -int CPVREpgInfoTag::ParentalRating() const +unsigned int CPVREpgInfoTag::ParentalRating() const { - return m_iParentalRating; + return m_parentalRating; } int CPVREpgInfoTag::StarRating() const @@ -449,11 +455,12 @@ bool CPVREpgInfoTag::Update(const CPVREpgInfoTag& tag, bool bUpdateBroadcastId / m_startTime != tag.m_startTime || m_endTime != tag.m_endTime || m_iGenreType != tag.m_iGenreType || m_iGenreSubType != tag.m_iGenreSubType || m_strGenreDescription != tag.m_strGenreDescription || m_firstAired != tag.m_firstAired || - m_iParentalRating != tag.m_iParentalRating || - m_strParentalRatingCode != tag.m_strParentalRatingCode || - m_iStarRating != tag.m_iStarRating || m_iEpisodeNumber != tag.m_iEpisodeNumber || - m_iEpisodePart != tag.m_iEpisodePart || m_iSeriesNumber != tag.m_iSeriesNumber || - m_strEpisodeName != tag.m_strEpisodeName || + m_parentalRating != tag.m_parentalRating || + m_parentalRatingCode != tag.m_parentalRatingCode || + m_parentalRatingIcon != tag.m_parentalRatingIcon || + m_parentalRatingSource != tag.m_parentalRatingSource || m_iStarRating != tag.m_iStarRating || + m_iEpisodeNumber != tag.m_iEpisodeNumber || m_iEpisodePart != tag.m_iEpisodePart || + m_iSeriesNumber != tag.m_iSeriesNumber || m_strEpisodeName != tag.m_strEpisodeName || m_iUniqueBroadcastID != tag.m_iUniqueBroadcastID || m_iEpgID != tag.m_iEpgID || m_genre != tag.m_genre || m_iconPath != tag.m_iconPath || m_iFlags != tag.m_iFlags || m_strSeriesLink != tag.m_strSeriesLink || m_channelData != tag.m_channelData); @@ -485,8 +492,10 @@ bool CPVREpgInfoTag::Update(const CPVREpgInfoTag& tag, bool bUpdateBroadcastId / m_iFlags = tag.m_iFlags; m_strSeriesLink = tag.m_strSeriesLink; m_firstAired = tag.m_firstAired; - m_iParentalRating = tag.m_iParentalRating; - m_strParentalRatingCode = tag.m_strParentalRatingCode; + m_parentalRating = tag.m_parentalRating; + m_parentalRatingCode = tag.m_parentalRatingCode; + m_parentalRatingIcon = tag.m_parentalRatingIcon; + m_parentalRatingSource = tag.m_parentalRatingSource; m_iStarRating = tag.m_iStarRating; m_iEpisodeNumber = tag.m_iEpisodeNumber; m_iEpisodePart = tag.m_iEpisodePart; diff --git a/xbmc/pvr/epg/EpgInfoTag.h b/xbmc/pvr/epg/EpgInfoTag.h index 4e3a5ee54085d..55735aff98cd7 100644 --- a/xbmc/pvr/epg/EpgInfoTag.h +++ b/xbmc/pvr/epg/EpgInfoTag.h @@ -301,14 +301,25 @@ class CPVREpgInfoTag final : public ISerializable, * @brief Get the parental rating of this event. * @return The parental rating. */ - int ParentalRating() const; + unsigned int ParentalRating() const; /*! * @brief Get the parental rating code of this event. * @return The parental rating code. */ - const std::string& ParentalRatingCode() const { return m_strParentalRatingCode; } + const std::string& ParentalRatingCode() const { return m_parentalRatingCode; } + /*! + * @brief Get the parental rating icon path of this event. + * @return Path to the parental rating icon. + */ + const std::string& ParentalRatingIcon() const { return m_parentalRatingIcon; } + + /*! + * @brief Get the parental rating source of this event. + * @return The parental rating source. + */ + const std::string& ParentalRatingSource() const { return m_parentalRatingSource; } /*! * @brief Get the star rating of this event. * @return The star rating. @@ -484,8 +495,10 @@ class CPVREpgInfoTag final : public ISerializable, int m_iGenreType = 0; /*!< genre type */ int m_iGenreSubType = 0; /*!< genre subtype */ std::string m_strGenreDescription; /*!< genre description */ - int m_iParentalRating = 0; /*!< parental rating */ - std::string m_strParentalRatingCode; /*!< parental rating code */ + unsigned int m_parentalRating = 0; /*!< parental rating */ + std::string m_parentalRatingCode; /*!< Parental rating code */ + std::string m_parentalRatingIcon; /*!< parental rating icon path */ + std::string m_parentalRatingSource; /*!< parental rating source */ int m_iStarRating = 0; /*!< star rating */ int m_iSeriesNumber = -1; /*!< series number */ int m_iEpisodeNumber = -1; /*!< episode number */ diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index 9049cf64c3162..779013d83db9a 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -428,6 +428,11 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, case LISTITEM_DIRECTOR: case LISTITEM_CHANNEL_NUMBER: case LISTITEM_PREMIERED: + case VIDEOPLAYER_PARENTAL_RATING: + case LISTITEM_PARENTAL_RATING: + case LISTITEM_PARENTAL_RATING_CODE: + case LISTITEM_PARENTAL_RATING_ICON: + case LISTITEM_PARENTAL_RATING_SOURCE: break; // obtain value from channel/epg default: return false; @@ -567,6 +572,26 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, strValue = CServiceBroker::GetPVRManager().GetClient(recording->ClientID())->GetInstanceName(); return true; + case VIDEOPLAYER_PARENTAL_RATING: + case LISTITEM_PARENTAL_RATING: + { + const unsigned int rating{recording->GetParentalRating()}; + if (rating > 0) + { + strValue = std::to_string(rating); + return true; + } + return false; + } + case LISTITEM_PARENTAL_RATING_CODE: + strValue = recording->GetParentalRatingCode(); + return true; + case LISTITEM_PARENTAL_RATING_ICON: + strValue = recording->GetParentalRatingIcon(); + return true; + case LISTITEM_PARENTAL_RATING_SOURCE: + strValue = recording->GetParentalRatingSource(); + return true; } return false; } @@ -785,12 +810,15 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, return true; case VIDEOPLAYER_PARENTAL_RATING: case LISTITEM_PARENTAL_RATING: - if (epgTag->ParentalRating() > 0) + { + const unsigned int rating{epgTag->ParentalRating()}; + if (rating > 0) { - strValue = std::to_string(epgTag->ParentalRating()); + strValue = std::to_string(rating); return true; } return false; + } case LISTITEM_PARENTAL_RATING_CODE: strValue = epgTag->ParentalRatingCode(); return true; @@ -800,6 +828,12 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, case LISTITEM_PVR_INSTANCE_NAME: strValue = CServiceBroker::GetPVRManager().GetClient(epgTag->ClientID())->GetInstanceName(); return true; + case LISTITEM_PARENTAL_RATING_ICON: + strValue = epgTag->ParentalRatingIcon(); + return true; + case LISTITEM_PARENTAL_RATING_SOURCE: + strValue = epgTag->ParentalRatingSource(); + return true; case VIDEOPLAYER_PREMIERED: case LISTITEM_PREMIERED: if (epgTag->FirstAired().IsValid()) diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp index 2f1b09ddb7db8..8fb9ee529871f 100644 --- a/xbmc/pvr/recordings/PVRRecording.cpp +++ b/xbmc/pvr/recordings/PVRRecording.cpp @@ -127,6 +127,14 @@ CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClien CVideoInfoTag::SetResumePoint(recording.iLastPlayedPosition, recording.iDuration, ""); SetDuration(recording.iDuration); + m_parentalRating = recording.iParentalRating; + if (recording.strParentalRatingCode) + m_parentalRatingCode = recording.strParentalRatingCode; + if (recording.strParentalRatingIcon) + m_parentalRatingIcon = recording.strParentalRatingIcon; + if (recording.strParentalRatingSource) + m_parentalRatingSource = recording.strParentalRatingSource; + // As the channel a recording was done on (probably long time ago) might no longer be // available today prefer addon-supplied channel type (tv/radio) over channel attribute. if (recording.channelType != PVR_RECORDING_CHANNEL_TYPE_UNKNOWN) @@ -180,7 +188,11 @@ bool CPVRRecording::operator==(const CPVRRecording& right) const m_iGenreSubType == right.m_iGenreSubType && m_firstAired == right.m_firstAired && m_iFlags == right.m_iFlags && m_sizeInBytes == right.m_sizeInBytes && m_strProviderName == right.m_strProviderName && - m_iClientProviderUniqueId == right.m_iClientProviderUniqueId); + m_iClientProviderUniqueId == right.m_iClientProviderUniqueId && + m_parentalRating == right.m_parentalRating && + m_parentalRatingCode == right.m_parentalRatingCode && + m_parentalRatingIcon == right.m_parentalRatingIcon && + m_parentalRatingSource == right.m_parentalRatingSource); } bool CPVRRecording::operator!=(const CPVRRecording& right) const @@ -204,6 +216,10 @@ void CPVRRecording::Serialize(CVariant& value) const value["channeluid"] = m_iChannelUid; value["radio"] = m_bRadio; value["genre"] = m_genre; + value["parentalrating"] = m_parentalRating; + value["parentalratingcode"] = m_parentalRatingCode; + value["parentalratingicon"] = m_parentalRatingIcon; + value["parentalratingsource"] = m_parentalRatingSource; if (!value.isMember("art")) value["art"] = CVariant(CVariant::VariantTypeObject); @@ -253,6 +269,12 @@ void CPVRRecording::Reset() m_iClientProviderUniqueId = PVR_PROVIDER_INVALID_UID; m_recordingTime.Reset(); + + m_parentalRating = 0; + m_parentalRatingCode.clear(); + m_parentalRatingIcon.clear(); + m_parentalRatingSource.clear(); + CVideoInfoTag::Reset(); } @@ -425,6 +447,10 @@ void CPVRRecording::Update(const CPVRRecording& tag, const CPVRClient& client) m_strPlotOutline = tag.m_strPlotOutline; m_strChannelName = tag.m_strChannelName; m_genre = tag.m_genre; + m_parentalRating = tag.m_parentalRating; + m_parentalRatingCode = tag.m_parentalRatingCode; + m_parentalRatingIcon = tag.m_parentalRatingIcon; + m_parentalRatingSource = tag.m_parentalRatingSource; m_iconPath = tag.m_iconPath; m_thumbnailPath = tag.m_thumbnailPath; m_fanartPath = tag.m_fanartPath; @@ -696,3 +722,27 @@ std::shared_ptr<CPVRProvider> CPVRRecording::GetProvider() const return provider; } + +unsigned int CPVRRecording::GetParentalRating() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_parentalRating; +} + +const std::string& CPVRRecording::GetParentalRatingCode() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_parentalRatingCode; +} + +const std::string& CPVRRecording::GetParentalRatingIcon() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_parentalRatingIcon; +} + +const std::string& CPVRRecording::GetParentalRatingSource() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_parentalRatingSource; +} diff --git a/xbmc/pvr/recordings/PVRRecording.h b/xbmc/pvr/recordings/PVRRecording.h index a9d43374ae160..5e12df9a41e7b 100644 --- a/xbmc/pvr/recordings/PVRRecording.h +++ b/xbmc/pvr/recordings/PVRRecording.h @@ -499,6 +499,30 @@ class CPVRRecording final : public CVideoInfoTag */ std::shared_ptr<CPVRProvider> GetProvider() const; + /*! + * @brief Get the parental rating of this recording. + * @return The parental rating. + */ + unsigned int GetParentalRating() const; + + /*! + * @brief Get the parental rating code of this recording. + * @return The parental rating code. + */ + const std::string& GetParentalRatingCode() const; + + /*! + * @brief Get the parental rating icon path of this recording. + * @return The parental rating icon path. + */ + const std::string& GetParentalRatingIcon() const; + + /*! + * @brief Get the parental rating source of this recording. + * @return The parental rating source. + */ + const std::string& GetParentalRatingSource() const; + private: CPVRRecording(const CPVRRecording& tag) = delete; CPVRRecording& operator=(const CPVRRecording& other) = delete; @@ -533,6 +557,10 @@ class CPVRRecording final : public CVideoInfoTag std::string m_strProviderName; /*!< name of the provider this recording is from */ int m_iClientProviderUniqueId = PVR_PROVIDER_INVALID_UID; /*!< provider uid associated with this recording on the client */ + unsigned int m_parentalRating{0}; /*!< parental rating */ + std::string m_parentalRatingCode; /*!< Parental rating code */ + std::string m_parentalRatingIcon; /*!< parental rating icon path */ + std::string m_parentalRatingSource; /*!< parental rating source */ mutable CCriticalSection m_critSection; }; From 653e4498655461cb332fb441fe195c7d7c26b465 Mon Sep 17 00:00:00 2001 From: Martin Vallevand <mvallevand@gmail.com> Date: Fri, 5 Jul 2024 17:10:01 -0400 Subject: [PATCH 395/651] [PVR] Add missing EpisodePart information PVR allows storing data for a shows that are broken into parts (e.g. 2/5) , however this data is only managed internally. This exposes the data to skins adding two new Infolabel ListItem.EpisodePart and VideoPlayer.EpisodePart --- addons/skin.estuary/xml/Variables.xml | 16 +++++++++++----- xbmc/GUIInfoManager.cpp | 22 +++++++++++++++++++++- xbmc/guilib/guiinfo/GUIInfoLabels.h | 2 ++ xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 6 ++++++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 78568c61b3b4e..57866c50b88a7 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -414,9 +414,12 @@ <variable name="OSDSubLabelVar"> <value condition="Window.IsActive(visualisation) + Integer.IsGreater(Playlist.Length(music),1) + Integer.IsGreater(Playlist.Position(music),0)">$LOCALIZE[554] $INFO[Playlist.Position] / $INFO[Playlist.Length]</value> <value condition="VideoPlayer.Content(musicvideos)">$VAR[NowPlayingSublabelVar,,[CR]]$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]: [/COLOR]]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value> - <value condition="VideoPlayer.Content(episodes) + !player.chaptercount">$INFO[VideoPlayer.Season,[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906][/CAPITALIZE]:[/COLOR] S]$INFO[VideoPlayer.Episode,E,: ]$INFO[VideoPlayer.Title]</value> - <value condition="VideoPlayer.Content(episodes) + player.chaptercount">$INFO[VideoPlayer.Season,[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906][/CAPITALIZE]:[/COLOR] S]$INFO[VideoPlayer.Episode,E, - ]$INFO[VideoPlayer.Title,,[CR]]$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value> - <value condition="VideoPlayer.Content(LiveTV) | PVR.IsPlayingRecording | PVR.IsPlayingEpgTag">$INFO[VideoPlayer.Season,[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906][/CAPITALIZE]:[/COLOR] S]$INFO[VideoPlayer.Episode,E, - ]$INFO[VideoPlayer.EpisodeName]</value> + <value condition="VideoPlayer.Content(episodes) + !player.chaptercount + ![String.IsEmpty(VideoPlayer.Episode) | String.IsEmpty(VideoPlayer.Title)]">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E,: ]$INFO[VideoPlayer.Title]</value> + <value condition="VideoPlayer.Content(episodes) + player.chaptercount + ![String.IsEmpty(VideoPlayer.Episode) | String.IsEmpty(VideoPlayer.Title)]">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E, - ]$INFO[VideoPlayer.Title,,[CR]]$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value> + <value condition="[VideoPlayer.Content(LiveTV) | PVR.IsPlayingEpgTag] + !String.IsEmpty(VideoPlayer.EpisodeName) + !String.IsEmpty(VideoPlayer.EpisodePart)">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.EpisodePart,/, - ]$INFO[VideoPlayer.EpisodeName]</value> + <value condition="[VideoPlayer.Content(LiveTV) | PVR.IsPlayingEpgTag] + !String.IsEmpty(VideoPlayer.EpisodePart)">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.EpisodePart,/]</value> + <value condition="[VideoPlayer.Content(LiveTV) | PVR.IsPlayingRecording | PVR.IsPlayingEpgTag] + !String.IsEmpty(VideoPlayer.EpisodeName)">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E, - ]$INFO[VideoPlayer.EpisodeName]</value> + <value condition="VideoPlayer.Content(LiveTV) | PVR.IsPlayingRecording | PVR.IsPlayingEpgTag + !String.IsEmpty(VideoPlayer.Episode)">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]</value> <value condition="player.chaptercount + [!VideoPlayer.Content(episodes) + !VideoPlayer.Content(LiveTV)]">$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value> <value>$INFO[VideoPlayer.Genre]</value> </variable> @@ -582,8 +585,10 @@ <value condition="Player.HasAudio">[COLOR grey]$INFO[MusicPlayer.Album][/COLOR]$INFO[MusicPlayer.Year, [,] ]</value> </variable> <variable name="PlayerLabel3"> + <value condition="VideoPlayer.Content(livetv) + !String.IsEmpty(VideoPlayer.Episode) + String.IsEmpty(VideoPlayer.EpisodePart)">$INFO[VideoPlayer.Season,$LOCALIZE[20373]: , ]$INFO[VideoPlayer.Episode,$LOCALIZE[20359]: ]</value> + <value condition="VideoPlayer.Content(livetv) + !String.IsEmpty(VideoPlayer.Episode) + !String.IsEmpty(VideoPlayer.EpisodePart)">$INFO[VideoPlayer.Season,$LOCALIZE[20373]: , ]$INFO[VideoPlayer.Episode,$LOCALIZE[20359]: ]$INFO[VideoPlayer.EpisodePart,/]</value> + <value condition="VideoPlayer.Content(movies) | VideoPlayer.Content(livetv) + String.IsEmpty(VideoPlayer.Episode)">$INFO[VideoPlayer.Genre]</value> <value condition="VideoPlayer.Content(episodes)">$INFO[VideoPlayer.TvShowTitle]</value> - <value condition="VideoPlayer.Content(movies) | VideoPlayer.Content(livetv)">$INFO[VideoPlayer.Genre]</value> <value condition="Player.HasAudio">$INFO[MusicPlayer.TrackNumber,,: ][COLOR=grey]$INFO[Player.Title][/COLOR]</value> </variable> <variable name="PVRTimerIcon"> @@ -601,7 +606,8 @@ <value condition="ListItem.IsPlayable">icons/pvr/PVR-HasArchive.png</value> </variable> <variable name="SeasonEpisodeLabel"> - <value condition="String.IsEmpty(ListItem.EpisodeName)">$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E]</value> + <value condition="String.IsEmpty(ListItem.EpisodeName)">$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E]$INFO[ListItem.EpisodePart,/]</value> + <value condition="!String.IsEmpty(ListItem.EpisodePart)">$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E]$INFO[ListItem.EpisodePart,/,: ]</value> <value>$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E,: ]</value> </variable> <variable name="FirstAiredLabel"> diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index e6a377efc2977..5fd4b308a24c6 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -3954,6 +3954,15 @@ const infomap musicplayer[] = {{ "title", MUSICPLAYER_TITLE }, /// <p><hr> /// @skinning_v21 **[New Infolabel]** \link VideoPlayer_VideoVersionName `VideoPlayer.VideoVersionName`\endlink /// } +/// \table_row3{ <b>`VideoPlayer.EpisodePart`</b>, +/// \anchor VideoPlayer_EpisodePart +/// _string_, +/// @return string containing the number of parts of a single episode - empty if no data provided +/// <p><hr> +/// @skinning_v22 **[Infolabel Updated]** \link VideoPlayer_EpisodePart `VideoPlayer.EpisodePart`\endlink +/// also supports EPG. +/// <p> +/// }/// /// \table_end /// /// ----------------------------------------------------------------------------- @@ -4032,7 +4041,8 @@ const infomap videoplayer[] = {{ "title", VIDEOPLAYER_TITLE }, { "hdrtype", VIDEOPLAYER_HDR_TYPE }, { "art", VIDEOPLAYER_ART}, { "videoversionname", VIDEOPLAYER_VIDEOVERSION_NAME}, - { "hasvideoversions", VIDEOPLAYER_HAS_VIDEOVERSIONS} + { "hasvideoversions", VIDEOPLAYER_HAS_VIDEOVERSIONS}, + { "episodepart", VIDEOPLAYER_EPISODEPART} }; // clang-format on @@ -7034,6 +7044,15 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @skinning_v22 **[New Infolabel]** \link ListItem_PVRGroupOrigin `ListItem.PVRGroupOrigin`\endlink /// <p> /// } +/// \table_row3{ <b>`ListItem.EpisodePart`</b>, +/// \anchor ListItem_EpisodePart +/// _string_, +/// @return string containing the number of parts of a single episode - empty if no data provided +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link ListItem_EpisodePart `ListItem.EpisodePart`\endlink +/// <p> +/// } +/// /// \table_end /// /// ----------------------------------------------------------------------------- @@ -7261,6 +7280,7 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "pvrclientname", LISTITEM_PVR_CLIENT_NAME }, { "pvrinstancename", LISTITEM_PVR_INSTANCE_NAME }, { "pvrgrouporigin", LISTITEM_PVR_GROUP_ORIGIN }, + { "episodepart", LISTITEM_EPISODEPART }, }; // clang-format on diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index 997ff42dff388..ff7a5861eb240 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -323,6 +323,7 @@ // More PVR infolabels #define VIDEOPLAYER_CHANNEL_LOGO 333 +#define VIDEOPLAYER_EPISODEPART 334 #define CONTAINER_HAS_PARENT_ITEM 341 #define CONTAINER_CAN_FILTER 342 @@ -991,6 +992,7 @@ static constexpr unsigned int SYSTEM_LOCALE = 1012; #define LISTITEM_PVR_GROUP_ORIGIN (LISTITEM_START + 220) #define LISTITEM_PARENTAL_RATING_ICON (LISTITEM_START + 221) #define LISTITEM_PARENTAL_RATING_SOURCE (LISTITEM_START + 222) +#define LISTITEM_EPISODEPART (LISTITEM_START + 223) #define LISTITEM_END (LISTITEM_START + 2500) diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index 779013d83db9a..6e45078bfab6a 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -425,6 +425,7 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, case LISTITEM_SEASON: case LISTITEM_EPISODE: case LISTITEM_EPISODENAME: + case LISTITEM_EPISODEPART: case LISTITEM_DIRECTOR: case LISTITEM_CHANNEL_NUMBER: case LISTITEM_PREMIERED: @@ -784,6 +785,11 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, return true; } return false; + case VIDEOPLAYER_EPISODEPART: + case LISTITEM_EPISODEPART: + if (epgTag->EpisodeNumber() >= 0 && epgTag->EpisodePart() > 0) + strValue = std::to_string(epgTag->EpisodePart()); + return true; case VIDEOPLAYER_EPISODENAME: case LISTITEM_EPISODENAME: if (!CServiceBroker::GetPVRManager().IsParentalLocked(epgTag)) From 5ff1e493dda4396fb934bef5895fa42965f21cb7 Mon Sep 17 00:00:00 2001 From: Martin Vallevand <mvallevand@gmail.com> Date: Tue, 16 Jul 2024 12:16:23 -0400 Subject: [PATCH 396/651] Season episode changes Additional changes to skin Episode labeling removing and VideoPlayer removing leading E from @jjd-uk --- addons/skin.estuary/xml/DialogPVRInfo.xml | 2 +- addons/skin.estuary/xml/Includes_PVR.xml | 2 +- addons/skin.estuary/xml/Variables.xml | 20 +++++++++++--------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/addons/skin.estuary/xml/DialogPVRInfo.xml b/addons/skin.estuary/xml/DialogPVRInfo.xml index 86a1edd432d8e..04d12c996b839 100644 --- a/addons/skin.estuary/xml/DialogPVRInfo.xml +++ b/addons/skin.estuary/xml/DialogPVRInfo.xml @@ -117,7 +117,7 @@ </control> <include content="InfoDialogTopBarInfo"> <param name="main_label" value="$INFO[ListItem.Title] $INFO[ListItem.Year,([COLOR grey],[/COLOR])]" /> - <param name="sub_label" value="$VAR[FlagDashLabel][COLOR grey]$VAR[SeasonEpisodeLabel][/COLOR]$INFO[ListItem.EpisodeName,[COLOR white][B],[/B][/COLOR]]" /> + <param name="sub_label" value="$VAR[FlagDashLabel]$VAR[SeasonEpisodeLabel,[COLOR grey],[/COLOR]]$INFO[ListItem.EpisodeName]" /> <param name="posy" value="40" /> </include> </control> diff --git a/addons/skin.estuary/xml/Includes_PVR.xml b/addons/skin.estuary/xml/Includes_PVR.xml index f2a80415259a6..f0f5b0338543b 100644 --- a/addons/skin.estuary/xml/Includes_PVR.xml +++ b/addons/skin.estuary/xml/Includes_PVR.xml @@ -537,7 +537,7 @@ <top>35</top> <width>100%</width> <height>30</height> - <label>$VAR[FlagDashLabel][I][COLOR grey]$VAR[SeasonEpisodeLabel][/COLOR]$INFO[ListItem.EpisodeName,[COLOR white],[/COLOR]][/I]</label> + <label>$VAR[FlagDashLabel]$VAR[SeasonEpisodeLabel,[COLOR grey],[/COLOR]]$INFO[ListItem.EpisodeName]</label> </control> </control> <control type="textbox"> diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 57866c50b88a7..480e21056b73e 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -83,7 +83,7 @@ <value condition="ListItem.HasVideoVersions + Window.IsActive(videoplaylist)">$INFO[ListItem.Label]$INFO[ListItem.VideoVersionName, [I](,)[/I]]</value> <value condition="String.IsEqual(ListItem.DbType,episode) + Window.IsActive(videoplaylist)">$INFO[ListItem.TVShowtitle,,: ]$INFO[ListItem.Season,,x]$INFO[ListItem.Episode,,. ]$INFO[ListItem.Title]</value> <value condition="String.IsEqual(ListItem.DbType,musicvideo) + Window.IsActive(videoplaylist)">$INFO[ListItem.Artist,, - ]$INFO[ListItem.Title]</value> - <value condition="[!String.IsEmpty(ListItem.Season) | !String.IsEmpty(ListItem.Episode) | !String.IsEmpty(ListItem.EpisodeName)] + Window.IsActive(videoplaylist)">$INFO[ListItem.Title,,: ]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName]</value> + <value condition="[!String.IsEmpty(ListItem.Season) | !String.IsEmpty(ListItem.Episode) | !String.IsEmpty(ListItem.EpisodeName)] + Window.IsActive(videoplaylist)">$INFO[ListItem.Title,,: ]$VAR[SeasonEpisodeLabel]</value> <value>$INFO[ListItem.Label]</value> </variable> <variable name="ListLabel2Var"> @@ -414,12 +414,9 @@ <variable name="OSDSubLabelVar"> <value condition="Window.IsActive(visualisation) + Integer.IsGreater(Playlist.Length(music),1) + Integer.IsGreater(Playlist.Position(music),0)">$LOCALIZE[554] $INFO[Playlist.Position] / $INFO[Playlist.Length]</value> <value condition="VideoPlayer.Content(musicvideos)">$VAR[NowPlayingSublabelVar,,[CR]]$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]: [/COLOR]]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value> - <value condition="VideoPlayer.Content(episodes) + !player.chaptercount + ![String.IsEmpty(VideoPlayer.Episode) | String.IsEmpty(VideoPlayer.Title)]">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E,: ]$INFO[VideoPlayer.Title]</value> - <value condition="VideoPlayer.Content(episodes) + player.chaptercount + ![String.IsEmpty(VideoPlayer.Episode) | String.IsEmpty(VideoPlayer.Title)]">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E, - ]$INFO[VideoPlayer.Title,,[CR]]$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value> - <value condition="[VideoPlayer.Content(LiveTV) | PVR.IsPlayingEpgTag] + !String.IsEmpty(VideoPlayer.EpisodeName) + !String.IsEmpty(VideoPlayer.EpisodePart)">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.EpisodePart,/, - ]$INFO[VideoPlayer.EpisodeName]</value> - <value condition="[VideoPlayer.Content(LiveTV) | PVR.IsPlayingEpgTag] + !String.IsEmpty(VideoPlayer.EpisodePart)">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.EpisodePart,/]</value> - <value condition="[VideoPlayer.Content(LiveTV) | PVR.IsPlayingRecording | PVR.IsPlayingEpgTag] + !String.IsEmpty(VideoPlayer.EpisodeName)">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E, - ]$INFO[VideoPlayer.EpisodeName]</value> - <value condition="VideoPlayer.Content(LiveTV) | PVR.IsPlayingRecording | PVR.IsPlayingEpgTag + !String.IsEmpty(VideoPlayer.Episode)">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]</value> + <value condition="VideoPlayer.Content(episodes) + !player.chaptercount + ![String.IsEmpty(VideoPlayer.Episode) | String.IsEmpty(VideoPlayer.Title)]">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.Title, - ]</value> + <value condition="VideoPlayer.Content(episodes) + player.chaptercount + ![String.IsEmpty(VideoPlayer.Episode) | String.IsEmpty(VideoPlayer.Title)]">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.Title, - ,[CR]]$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value> + <value condition="[VideoPlayer.Content(LiveTV) | PVR.IsPlayingRecording | PVR.IsPlayingEpgTag] ">$VAR[VideoPlayerSeasonEpisodeLabel,[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR]]</value> <value condition="player.chaptercount + [!VideoPlayer.Content(episodes) + !VideoPlayer.Content(LiveTV)]">$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value> <value>$INFO[VideoPlayer.Genre]</value> </variable> @@ -607,9 +604,14 @@ </variable> <variable name="SeasonEpisodeLabel"> <value condition="String.IsEmpty(ListItem.EpisodeName)">$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E]$INFO[ListItem.EpisodePart,/]</value> - <value condition="!String.IsEmpty(ListItem.EpisodePart)">$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E]$INFO[ListItem.EpisodePart,/,: ]</value> + <value condition="!String.IsEmpty(ListItem.EpisodeName) + !String.IsEmpty(ListItem.EpisodePart)">$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E]$INFO[ListItem.EpisodePart,/,: ]</value> <value>$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E,: ]</value> </variable> + <variable name="VideoPlayerSeasonEpisodeLabel"> + <value condition="!String.IsEmpty(VideoPlayer.Season)"> $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.EpisodePart,/]$INFO[VideoPlayer.EpisodeName, - ]</value> + <value condition="!String.IsEmpty(VideoPlayer.Episode)"> $INFO[VideoPlayer.Episode]$INFO[VideoPlayer.EpisodePart,/]$INFO[VideoPlayer.EpisodeName, - ]</value> + <value condition="!String.IsEmpty(VideoPlayer.EpisodeName)"> $INFO[VideoPlayer.EpisodeName]</value> + </variable> <variable name="FirstAiredLabel"> <value condition="String.IsEqual(ListItem.DBType,movie)">$LOCALIZE[20473]</value> <value>$LOCALIZE[20416]</value> @@ -662,7 +664,7 @@ </variable> <variable name="PVRListItemSubLabelFocused"> <value condition="ListItem.IsFolder">$INFO[ListItem.Timertype]</value> - <value condition="$EXP[listitem_has_episode_info] + !String.IsEmpty(ListItem.EpgEventTitle) + !String.StartsWith(ListItem.EpisodeName,ListItem.EpgEventTitle)">$INFO[ListItem.EpgEventTitle] | $VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName]</value> + <value condition="$EXP[listitem_has_episode_info] + !String.IsEmpty(ListItem.EpgEventTitle) + !String.StartsWith(ListItem.EpisodeName,ListItem.EpgEventTitle)">$INFO[ListItem.EpgEventTitle] | [COLOR grey]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/COLOR]</value> <value condition="$EXP[listitem_has_episode_info]">$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName]</value> <value>$INFO[ListItem.EpgEventTitle]</value> </variable> From 45b0c87ed2329153d21b769197383fc73019d63e Mon Sep 17 00:00:00 2001 From: the-black-eagle <g.moore@gmx.co.uk> Date: Tue, 20 Aug 2024 08:45:27 +0100 Subject: [PATCH 397/651] [MUSIC] Fix incorrect length for last track in audiobooks --- xbmc/filesystem/AudioBookFileDirectory.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/xbmc/filesystem/AudioBookFileDirectory.cpp b/xbmc/filesystem/AudioBookFileDirectory.cpp index dda1c0cb5f001..f730ec2df4de5 100644 --- a/xbmc/filesystem/AudioBookFileDirectory.cpp +++ b/xbmc/filesystem/AudioBookFileDirectory.cpp @@ -106,20 +106,26 @@ bool CAudioBookFileDirectory::GetDirectory(const CURL& url, item->GetMusicInfoTag()->GetTitle())); item->SetStartOffset(CUtil::ConvertSecsToMilliSecs(m_fctx->chapters[i]->start * av_q2d(m_fctx->chapters[i]->time_base))); - item->SetEndOffset(m_fctx->chapters[i]->end * av_q2d(m_fctx->chapters[i]->time_base)); + item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(m_fctx->chapters[i]->end * + av_q2d(m_fctx->chapters[i]->time_base))); int compare = m_fctx->streams[0]->duration * av_q2d(m_fctx->streams[0]->time_base); - if (item->GetEndOffset() < 0 || item->GetEndOffset() > compare) + if (item->GetEndOffset() < 0 || + item->GetEndOffset() > CUtil::ConvertMilliSecsToSecs(m_fctx->duration)) { - if (i < m_fctx->nb_chapters-1) - item->SetEndOffset(m_fctx->chapters[i + 1]->start * - av_q2d(m_fctx->chapters[i + 1]->time_base)); + if (i < m_fctx->nb_chapters - 1) + item->SetEndOffset(CUtil::ConvertSecsToMilliSecs( + m_fctx->chapters[i + 1]->start * av_q2d(m_fctx->chapters[i + 1]->time_base))); else - item->SetEndOffset(compare); + { + item->SetEndOffset(m_fctx->duration); // mka file + if (item->GetEndOffset() < 0) + item->SetEndOffset(compare); // m4b file + } } - item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(item->GetEndOffset())); item->GetMusicInfoTag()->SetDuration( CUtil::ConvertMilliSecsToSecsInt(item->GetEndOffset() - item->GetStartOffset())); item->SetProperty("item_start", item->GetStartOffset()); + item->SetProperty("audio_bookmark", item->GetStartOffset()); if (!thumb.empty()) item->SetArt("thumb", thumb); items.Add(item); From f7590a1644f1266b3c3f6951b1f7d5c6d99c59a3 Mon Sep 17 00:00:00 2001 From: the-black-eagle <g.moore@gmx.co.uk> Date: Tue, 20 Aug 2024 08:50:46 +0100 Subject: [PATCH 398/651] [MUSIC] Fix playback of mka/m4b files to start from selected track in files view --- xbmc/windows/GUIMediaWindow.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/xbmc/windows/GUIMediaWindow.cpp b/xbmc/windows/GUIMediaWindow.cpp index dffccda6211ef..9c1f8e48dfd47 100644 --- a/xbmc/windows/GUIMediaWindow.cpp +++ b/xbmc/windows/GUIMediaWindow.cpp @@ -47,6 +47,8 @@ #include "input/actions/ActionIDs.h" #include "interfaces/generic/ScriptInvocationManager.h" #include "messaging/helpers/DialogOKHelper.h" +#include "music/MusicFileItemClassify.h" +#include "music/tags/MusicInfoTag.h" #include "network/Network.h" #include "playlists/PlayList.h" #include "profiles/ProfileManager.h" @@ -1546,6 +1548,19 @@ bool CGUIMediaWindow::OnPlayAndQueueMedia(const CFileItemPtr& item, const std::s std::distance(playlist.begin(), std::find_if(playlist.begin(), playlist.end(), [&item](const std::shared_ptr<CFileItem>& i) { return i->GetPath() == item->GetPath(); })); + /* For .mka albums, all tracks are in the same file so using path as above will always play the + * first track. Use the track and disk number to ensure we start playback on the correct track. + * This only applies to mka or m4b items played back via files view. Music library takes + * a different play path. + */ + if (MUSIC::IsAudioBook(*item)) + mediaToPlay = std::distance( + playlist.begin(), std::find_if(playlist.begin(), playlist.end(), + [&item](const std::shared_ptr<CFileItem>& i) + { + return i->GetMusicInfoTag()->GetTrackAndDiscNumber() == + item->GetMusicInfoTag()->GetTrackAndDiscNumber(); + })); // Add to playlist CServiceBroker::GetPlaylistPlayer().ClearPlaylist(playlistId); From b32d79bd668fefafc182af6a5edae029297ed276 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:57:52 +0200 Subject: [PATCH 399/651] [addons] PVR Add-on API: Fix initialization of PVRRecording::iClientProviderUid. --- .../include/kodi/addon-instance/pvr/Recordings.h | 1 + xbmc/addons/kodi-dev-kit/include/kodi/versions.h | 2 +- xbmc/pvr/recordings/PVRRecording.cpp | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h index 5a97cc8eddde9..1fd86166911cf 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h @@ -56,6 +56,7 @@ class PVRRecording : public DynamicCStructHdl<PVRRecording, PVR_RECORDING> m_cStructure->iChannelUid = PVR_RECORDING_VALUE_NOT_AVAILABLE; m_cStructure->channelType = PVR_RECORDING_CHANNEL_TYPE_UNKNOWN; m_cStructure->sizeInBytes = PVR_RECORDING_VALUE_NOT_AVAILABLE; + m_cStructure->iClientProviderUid = PVR_PROVIDER_INVALID_UID; } PVRRecording(const PVRRecording& recording) : DynamicCStructHdl(recording) {} /*! \endcond */ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h index 622a6920e0115..ad597e914f8ac 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h @@ -130,7 +130,7 @@ #define ADDON_INSTANCE_VERSION_PERIPHERAL_DEPENDS "addon-instance/Peripheral.h" \ "addon-instance/PeripheralUtils.h" -#define ADDON_INSTANCE_VERSION_PVR "9.0.0" +#define ADDON_INSTANCE_VERSION_PVR "9.0.1" #define ADDON_INSTANCE_VERSION_PVR_MIN "9.0.0" #define ADDON_INSTANCE_VERSION_PVR_XML_ID "kodi.binary.instance.pvr" #define ADDON_INSTANCE_VERSION_PVR_DEPENDS "c-api/addon-instance/pvr.h" \ diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp index 8fb9ee529871f..4ba50c4469c5f 100644 --- a/xbmc/pvr/recordings/PVRRecording.cpp +++ b/xbmc/pvr/recordings/PVRRecording.cpp @@ -120,6 +120,11 @@ CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClien m_strProviderName = recording.strProviderName; m_iClientProviderUniqueId = recording.iClientProviderUid; + // Workaround for C++ PVR Add-on API wrapper not initializing this value correctly until API 9.0.1 + //! @todo Remove with next incompatible API bump. + if (m_iClientProviderUniqueId == 0) + m_iClientProviderUniqueId = PVR_PROVIDER_INVALID_UID; + SetGenre(recording.iGenreType, recording.iGenreSubType, recording.strGenreDescription ? recording.strGenreDescription : ""); CVideoInfoTag::SetPlayCount(recording.iPlayCount); From d1104ad35e9bd3ca78827deac4b15c03e8b982c9 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 17 Aug 2024 18:44:07 +0200 Subject: [PATCH 400/651] [PVR] Cleanup: Remove unused function CPVRClient::GetInstanceInterface. --- xbmc/pvr/addons/PVRClient.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h index 30417dfffbf9b..2e56b7ce33dc1 100644 --- a/xbmc/pvr/addons/PVRClient.h +++ b/xbmc/pvr/addons/PVRClient.h @@ -812,12 +812,6 @@ class CPVRClient : public ADDON::IAddonInstanceHandler */ PVR_ERROR GetStreamReadChunkSize(int& iChunkSize) const; - /*! - * @brief Get the interface table used between addon and Kodi. - * @todo This function will be removed after old callback library system is removed. - */ - AddonInstance_PVR* GetInstanceInterface() { return m_ifc.pvr; } - private: /*! * @brief Resets all class members to their defaults, accept the client id. From 77af2219b7f2c373e127428d3d86445c40a29bc0 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:58:16 +0200 Subject: [PATCH 401/651] [PVR] PVRGUIInfo: Remove dead code. Videoplayer never 'plays' PVR timers. --- xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index 6e45078bfab6a..24b16801e53f7 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -429,7 +429,6 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, case LISTITEM_DIRECTOR: case LISTITEM_CHANNEL_NUMBER: case LISTITEM_PREMIERED: - case VIDEOPLAYER_PARENTAL_RATING: case LISTITEM_PARENTAL_RATING: case LISTITEM_PARENTAL_RATING_CODE: case LISTITEM_PARENTAL_RATING_ICON: From 7091c309f1aa20875e69decfcacc3484839275a7 Mon Sep 17 00:00:00 2001 From: Martin Vallevand <mvallevand@gmail.com> Date: Tue, 20 Aug 2024 18:12:01 -0400 Subject: [PATCH 402/651] Add EpisodePart to recordings Use API 9 to add the EpisodePart to recordings. PR 25439 added the EpisodePart to the SeasonEpisodeLabel and recording calls already use that label although PlayerLabel3 needed to be updated to handle recordings which had never been considered. --- addons/skin.estuary/xml/Variables.xml | 4 ++-- .../c-api/addon-instance/pvr/pvr_recordings.h | 7 ++++--- xbmc/pvr/addons/PVRClient.cpp | 2 +- xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 7 +++++++ xbmc/pvr/recordings/PVRRecording.cpp | 18 ++++++++++++++---- xbmc/pvr/recordings/PVRRecording.h | 8 ++++++++ 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 480e21056b73e..4b0feff31735d 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -582,8 +582,8 @@ <value condition="Player.HasAudio">[COLOR grey]$INFO[MusicPlayer.Album][/COLOR]$INFO[MusicPlayer.Year, [,] ]</value> </variable> <variable name="PlayerLabel3"> - <value condition="VideoPlayer.Content(livetv) + !String.IsEmpty(VideoPlayer.Episode) + String.IsEmpty(VideoPlayer.EpisodePart)">$INFO[VideoPlayer.Season,$LOCALIZE[20373]: , ]$INFO[VideoPlayer.Episode,$LOCALIZE[20359]: ]</value> - <value condition="VideoPlayer.Content(livetv) + !String.IsEmpty(VideoPlayer.Episode) + !String.IsEmpty(VideoPlayer.EpisodePart)">$INFO[VideoPlayer.Season,$LOCALIZE[20373]: , ]$INFO[VideoPlayer.Episode,$LOCALIZE[20359]: ]$INFO[VideoPlayer.EpisodePart,/]</value> + <value condition="VideoPlayer.Content(livetv) | PVR.IsPlayingRecording + !String.IsEmpty(VideoPlayer.Episode) + String.IsEmpty(VideoPlayer.EpisodePart)">$INFO[VideoPlayer.Season,$LOCALIZE[20373]: , ]$INFO[VideoPlayer.Episode,$LOCALIZE[20359]: ]</value> + <value condition="VideoPlayer.Content(livetv) | PVR.IsPlayingRecording + !String.IsEmpty(VideoPlayer.Episode) + !String.IsEmpty(VideoPlayer.EpisodePart)">$INFO[VideoPlayer.Season,$LOCALIZE[20373]: , ]$INFO[VideoPlayer.Episode,$LOCALIZE[20359]: ]$INFO[VideoPlayer.EpisodePart,/]</value> <value condition="VideoPlayer.Content(movies) | VideoPlayer.Content(livetv) + String.IsEmpty(VideoPlayer.Episode)">$INFO[VideoPlayer.Genre]</value> <value condition="VideoPlayer.Content(episodes)">$INFO[VideoPlayer.TvShowTitle]</value> <value condition="Player.HasAudio">$INFO[MusicPlayer.TrackNumber,,: ][COLOR=grey]$INFO[Player.Title][/COLOR]</value> diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h index d43c40724fef8..8b0e19c5b390c 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h @@ -61,12 +61,13 @@ extern "C" //============================================================================ /// @ingroup cpp_kodi_addon_pvr_Defs_Recording_PVRRecording /// @brief Special @ref kodi::addon::PVRRecording::SetSeriesNumber() and - /// @ref kodi::addon::PVRRecording::SetEpisodeNumber() value to indicate it is - /// not to be used. + /// @ref kodi::addon::PVRRecording::SetEpisodeNumber() and + /// @ref kodi::addon::PVRRecording::SetEpisodePartNumber() value to indicate + /// it is not to be used. /// /// Used if recording has no valid season and/or episode info. /// -#define PVR_RECORDING_INVALID_SERIES_EPISODE EPG_TAG_INVALID_SERIES_EPISODE +#define PVR_RECORDING_INVALID_SERIES_EPISODE -1 //---------------------------------------------------------------------------- //============================================================================ diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 8864dc684d8a2..3b0169c618af7 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -135,7 +135,7 @@ class CAddonRecording : public PVR_RECORDING strEpisodeName = m_episodeName.c_str(); iSeriesNumber = recording.m_iSeason; iEpisodeNumber = recording.m_iEpisode; - iEpisodePartNumber = PVR_RECORDING_INVALID_SERIES_EPISODE; //! @todo + iEpisodePartNumber = recording.EpisodePart(); iYear = recording.GetYear(); strDirectory = m_directory.c_str(); strPlotOutline = m_plotOutline.c_str(); diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index 6e45078bfab6a..c1093c254803f 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -593,6 +593,13 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, case LISTITEM_PARENTAL_RATING_SOURCE: strValue = recording->GetParentalRatingSource(); return true; + case VIDEOPLAYER_EPISODEPART: + case LISTITEM_EPISODEPART: + if (recording->m_iEpisode > 0 && recording->EpisodePart() > 0) + { + strValue = std::to_string(recording->EpisodePart()); + return true; + } } return false; } diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp index 8fb9ee529871f..d4815ed6c0310 100644 --- a/xbmc/pvr/recordings/PVRRecording.cpp +++ b/xbmc/pvr/recordings/PVRRecording.cpp @@ -9,7 +9,6 @@ #include "PVRRecording.h" #include "ServiceBroker.h" -#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h" #include "cores/EdlEdit.h" #include "guilib/LocalizeStrings.h" #include "pvr/PVRManager.h" @@ -91,6 +90,7 @@ CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClien m_strShowTitle = recording.strEpisodeName; m_iSeason = recording.iSeriesNumber; m_iEpisode = recording.iEpisodeNumber; + m_episodePartNumber = recording.iEpisodePartNumber; if (recording.iYear > 0) SetYear(recording.iYear); m_iClientId = iClientId; @@ -192,7 +192,8 @@ bool CPVRRecording::operator==(const CPVRRecording& right) const m_parentalRating == right.m_parentalRating && m_parentalRatingCode == right.m_parentalRatingCode && m_parentalRatingIcon == right.m_parentalRatingIcon && - m_parentalRatingSource == right.m_parentalRatingSource); + m_parentalRatingSource == right.m_parentalRatingSource && + m_episodePartNumber == right.m_episodePartNumber); } bool CPVRRecording::operator!=(const CPVRRecording& right) const @@ -220,6 +221,7 @@ void CPVRRecording::Serialize(CVariant& value) const value["parentalratingcode"] = m_parentalRatingCode; value["parentalratingicon"] = m_parentalRatingIcon; value["parentalratingsource"] = m_parentalRatingSource; + value["episodepart"] = m_episodePartNumber; if (!value.isMember("art")) value["art"] = CVariant(CVariant::VariantTypeObject); @@ -256,8 +258,9 @@ void CPVRRecording::Reset() m_bIsDeleted = false; m_bInProgress = true; m_iEpgEventId = EPG_TAG_INVALID_UID; - m_iSeason = -1; - m_iEpisode = -1; + m_iSeason = PVR_RECORDING_INVALID_SERIES_EPISODE; + m_iEpisode = PVR_RECORDING_INVALID_SERIES_EPISODE; + m_episodePartNumber = PVR_RECORDING_INVALID_SERIES_EPISODE; m_iChannelUid = PVR_CHANNEL_INVALID_UID; m_bRadio = false; m_iFlags = PVR_RECORDING_FLAG_UNDEFINED; @@ -438,6 +441,7 @@ void CPVRRecording::Update(const CPVRRecording& tag, const CPVRClient& client) m_strShowTitle = tag.m_strShowTitle; m_iSeason = tag.m_iSeason; m_iEpisode = tag.m_iEpisode; + m_episodePartNumber = tag.m_episodePartNumber; SetPremiered(tag.GetPremiered()); m_recordingTime = tag.m_recordingTime; m_iPriority = tag.m_iPriority; @@ -746,3 +750,9 @@ const std::string& CPVRRecording::GetParentalRatingSource() const std::unique_lock<CCriticalSection> lock(m_critSection); return m_parentalRatingSource; } + +int CPVRRecording::EpisodePart() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_episodePartNumber; +} diff --git a/xbmc/pvr/recordings/PVRRecording.h b/xbmc/pvr/recordings/PVRRecording.h index 5e12df9a41e7b..2e70b48c94791 100644 --- a/xbmc/pvr/recordings/PVRRecording.h +++ b/xbmc/pvr/recordings/PVRRecording.h @@ -10,6 +10,7 @@ #include "XBDateTime.h" #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h" +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h" #include "pvr/PVRCachedImage.h" #include "threads/CriticalSection.h" #include "threads/SystemClock.h" @@ -523,6 +524,12 @@ class CPVRRecording final : public CVideoInfoTag */ const std::string& GetParentalRatingSource() const; + /*! + * @brief Get the episode part number of this recording. + * @return The episode part number. + */ + int EpisodePart() const; + private: CPVRRecording(const CPVRRecording& tag) = delete; CPVRRecording& operator=(const CPVRRecording& other) = delete; @@ -561,6 +568,7 @@ class CPVRRecording final : public CVideoInfoTag std::string m_parentalRatingCode; /*!< Parental rating code */ std::string m_parentalRatingIcon; /*!< parental rating icon path */ std::string m_parentalRatingSource; /*!< parental rating source */ + int m_episodePartNumber{PVR_RECORDING_INVALID_SERIES_EPISODE}; /*!< episode part number */ mutable CCriticalSection m_critSection; }; From 77b9fef385e3cc80439e6d53342aee337a63fcf8 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 17 Aug 2024 18:53:33 +0200 Subject: [PATCH 403/651] [PVR] Remember date and time channels were obtained from a client for the first time, to not populate 'Recently added channels' widget with those channels. --- xbmc/pvr/PVRDatabase.cpp | 48 +++++++++++++++++++++---- xbmc/pvr/PVRDatabase.h | 11 +++++- xbmc/pvr/addons/PVRClient.cpp | 39 +++++++++++++++++++- xbmc/pvr/addons/PVRClient.h | 15 ++++++++ xbmc/pvr/filesystem/PVRGUIDirectory.cpp | 25 ++++++++++--- 5 files changed, 126 insertions(+), 12 deletions(-) diff --git a/xbmc/pvr/PVRDatabase.cpp b/xbmc/pvr/PVRDatabase.cpp index fb8bb87394185..4b6626deca9f0 100644 --- a/xbmc/pvr/PVRDatabase.cpp +++ b/xbmc/pvr/PVRDatabase.cpp @@ -200,7 +200,8 @@ void CPVRDatabase::CreateTables() "idClient integer primary key, " "iPriority integer, " "sAddonID TEXT, " - "iInstanceID integer" + "iInstanceID integer," + "sDateTimeFirstChannelsAdded varchar(20)" ")"); CLog::LogFC(LOGDEBUG, LOGPVR, "Creating table 'timers'"); @@ -389,6 +390,12 @@ void CPVRDatabase::UpdateTables(int iVersion) FixupClientIDs(); } + + if (iVersion < 47) + { + m_pDS->exec("ALTER TABLE clients ADD sDateTimeFirstChannelsAdded varchar(20)"); + m_pDS->exec("UPDATE clients SET sDateTimeFirstChannelsAdded = ''"); + } } /********** Client methods **********/ @@ -408,11 +415,18 @@ bool CPVRDatabase::Persist(const CPVRClient& client) CLog::LogFC(LOGDEBUG, LOGPVR, "Persisting client {} to database", client.GetID()); + const CDateTime& dateTime{client.GetDateTimeFirstChannelsAdded()}; + std::string dateTimeAdded; + if (dateTime.IsValid()) + dateTimeAdded = dateTime.GetAsDBDateTime(); + std::unique_lock<CCriticalSection> lock(m_critSection); - const std::string sql{PrepareSQL( - "REPLACE INTO clients (idClient, iPriority, sAddonID, iInstanceID) VALUES (%i, %i, '%s', %i)", - client.GetID(), client.GetPriority(), client.ID().c_str(), client.InstanceID())}; + const std::string sql{ + PrepareSQL("REPLACE INTO clients (idClient, iPriority, sAddonID, iInstanceID, " + "sDateTimeFirstChannelsAdded) VALUES (%i, %i, '%s', %i, '%s')", + client.GetID(), client.GetPriority(), client.ID().c_str(), client.InstanceID(), + dateTimeAdded.c_str())}; return ExecuteQuery(sql); } @@ -449,6 +463,26 @@ int CPVRDatabase::GetPriority(const CPVRClient& client) const return atoi(strValue.c_str()); } +CDateTime CPVRDatabase::GetDateTimeFirstChannelsAdded(const CPVRClient& client) const +{ + if (client.GetID() == PVR_INVALID_CLIENT_ID) + return {}; + + CLog::LogFC(LOGDEBUG, LOGPVR, + "Getting datetime first channels added for client {} from the database", + client.GetID()); + + std::unique_lock<CCriticalSection> lock(m_critSection); + + const std::string whereClause{PrepareSQL("idClient = %i", client.GetID())}; + const std::string value{GetSingleValue("clients", "sDateTimeFirstChannelsAdded", whereClause)}; + + if (value.empty()) + return {}; + + return CDateTime::FromDBDateTime(value); +} + void CPVRDatabase::FixupClientIDs() { // Get enabled and disabled PVR client addon infos @@ -548,8 +582,10 @@ int CPVRDatabase::GetClientID(const std::string& addonID, unsigned int instanceI return std::atoi(idString.c_str()); // Create a new entry with a new client id in clients table. - sql = PrepareSQL("INSERT INTO clients (iPriority, sAddonID, iInstanceID) VALUES (%i, '%s', %i)", - 0, addonID.c_str(), instanceID); + // Priority and ChannelsAdded fields will be populated with real values later, on-demand. + sql = PrepareSQL("INSERT INTO clients (iPriority, sAddonID, iInstanceID, " + "sDateTimeFirstChannelsAdded) VALUES (%i, '%s', %i, '%s')", + 0, addonID.c_str(), instanceID, ""); if (ExecuteQuery(sql)) return static_cast<int>(m_pDS->lastinsertid()); diff --git a/xbmc/pvr/PVRDatabase.h b/xbmc/pvr/PVRDatabase.h index 69272b55036c8..8784741801064 100644 --- a/xbmc/pvr/PVRDatabase.h +++ b/xbmc/pvr/PVRDatabase.h @@ -14,6 +14,8 @@ #include <map> #include <vector> +class CDateTime; + namespace PVR { class CPVRChannel; @@ -64,7 +66,7 @@ namespace PVR * @brief Get the minimal database version that is required to operate correctly. * @return The minimal database version. */ - int GetSchemaVersion() const override { return 46; } + int GetSchemaVersion() const override { return 47; } /*! * @brief Get the default sqlite database filename. @@ -102,6 +104,13 @@ namespace PVR */ int GetPriority(const CPVRClient& client) const; + /*! + * @brief Get the date and time first channels were added for the given client. + * @param client The client. + * @return The date and time first channels were added. + */ + CDateTime GetDateTimeFirstChannelsAdded(const CPVRClient& client) const; + /*! * @brief Get the numeric client ID for given addon ID and instance ID from the database. * @param addonID The addon ID. diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 3b0169c618af7..1e8e40a0868ef 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -1053,7 +1053,20 @@ PVR_ERROR CPVRClient::GetChannels(bool radio, PVR_HANDLE_STRUCT handle = {}; handle.callerAddress = this; handle.dataAddress = &channels; - return addon->toAddon->GetChannels(addon, &handle, radio); + const PVR_ERROR error{addon->toAddon->GetChannels(addon, &handle, radio)}; + + if (error == PVR_ERROR_NO_ERROR) + { + const CDateTime& dateTime{GetDateTimeFirstChannelsAdded()}; + if (!dateTime.IsValid()) + { + // Remember when first channels were added for this client. + const_cast<CPVRClient*>(this)->SetDateTimeFirstChannelsAdded( + CDateTime::GetUTCDateTime()); + } + } + + return error; }, (radio && m_clientCapabilities.SupportsRadio()) || (!radio && m_clientCapabilities.SupportsTV())); @@ -2004,6 +2017,30 @@ int CPVRClient::GetPriority() const return *m_priority; } +const CDateTime& CPVRClient::GetDateTimeFirstChannelsAdded() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (!m_firstChannelsAdded.has_value() && m_iClientId > PVR_INVALID_CLIENT_ID) + { + m_firstChannelsAdded = + CServiceBroker::GetPVRManager().GetTVDatabase()->GetDateTimeFirstChannelsAdded(*this); + } + return *m_firstChannelsAdded; +} + +void CPVRClient::SetDateTimeFirstChannelsAdded(const CDateTime& dateTime) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_firstChannelsAdded != dateTime) + { + m_firstChannelsAdded = dateTime; + if (m_iClientId > PVR_INVALID_CLIENT_ID) + { + CServiceBroker::GetPVRManager().GetTVDatabase()->Persist(*this); + } + } +} + void CPVRClient::HandleAddonCallback(const char* strFunctionName, void* kodiInstance, const std::function<void(CPVRClient* client)>& function, diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h index 2e56b7ce33dc1..9f29e6dd051fc 100644 --- a/xbmc/pvr/addons/PVRClient.h +++ b/xbmc/pvr/addons/PVRClient.h @@ -8,6 +8,7 @@ #pragma once +#include "XBDateTime.h" #include "addons/binary-addons/AddonInstanceHandler.h" #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h" #include "pvr/addons/PVRClientCapabilities.h" @@ -805,6 +806,18 @@ class CPVRClient : public ADDON::IAddonInstanceHandler */ void SetPriority(int iPriority); + /*! + * @brief Get the date and time first channels were added for this client. + * @return The date and time first channels were added. + */ + const CDateTime& GetDateTimeFirstChannelsAdded() const; + + /*! + * @brief Set the date and time first channels were added for this client. + * @param dateTime The date and time first channels were added. + */ + void SetDateTimeFirstChannelsAdded(const CDateTime& dateTime); + /*! * @brief Obtain the chunk size to use when reading streams. * @param iChunkSize the chunk size in bytes. @@ -1066,6 +1079,8 @@ class CPVRClient : public ADDON::IAddonInstanceHandler std::vector<std::shared_ptr<CPVRTimerType>> m_timertypes; /*!< timer types supported by this backend */ mutable std::optional<int> m_priority; /*!< priority of the client */ + mutable std::optional<CDateTime> + m_firstChannelsAdded; /*!< date and time the first channels were added for this client */ /* cached data */ std::string m_strBackendName; /*!< the cached backend version */ diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp index 290b2b8bce41a..da92dc8d2f74e 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp @@ -15,7 +15,7 @@ #include "guilib/WindowIDs.h" #include "input/WindowTranslator.h" #include "pvr/PVRManager.h" -#include "pvr/addons/PVRClient.h" // PVR_ANY_CLIENT_ID +#include "pvr/addons/PVRClient.h" #include "pvr/addons/PVRClients.h" #include "pvr/channels/PVRChannel.h" #include "pvr/channels/PVRChannelGroupMember.h" @@ -624,9 +624,26 @@ bool CPVRGUIDirectory::GetChannelsDirectory(CFileItemList& results) const if (playedOnly && !groupMember->Channel()->LastWatched()) continue; - if (dateAdded && (!groupMember->Channel()->DateTimeAdded().IsValid() || - groupMember->Channel()->LastWatched())) - continue; + if (dateAdded) + { + if (groupMember->Channel()->LastWatched()) + continue; + + const CDateTime dtChannelAdded{groupMember->Channel()->DateTimeAdded()}; + if (!dtChannelAdded.IsValid()) + continue; + + const std::shared_ptr<const CPVRClient> client{ + CServiceBroker::GetPVRManager().GetClient(groupMember->ChannelClientID())}; + if (client) + { + const CDateTime& dtFirstChannelsAdded{client->GetDateTimeFirstChannelsAdded()}; + if (dtFirstChannelsAdded.IsValid() && (dtChannelAdded <= dtFirstChannelsAdded)) + { + continue; // Ignore channels added on very first GetChannels call for the client. + } + } + } results.Add(std::make_shared<CFileItem>(groupMember)); } From 495390b00afa566c2a34b190aec9cbe1242ce764 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:04:50 +0200 Subject: [PATCH 404/651] [Windows] Fix crash when audio device not has 'PKEY_Device_EnumeratorName' property Fix crash when computer is accessed with RDP. --- .../Sinks/windows/AESinkFactoryWin32.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp index 3b6abaae9038c..d5869c5e2e312 100644 --- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp @@ -130,14 +130,16 @@ std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails() PropVariantClear(&varName); hr = pProperty->GetValue(PKEY_Device_EnumeratorName, &varName); - if (FAILED(hr)) + if (SUCCEEDED(hr) && varName.pwszVal != nullptr) { - CLog::LogF(LOGERROR, "Retrieval of endpoint enumerator name failed."); - goto failed; + details.strDeviceEnumerator = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal); + StringUtils::ToUpper(details.strDeviceEnumerator); + } + else + { + CLog::LogF(LOGDEBUG, "Retrieval of endpoint enumerator name failed: {}.", + (FAILED(hr)) ? "'GetValue' has failed" : "'varName.pwszVal' is NULL"); } - - details.strDeviceEnumerator = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal); - StringUtils::ToUpper(details.strDeviceEnumerator); PropVariantClear(&varName); if (pDevice->GetId(&pwszID) == S_OK) From 9704ee92b66f3aa8cc54f81d11bfe766695b5e70 Mon Sep 17 00:00:00 2001 From: Sam Nazarko <email@samnazarko.co.uk> Date: Sat, 24 Aug 2024 02:56:26 +0100 Subject: [PATCH 405/651] Keymaps: update OSMC remote keymapping Signed-off-by: Sam Nazarko <email@samnazarko.co.uk> --- system/keymaps/osmc/osmc_remote.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/keymaps/osmc/osmc_remote.xml b/system/keymaps/osmc/osmc_remote.xml index 79f392eb588fd..50b61170261a4 100644 --- a/system/keymaps/osmc/osmc_remote.xml +++ b/system/keymaps/osmc/osmc_remote.xml @@ -18,7 +18,7 @@ <!-- Vol- = volume_down <key id="61624"> RW = rewind Vol- = minus Vol- = minus --> <!-- Vol+ = volume_up <key id="61625"> FF = fastforward Vol+ = equals Vol+ = equals --> <!-- --> -<!-- Keymap created by DarwinDesign version 20-11-02 --> +<!-- Keymap created by DarwinDesign version 24-08-24 --> <!-- --> <keymap> <global> @@ -50,7 +50,7 @@ <x>Stop</x> <volume_down>VolumeDown</volume_down> <volume_up>VolumeUp</volume_up> - <f2>Notification(OSMC Remote Controller, Low Battery Please Replace,5000)</f2> + <f2>Notification(OSMC $LOCALIZE[790],$LOCALIZE[13050],5000,DefaultIconerror.png)</f2> <!-- OSMC Remote Control, Running low on battery --> </keyboard> </global> <Home> From 971be041578ef2f8c4af6a54dde381d43269eeb6 Mon Sep 17 00:00:00 2001 From: Sam Nazarko <email@samnazarko.co.uk> Date: Sat, 24 Aug 2024 02:56:26 +0100 Subject: [PATCH 406/651] Keymaps: Add support for 3rd generation remote Signed-off-by: Sam Nazarko <email@samnazarko.co.uk> --- system/keymaps/osmcv3/osmcv3_remote.xml | 584 ++++++++++++++++++++++++ system/peripherals.xml | 5 + 2 files changed, 589 insertions(+) create mode 100644 system/keymaps/osmcv3/osmcv3_remote.xml diff --git a/system/keymaps/osmcv3/osmcv3_remote.xml b/system/keymaps/osmcv3/osmcv3_remote.xml new file mode 100644 index 0000000000000..adac2fc6c69b9 --- /dev/null +++ b/system/keymaps/osmcv3/osmcv3_remote.xml @@ -0,0 +1,584 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- OSMC remotes use i and c keys that stop functioning with some keyboard languages --> +<!-- in OSMC. We have remapped those keys in OSMC to kpleftparen and kprightparen with --> +<!-- udev to overcome this issue. This file maps those keys to Kodi actions and adds --> +<!-- tweaks to provide enhanced function.The buttons map in Kodi as follows... --> +<!-- --> +<!-- OSMC with udev remap Stock layout --> +<!-- Home = escape <key id="61467"> Home = escape --> +<!-- Info = leftbracket <key id="61480"> Info = i --> +<!-- Up = up <key id="61568"> Up = up --> +<!-- Down = down <key id="61569"> Down = down --> +<!-- Left = left <key id="61570"> Left = left --> +<!-- Right = right <key id="61571"> Right = right --> +<!-- OK = return <key id="61453"> OK = return --> +<!-- Back = browser_back <key id="61616"> Back = browser_back --> +<!-- Menu = rightbracket <key id="61481"> Menu = c --> +<!-- Play = play_pause <key id="61629"> Play = play_pause --> +<!-- Stop = stop <key id="61628"> Stop = stop --> +<!-- Vol- = volume_down <key id="61624"> Vol- = volume_down --> +<!-- Vol+ = volume_up <key id="61625"> Vol+ = volume_up --> +<!-- --> +<!-- Keymap created by DarwinDesign version 24-08-24 --> +<!-- --> +<keymap> + <global> + <keyboard> + <escape>PreviousMenu</escape> + <home>PreviousMenu</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + <leftbracket>Info</leftbracket> + <i>Info</i> + <leftbracket mod="longpress">noop</leftbracket> <!-- stops cycling --> + <i mod="longpress">noop</i> <!-- stops cycling --> + <left>Left</left> + <right>Right</right> + <up>Up</up> + <down>Down</down> + <return>Select</return> + <return mod="longpress">noop</return> <!-- removes default context menu & stops cycling --> + <browser_back>Back</browser_back> + <rightbracket>ContextMenu</rightbracket> + <c>ContextMenu</c> + <rightbracket mod="longpress">Menu</rightbracket> + <c mod="longpress">Menu</c> + <play_pause>PlayPause</play_pause> + <p>PlayPause</p> + <play_pause mod="longpress">noop</play_pause> <!-- removes default info & stops cycling --> + <p mod="longpress">noop</p> + <stop>Stop</stop> + <x>Stop</x> + <volume_down>VolumeDown</volume_down> + <volume_up>VolumeUp</volume_up> + <f1>VoiceRecognizer</f1> <!-- Mic Button --> + <browser_search>RunScript(script.globalsearch)</browser_search> <!-- OSMC Button --> + <f2>Notification(OSMC $LOCALIZE[790],$LOCALIZE[13050],5000,DefaultIconerror.png)</f2> <!-- OSMC Remote Control, Running low on battery --> + <f3>Notification(OSMC $LOCALIZE[790],$LOCALIZE[38373],5000)</f3> <!-- OSMC Remote Control, Battery is charging --> + <f4>Notification(OSMC $LOCALIZE[790],$LOCALIZE[38374],5000)</f4> <!-- OSMC Remote Control, Battery is fully charged --> + <f5>Notification(OSMC $LOCALIZE[790],$LOCALIZE[38375],5000)</f5> <!-- OSMC Remote Control, Battery charger removed --> + </keyboard> + </global> + <Home> + <keyboard> + <escape>CECActivateSource</escape> + <home>CECActivateSource</home> + <escape mod="longpress">CECStandby</escape> + <home mod="longpress">CECStandby</home> + <leftbracket>info</leftbracket> + <i>info</i> + <browser_back mod="longpress">ActivateWindow(ShutdownMenu)</browser_back> + <return mod="longpress">ReloadSkin()</return> + <play_pause mod="longpress">UpdateLibrary(video)</play_pause> + <p mod="longpress">UpdateLibrary(video)</p> + </keyboard> + </Home> + <VirtualKeyboard> + <keyboard> + <rightbracket mod="longpress">noop</rightbracket> + <c mod="longpress">noop</c> + <up mod="longpress">Shift</up> + <down mod="longpress">Symbols</down> + <return mod="longpress">Enter</return> + </keyboard> + </VirtualKeyboard> + <FileManager> + <keyboard> + <right mod="longpress">Highlight</right> + <left mod="longpress">Highlight</left> + </keyboard> + </FileManager> + <FullscreenVideo> + <keyboard> + <escape>ActivateWindow(videobookmarks)</escape> + <home>ActivateWindow(videobookmarks)</home> + <escape mod="longpress">playerdebug</escape> + <home mod="longpress">playerdebug</home> + <leftbracket>info</leftbracket> + <i>info</i> + <leftbracket mod="longpress">playerprocessinfo</leftbracket> + <i mod="longpress">playerprocessinfo</i> + <return mod="longpress">Playlist</return> + <up mod="longpress">SkipNext</up> + <down mod="longpress">SkipPrevious</down> + <left mod="longpress">AudioDelay</left> + <right mod="longpress">subtitledelay</right> + <rightbracket>ActivateWindow(osdvideosettings)</rightbracket> + <c>ActivateWindow(osdvideosettings)</c> + <rightbracket mod="longpress">ActivateWindow(osdaudiosettings)</rightbracket> + <c mod="longpress">ActivateWindow(osdaudiosettings)</c> + <play_pause mod="longpress">showsubtitles</play_pause> + <p mod="longpress">showsubtitles</p> + <stop mod="longpress">ActivateWindow(osdsubtitlesettings)</stop> + <x mod="longpress">ActivateWindow(osdsubtitlesettings)</x> + </keyboard> + </FullscreenVideo> + <FullscreenGame> + <keyboard> + <leftbracket>info</leftbracket> + <i>info</i> + <leftbracket mod="longpress">playerprocessinfo</leftbracket> + <i mod="longpress">playerprocessinfo</i> + <rightbracket>OSD</rightbracket> + <c>OSD</c> + </keyboard> + </FullscreenGame> + <FullscreenInfo> + <keyboard> + <leftbracket>Back</leftbracket> + <i>Back</i> + <leftbracket mod="longpress">playerprocessinfo</leftbracket> + <i mod="longpress">playerprocessinfo</i> + <rightbracket>Back</rightbracket> + <c>Back</c> + </keyboard> + </FullscreenInfo> + <Visualisation> + <keyboard> + <leftbracket>info</leftbracket> + <i>info</i> + <leftbracket mod="longpress">playerprocessinfo</leftbracket> + <i mod="longpress">playerprocessinfo</i> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + <return mod="longpress">ActivateWindow(MusicPlaylist)</return> + <rightbracket>Addon.Default.OpenSettings(xbmc.player.musicviz)</rightbracket> + <c>Addon.Default.OpenSettings(xbmc.player.musicviz)</c> + <rightbracket mod="longpress">ActivateWindow(VisualisationPresetList)</rightbracket> + <c mod="longpress">ActivateWindow(VisualisationPresetList)</c> + <p/> + </keyboard> + </Visualisation> + <MusicOSD> + <keyboard> + <escape>back</escape> + <home>back</home> + <leftbracket>info</leftbracket> + <i>info</i> + <leftbracket mod="longpress">playerprocessinfo</leftbracket> + <i mod="longpress">playerprocessinfo</i> + <return mod="longpress">back</return> + <rightbracket>Addon.Default.OpenSettings(xbmc.player.musicviz)</rightbracket> + <c>Addon.Default.OpenSettings(xbmc.player.musicviz)</c> + <rightbracket mod="longpress">ActivateWindow(VisualisationPresetList)</rightbracket> + <c mod="longpress">ActivateWindow(VisualisationPresetList)</c> + <p/> + </keyboard> + </MusicOSD> + <VisualisationPresetList> + <keyboard> + <leftbracket>info</leftbracket> + <i>info</i> + <leftbracket mod="longpress">playerprocessinfo</leftbracket> + <i mod="longpress">playerprocessinfo</i> + <rightbracket>back</rightbracket> + <c>back</c> + <p/> + </keyboard> + </VisualisationPresetList> + <slideshow> + <keyboard> + <escape>back</escape> + <home>back</home> + <leftbracket>info</leftbracket> + <i>info</i> + <play_pause>pause</play_pause> + <p>pause</p> + <up mod="longpress">ZoomIn</up> + <down mod="longpress">ZoomOut</down> + <return mod="longpress">ZoomNormal</return> + <rightbracket></rightbracket> <!-- removes mapping from osmc-classic --> + </keyboard> + </slideshow> + <VideoOSD> + <keyboard> + <escape>ActivateWindow(videobookmarks)</escape> + <home>ActivateWindow(videobookmarks)</home> + <escape mod="longpress">playerdebug</escape> + <home mod="longpress">playerdebug</home> + <leftbracket>info</leftbracket> + <i>info</i> + <leftbracket mod="longpress">playerprocessinfo</leftbracket> + <i mod="longpress">playerprocessinfo</i> + <up mod="longpress">SkipNext</up> + <down mod="longpress">SkipPrevious</down> + <left mod="longpress">AudioDelay</left> + <right mod="longpress">subtitledelay</right> + <rightbracket>ActivateWindow(osdvideosettings)</rightbracket> + <return mod="longpress">back</return> + <c>ActivateWindow(osdvideosettings)</c> + <rightbracket mod="longpress">ActivateWindow(osdaudiosettings)</rightbracket> + <c mod="longpress">ActivateWindow(osdaudiosettings)</c> + <play_pause mod="longpress">showsubtitles</play_pause> + <p mod="longpress">showsubtitles</p> + <stop mod="longpress">ActivateWindow(osdsubtitlesettings)</stop> + <x mod="longpress">ActivateWindow(osdsubtitlesettings)</x> + </keyboard> + </VideoOSD> + <VideoMenu> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + <leftbracket>info</leftbracket> + <i>info</i> + <leftbracket mod="longpress">playerprocessinfo</leftbracket> + <i mod="longpress">playerprocessinfo</i> + <rightbracket></rightbracket> <!-- removes mapping from osmc-classic --> + </keyboard> + </VideoMenu> + <OSDVideoSettings> + <keyboard> + <leftbracket>info</leftbracket> + <i>info</i> + <leftbracket mod="longpress">playerprocessinfo</leftbracket> + <i mod="longpress">playerprocessinfo</i> + <rightbracket>back</rightbracket> + <c>back</c> + <stop>back</stop> + <x>back</x> + </keyboard> + </OSDVideoSettings> + <OSDAudioSettings> + <keyboard> + <leftbracket>info</leftbracket> + <i>info</i> + <leftbracket mod="longpress">playerprocessinfo</leftbracket> + <i mod="longpress">playerprocessinfo</i> + <rightbracket>back</rightbracket> + <c>back</c> + <stop>back</stop> + <x>back</x> + </keyboard> + </OSDAudioSettings> + <osdsubtitlesettings> + <keyboard> + <leftbracket>info</leftbracket> + <i>info</i> + <leftbracket mod="longpress">playerprocessinfo</leftbracket> + <i mod="longpress">playerprocessinfo</i> + <rightbracket>back</rightbracket> + <c>back</c> + <stop>back</stop> + <x>back</x> + </keyboard> + </osdsubtitlesettings> + <VideoBookmarks> + <keyboard> + <escape>back</escape> + <home>back</home> + <rightbracket mod="longpress">back</rightbracket> + <c mod="longpress">back</c> + </keyboard> + </VideoBookmarks> + <Videos> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + <return mod="longpress">SendClick(14)</return> <!-- Toggle view between unwatched and all videos --> + <play_pause mod="longpress">togglewatched</play_pause> + <p mod="longpress">togglewatched</p> + <browser_search>SendClick(8)</browser_search> <!-- Search --> + </keyboard> + </Videos> + <VideoPlaylist> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + <return mod="longpress">Back</return> + </keyboard> + </VideoPlaylist> + <ContextMenu> + <keyboard> + <rightbracket>Back</rightbracket> + <c>Back</c> + </keyboard> + </ContextMenu> + <MusicInformation> + <keyboard> + <escape>back</escape> + <home>back</home> + <leftbracket>Back</leftbracket> + <i>Back</i> + <leftbracket mod="longpress">noop</leftbracket> <!-- stops cycling --> + <i mod="longpress">noop</i> <!-- stops cycling --> + <rightbracket>Back</rightbracket> + <c>Back</c> + </keyboard> + </MusicInformation> + <MusicPlaylist> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + <return mod="longpress">back</return> + </keyboard> + </MusicPlaylist> + <SongInformation> + <keyboard> + <escape>back</escape> + <home>back</home> + <leftbracket>Back</leftbracket> + <i>Back</i> + <leftbracket mod="longpress">noop</leftbracket> <!-- stops cycling --> + <i mod="longpress">noop</i> <!-- stops cycling --> + <rightbracket>Back</rightbracket> + <c>Back</c> + </keyboard> + </SongInformation> + <MovieInformation> + <keyboard> + <escape>back</escape> + <home>back</home> + <leftbracket>Back</leftbracket> + <i>Back</i> + <leftbracket mod="longpress">noop</leftbracket> <!-- stops cycling --> + <i mod="longpress">noop</i> <!-- stops cycling --> + <rightbracket>Back</rightbracket> + <c>Back</c> + </keyboard> + </MovieInformation> + <PictureInfo> + <keyboard> + <escape>back</escape> + <home>back</home> + <leftbracket>Back</leftbracket> + <i>Back</i> + <leftbracket mod="longpress">noop</leftbracket> <!-- stops cycling --> + <i mod="longpress">noop</i> <!-- stops cycling --> + <rightbracket>Back</rightbracket> + <c>Back</c> + </keyboard> + </PictureInfo> + <FullscreenLiveTV> + <keyboard> + <rightbracket>ActivateWindow(PVROSDChannels)</rightbracket> + <c>ActivateWindow(PVROSDChannels)</c> + <leftbracket>info</leftbracket> + <i>info</i> + <leftbracket mod="longpress">playerprocessinfo</leftbracket> + <i mod="longpress">playerprocessinfo</i> + <left mod="longpress">AudioDelay</left> + <right mod="longpress">subtitledelay</right> + <return mod="longpress">Record</return> + <play_pause mod="longpress">showsubtitles</play_pause> + <p mod="longpress">showsubtitles</p> + <stop mod="longpress">ActivateWindow(Teletext)</stop> + <x mod="longpress">ActivateWindow(Teletext)</x> + </keyboard> + </FullscreenLiveTV> + <TVGuide> + <keyboard> + <return mod="longpress">Record</return> + </keyboard> + </TVGuide> + <FullscreenRadio> + <keyboard> + <rightbracket>ActivateWindow(PVROSDChannels)</rightbracket> + <c>ActivateWindow(PVROSDChannels)</c> + </keyboard> + </FullscreenRadio> + <AddonInformation> + <keyboard> + <escape>back</escape> + <home>back</home> + <leftbracket>Back</leftbracket> + <i>Back</i> + <leftbracket mod="longpress">noop</leftbracket> <!-- stops cycling --> + <i mod="longpress">noop</i> <!-- stops cycling --> + <rightbracket>Back</rightbracket> + <c>Back</c> + </keyboard> + </AddonInformation> + <PlayerProcessInfo> + <keyboard> + <leftbracket>back</leftbracket> + <i>back</i> + <rightbracket>ActivateWindow(osdvideosettings)</rightbracket> + <c>ActivateWindow(osdvideosettings)</c> + <rightbracket mod="longpress">noop</rightbracket> + <c mod="longpress">noop</c> + <stop mod="longpress">ActivateWindow(osdsubtitlesettings)</stop> + <x mod="longpress">ActivateWindow(osdsubtitlesettings)</x> + </keyboard> + </PlayerProcessInfo> + <yesnodialog> <!-- Added to allow CEC when update dialog box appears --> + <keyboard> + <escape>CECActivateSource</escape> + <home>CECActivateSource</home> + <escape mod="longpress">CECStandby</escape> + <home mod="longpress">CECStandby</home> + </keyboard> + </yesnodialog> + <selectdialog> + <keyboard> + <escape>back</escape> + <home>back</home> + </keyboard> + </selectdialog> + <contextmenu> + <keyboard> + <escape>back</escape> + <home>back</home> + </keyboard> + </contextmenu> + <addonsettings> + <keyboard> + <escape>back</escape> + <home>back</home> + </keyboard> + </addonsettings> + <addonbrowser> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </addonbrowser> + <filemanager> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </filemanager> + <interfacesettings> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </interfacesettings> + <systeminfo> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </systeminfo> + <eventlog> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </eventlog> + <playersettings> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </playersettings> + <mediasettings> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </mediasettings> + <pvrsettings> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </pvrsettings> + <servicesettings> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </servicesettings> + <gamesettings> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </gamesettings> + <profiles> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </profiles> + <systemsettings> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </systemsettings> + <music> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + <browser_search>SendClick(8)</browser_search> <!-- Search --> + </keyboard> + </music> + <pictures> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </pictures> + <skinsettings> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </skinsettings> + <musicplaylisteditor> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </musicplaylisteditor> + <games> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </games> + <programs> + <keyboard> + <escape>ActivateWindow(Home)</escape> + <home>ActivateWindow(Home)</home> + <escape mod="longpress">fullscreen</escape> + <home mod="longpress">fullscreen</home> + </keyboard> + </programs> + </keymap> diff --git a/system/peripherals.xml b/system/peripherals.xml index a110c0db6088f..66dd13aa9bf79 100644 --- a/system/peripherals.xml +++ b/system/peripherals.xml @@ -57,6 +57,11 @@ <setting key="keymap" value="osmc" label="35007" configurable="1"/> </peripheral> + <peripheral vendor_product="2017:1710,2017:1720" bus="usb" name="OSMC RF Remote" mapTo="hid"> + <setting key="do_not_use_custom_keymap" type="bool" value="0" label="35009" order="1"/> + <setting key="keymap" value="osmcv3" label="35007" configurable="1"/> + </peripheral> + <peripheral class="joystick" name="joystick" mapTo="joystick"> <setting key="appearance" type="addon" addontype="kodi.game.controller" label="480" order="1"/> <setting key="left_stick_deadzone" type="float" label="35078" order="2" value="0.2" min="0.0" step="0.05" max="1.0" /> From 4cc349dfa9c9ba428374579fd943b92e27019f53 Mon Sep 17 00:00:00 2001 From: Garrett Brown <themagnificentmrb@gmail.com> Date: Sat, 10 Aug 2024 18:38:14 -0700 Subject: [PATCH 407/651] RetroPlayer: Show emulator name and icon in Advanced Settings dialog --- .../skin.estuary/xml/DialogAddonSettings.xml | 38 +++++++++++++++++++ xbmc/addons/gui/GUIDialogAddonSettings.cpp | 30 ++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/addons/skin.estuary/xml/DialogAddonSettings.xml b/addons/skin.estuary/xml/DialogAddonSettings.xml index 38349c180051e..737366dfdd65e 100644 --- a/addons/skin.estuary/xml/DialogAddonSettings.xml +++ b/addons/skin.estuary/xml/DialogAddonSettings.xml @@ -123,6 +123,44 @@ <param name="label" value="" /> </include> </control> + <control type="group"> + <description>Currently-playing emulator name and icon</description> + <visible>Player.HasGame + String.IsEqual(Window(addonsettings).Property(Addon.Type),kodi.gameclient)</visible> + <left>1510</left> + <width>300</width> + <top>528</top> + <height>260</height> + <control type="label"> + <description>Emulator name</description> + <height>30</height> + <font>font23_narrow</font> + <textoffsetx>20</textoffsetx> + <textcolor>button_focus</textcolor> + <shadowcolor>text_shadow</shadowcolor> + <align>center</align> + <label>$INFO[Window(addonsettings).Property(GameClient.Name)]</label> + </control> + <control type="label"> + <description>Emulator version</description> + <top>30</top> + <height>30</height> + <font>font23_narrow</font> + <textoffsetx>20</textoffsetx> + <textcolor>button_focus</textcolor> + <shadowcolor>text_shadow</shadowcolor> + <align>center</align> + <label>$INFO[Window(addonsettings).Property(Addon.Version)]</label> + </control> + <control type="image"> + <description>Emulator icon</description> + <left>50</left> + <top>70</top> + <height>200</height> + <width>200</width> + <aspectratio>keep</aspectratio> + <texture>$INFO[Window(addonsettings).Property(Addon.Icon)]</texture> + </control> + </control> <control type="radiobutton" id="20"> <left>29</left> <top>700</top> diff --git a/xbmc/addons/gui/GUIDialogAddonSettings.cpp b/xbmc/addons/gui/GUIDialogAddonSettings.cpp index 60cbaa2a2ea29..8efb42876b2bc 100644 --- a/xbmc/addons/gui/GUIDialogAddonSettings.cpp +++ b/xbmc/addons/gui/GUIDialogAddonSettings.cpp @@ -14,10 +14,12 @@ #include "GUIUserMessages.h" #include "ServiceBroker.h" #include "addons/AddonManager.h" +#include "addons/addoninfo/AddonInfo.h" #include "addons/addoninfo/AddonType.h" #include "addons/settings/AddonSettings.h" #include "dialogs/GUIDialogSelect.h" #include "dialogs/GUIDialogYesNo.h" +#include "games/addons/GameClient.h" #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" @@ -37,7 +39,14 @@ #define CONTROL_BTN_LEVELS 20 using namespace ADDON; -using namespace KODI::MESSAGING; +using namespace KODI; +using namespace MESSAGING; + +namespace +{ +// Fallback icon shown when no add-on icon is available +constexpr const char* DEFAULT_ADDON_ICON = "DefaultAddon.png"; +} // namespace CGUIDialogAddonSettings::CGUIDialogAddonSettings() : CGUIDialogSettingsManagerBase(WINDOW_DIALOG_ADDON_SETTINGS, "DialogAddonSettings.xml") @@ -418,8 +427,25 @@ void CGUIDialogAddonSettings::SetupView() CGUIDialogSettingsManagerBase::SetupView(); - // set addon id as window property + // set addon properties as window properties SetProperty("Addon.ID", m_addon->ID()); + SetProperty("Addon.Type", ADDON::CAddonInfo::TranslateType(m_addon->Type(), false)); + if (!m_addon->Icon().empty()) + SetProperty("Addon.Icon", m_addon->Icon()); + else + SetProperty("Addon.Icon", DEFAULT_ADDON_ICON); + SetProperty("Addon.Version", m_addon->Version().asString()); + + if (m_addon->Type() == ADDON::AddonType::GAMEDLL) + { + std::shared_ptr<GAME::CGameClient> gameClient = + std::dynamic_pointer_cast<GAME::CGameClient>(m_addon); + if (gameClient) + { + SetProperty("GameClient.Name", gameClient->GetEmulatorName()); + SetProperty("GameClient.Platforms", gameClient->GetPlatforms()); + } + } // set heading SetHeading(StringUtils::Format("$LOCALIZE[10004] - {}", From cd1020ed56311d121032ddb872d32d09f2a8dc3c Mon Sep 17 00:00:00 2001 From: DeltaMikeCharlie <127641886+DeltaMikeCharlie@users.noreply.github.com> Date: Sun, 25 Aug 2024 21:07:17 +1000 Subject: [PATCH 408/651] [PVR][guilib][GUIInfoManager] Add support for VideoPlayer.ParentalRatingCode/Icon/Source --- xbmc/GUIInfoManager.cpp | 30 ++++++++++++++++++++++++++ xbmc/guilib/guiinfo/GUIInfoLabels.h | 3 +++ xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 7 +++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 5fd4b308a24c6..85d2875428260 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -3889,6 +3889,33 @@ const infomap musicplayer[] = {{ "title", MUSICPLAYER_TITLE }, /// @return The parental rating of the currently playing programme (PVR). /// <p> /// } +/// \table_row3{ <b>`VideoPlayer.ParentalRatingCode`</b>, +/// \anchor VideoPlayer_ParentalRatingCode +/// _string_, +/// @return The parental rating code (eg: 'PG'\, etc) of the currently playing programme (PVR). +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link VideoPlayer_ParentalRatingCode `VideoPlayer.ParentalRatingCode`\endlink +/// <p> +/// } +/// \table_row3{ <b>`VideoPlayer.ParentalRatingIcon`</b>, +/// \anchor VideoPlayer_ParentalRatingIcon +/// _string_, +/// @return The parental rating icon path of the currently playing programme (PVR). +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link VideoPlayer_ParentalRatingIcon `VideoPlayer.ParentalRatingIcon`\endlink +/// <p> +/// } +/// \table_row3{ <b>`VideoPlayer.ParentalRatingSource`</b>, +/// \anchor VideoPlayer_ParentalRatingSource +/// _string_, +/// @return The source used to determine the parental rating of the currently playing programme (PVR). +/// Values could include the Country alpha-3 code or the name/abbreviation +/// of the authority issuing the rating code. Can be used with +/// the \link VideoPlayer_ParentalRatingCode `ParentalRatingCode`\endlink for skin-derived icons if required. +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link VideoPlayer_ParentalRatingSource `VideoPlayer.ParentalRatingSource`\endlink +/// <p> +/// } /// \table_row3{ <b>`VideoPlayer.DBID`</b>, /// \anchor VideoPlayer_DBID /// _string_, @@ -4029,6 +4056,9 @@ const infomap videoplayer[] = {{ "title", VIDEOPLAYER_TITLE }, { "channelgroup", VIDEOPLAYER_CHANNEL_GROUP }, { "hasepg", VIDEOPLAYER_HAS_EPG }, { "parentalrating", VIDEOPLAYER_PARENTAL_RATING }, + { "parentalratingcode", VIDEOPLAYER_PARENTAL_RATING_CODE }, + { "parentalratingicon", VIDEOPLAYER_PARENTAL_RATING_ICON }, + { "parentalratingsource", VIDEOPLAYER_PARENTAL_RATING_SOURCE }, { "isstereoscopic", VIDEOPLAYER_IS_STEREOSCOPIC }, { "stereoscopicmode", VIDEOPLAYER_STEREOSCOPIC_MODE }, { "canresumelivetv", VIDEOPLAYER_CAN_RESUME_LIVE_TV }, diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index ff7a5861eb240..be24323d2f128 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -324,6 +324,9 @@ // More PVR infolabels #define VIDEOPLAYER_CHANNEL_LOGO 333 #define VIDEOPLAYER_EPISODEPART 334 +#define VIDEOPLAYER_PARENTAL_RATING_CODE 335 +#define VIDEOPLAYER_PARENTAL_RATING_ICON 336 +#define VIDEOPLAYER_PARENTAL_RATING_SOURCE 337 #define CONTAINER_HAS_PARENT_ITEM 341 #define CONTAINER_CAN_FILTER 342 diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index c1093c254803f..c89ea997831c7 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -429,7 +429,6 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, case LISTITEM_DIRECTOR: case LISTITEM_CHANNEL_NUMBER: case LISTITEM_PREMIERED: - case VIDEOPLAYER_PARENTAL_RATING: case LISTITEM_PARENTAL_RATING: case LISTITEM_PARENTAL_RATING_CODE: case LISTITEM_PARENTAL_RATING_ICON: @@ -584,12 +583,15 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, } return false; } + case VIDEOPLAYER_PARENTAL_RATING_CODE: case LISTITEM_PARENTAL_RATING_CODE: strValue = recording->GetParentalRatingCode(); return true; + case VIDEOPLAYER_PARENTAL_RATING_ICON: case LISTITEM_PARENTAL_RATING_ICON: strValue = recording->GetParentalRatingIcon(); return true; + case VIDEOPLAYER_PARENTAL_RATING_SOURCE: case LISTITEM_PARENTAL_RATING_SOURCE: strValue = recording->GetParentalRatingSource(); return true; @@ -832,6 +834,7 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, } return false; } + case VIDEOPLAYER_PARENTAL_RATING_CODE: case LISTITEM_PARENTAL_RATING_CODE: strValue = epgTag->ParentalRatingCode(); return true; @@ -841,9 +844,11 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, case LISTITEM_PVR_INSTANCE_NAME: strValue = CServiceBroker::GetPVRManager().GetClient(epgTag->ClientID())->GetInstanceName(); return true; + case VIDEOPLAYER_PARENTAL_RATING_ICON: case LISTITEM_PARENTAL_RATING_ICON: strValue = epgTag->ParentalRatingIcon(); return true; + case VIDEOPLAYER_PARENTAL_RATING_SOURCE: case LISTITEM_PARENTAL_RATING_SOURCE: strValue = epgTag->ParentalRatingSource(); return true; From 9c9bc7d6087ceb3995b4a3cd27d1bfdb8b2c339e Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:36:20 +0200 Subject: [PATCH 409/651] [PVR] Cleanup: Get rid of PVR_INVALID_CLIENT_ID and PVR_ANY_CLIENT_ID. Introduce PVR_CLIENT_INVALID_UID. --- xbmc/pvr/CMakeLists.txt | 1 + xbmc/pvr/PVRConstants.h | 18 ++++++++++++++++++ xbmc/pvr/PVRDatabase.cpp | 9 +++++---- xbmc/pvr/PVRManager.cpp | 3 ++- xbmc/pvr/addons/PVRClient.cpp | 9 +++++---- xbmc/pvr/addons/PVRClient.h | 3 --- xbmc/pvr/addons/PVRClients.cpp | 3 ++- xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp | 12 +++++++----- xbmc/pvr/filesystem/PVRGUIDirectory.cpp | 3 ++- xbmc/pvr/guilib/PVRGUIActionsChannels.cpp | 5 +++-- xbmc/pvr/timers/PVRTimerRuleMatcher.cpp | 4 ++-- xbmc/pvr/timers/PVRTimers.cpp | 3 ++- 12 files changed, 49 insertions(+), 24 deletions(-) create mode 100644 xbmc/pvr/PVRConstants.h diff --git a/xbmc/pvr/CMakeLists.txt b/xbmc/pvr/CMakeLists.txt index b7cbc7d11e750..a0c262603d35d 100644 --- a/xbmc/pvr/CMakeLists.txt +++ b/xbmc/pvr/CMakeLists.txt @@ -19,6 +19,7 @@ set(HEADERS IPVRComponent.h PVRChannelGroupImageFileLoader.h PVRChannelNumberInputHandler.h PVRComponentRegistration.h + PVRConstants.h PVRContextMenus.h PVRDatabase.h PVRDescrambleInfo.h diff --git a/xbmc/pvr/PVRConstants.h b/xbmc/pvr/PVRConstants.h new file mode 100644 index 0000000000000..06d389fe96e3a --- /dev/null +++ b/xbmc/pvr/PVRConstants.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2024 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 + +namespace PVR +{ +/*! + * @brief Denotes an invalid PVR client UID. + */ +static constexpr int PVR_CLIENT_INVALID_UID{-1}; + +} // namespace PVR diff --git a/xbmc/pvr/PVRDatabase.cpp b/xbmc/pvr/PVRDatabase.cpp index 4b6626deca9f0..ec4428384cb25 100644 --- a/xbmc/pvr/PVRDatabase.cpp +++ b/xbmc/pvr/PVRDatabase.cpp @@ -13,6 +13,7 @@ #include "addons/addoninfo/AddonInfo.h" #include "addons/addoninfo/AddonType.h" #include "dbwrappers/dataset.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/addons/PVRClient.h" #include "pvr/addons/PVRClientUID.h" #include "pvr/channels/PVRChannel.h" @@ -410,7 +411,7 @@ bool CPVRDatabase::DeleteClients() bool CPVRDatabase::Persist(const CPVRClient& client) { - if (client.GetID() == PVR_INVALID_CLIENT_ID) + if (client.GetID() == PVR_CLIENT_INVALID_UID) return false; CLog::LogFC(LOGDEBUG, LOGPVR, "Persisting client {} to database", client.GetID()); @@ -432,7 +433,7 @@ bool CPVRDatabase::Persist(const CPVRClient& client) bool CPVRDatabase::Delete(const CPVRClient& client) { - if (client.GetID() == PVR_INVALID_CLIENT_ID) + if (client.GetID() == PVR_CLIENT_INVALID_UID) return false; CLog::LogFC(LOGDEBUG, LOGPVR, "Deleting client {} from the database", client.GetID()); @@ -447,7 +448,7 @@ bool CPVRDatabase::Delete(const CPVRClient& client) int CPVRDatabase::GetPriority(const CPVRClient& client) const { - if (client.GetID() == PVR_INVALID_CLIENT_ID) + if (client.GetID() == PVR_CLIENT_INVALID_UID) return 0; CLog::LogFC(LOGDEBUG, LOGPVR, "Getting priority for client {} from the database", client.GetID()); @@ -465,7 +466,7 @@ int CPVRDatabase::GetPriority(const CPVRClient& client) const CDateTime CPVRDatabase::GetDateTimeFirstChannelsAdded(const CPVRClient& client) const { - if (client.GetID() == PVR_INVALID_CLIENT_ID) + if (client.GetID() == PVR_CLIENT_INVALID_UID) return {}; CLog::LogFC(LOGDEBUG, LOGPVR, diff --git a/xbmc/pvr/PVRManager.cpp b/xbmc/pvr/PVRManager.cpp index 47bc3a43fce4c..742561e7c9f0a 100644 --- a/xbmc/pvr/PVRManager.cpp +++ b/xbmc/pvr/PVRManager.cpp @@ -14,6 +14,7 @@ #include "interfaces/AnnouncementManager.h" #include "messaging/ApplicationMessenger.h" #include "pvr/PVRComponentRegistration.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/PVRDatabase.h" #include "pvr/PVRPlaybackState.h" #include "pvr/addons/PVRClient.h" @@ -281,7 +282,7 @@ std::shared_ptr<CPVRClients> CPVRManager::Clients() const std::shared_ptr<CPVRClient> CPVRManager::GetClient(const CFileItem& item) const { - int iClientID = PVR_INVALID_CLIENT_ID; + int iClientID = PVR_CLIENT_INVALID_UID; if (item.HasPVRChannelInfoTag()) iClientID = item.GetPVRChannelInfoTag()->ClientID(); diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 1e8e40a0868ef..172b2efea7f5f 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -18,6 +18,7 @@ #include "events/NotificationEvent.h" #include "filesystem/SpecialProtocol.h" #include "guilib/LocalizeStrings.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/PVRDatabase.h" #include "pvr/PVRDescrambleInfo.h" #include "pvr/PVRManager.h" @@ -1999,7 +2000,7 @@ void CPVRClient::SetPriority(int iPriority) if (m_priority != iPriority) { m_priority = iPriority; - if (m_iClientId > PVR_INVALID_CLIENT_ID) + if (m_iClientId != PVR_CLIENT_INVALID_UID) { CServiceBroker::GetPVRManager().GetTVDatabase()->Persist(*this); } @@ -2010,7 +2011,7 @@ void CPVRClient::SetPriority(int iPriority) int CPVRClient::GetPriority() const { std::unique_lock<CCriticalSection> lock(m_critSection); - if (!m_priority.has_value() && m_iClientId > PVR_INVALID_CLIENT_ID) + if (!m_priority.has_value() && m_iClientId != PVR_CLIENT_INVALID_UID) { m_priority = CServiceBroker::GetPVRManager().GetTVDatabase()->GetPriority(*this); } @@ -2020,7 +2021,7 @@ int CPVRClient::GetPriority() const const CDateTime& CPVRClient::GetDateTimeFirstChannelsAdded() const { std::unique_lock<CCriticalSection> lock(m_critSection); - if (!m_firstChannelsAdded.has_value() && m_iClientId > PVR_INVALID_CLIENT_ID) + if (!m_firstChannelsAdded.has_value() && m_iClientId != PVR_CLIENT_INVALID_UID) { m_firstChannelsAdded = CServiceBroker::GetPVRManager().GetTVDatabase()->GetDateTimeFirstChannelsAdded(*this); @@ -2034,7 +2035,7 @@ void CPVRClient::SetDateTimeFirstChannelsAdded(const CDateTime& dateTime) if (m_firstChannelsAdded != dateTime) { m_firstChannelsAdded = dateTime; - if (m_iClientId > PVR_INVALID_CLIENT_ID) + if (m_iClientId != PVR_CLIENT_INVALID_UID) { CServiceBroker::GetPVRManager().GetTVDatabase()->Persist(*this); } diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h index 9f29e6dd051fc..2393b6f1fb3f8 100644 --- a/xbmc/pvr/addons/PVRClient.h +++ b/xbmc/pvr/addons/PVRClient.h @@ -50,9 +50,6 @@ class CPVRTimerInfoTag; class CPVRTimerType; class CPVRTimersContainer; -static constexpr int PVR_ANY_CLIENT_ID = -1; -static constexpr int PVR_INVALID_CLIENT_ID = -2; - /*! * Interface from Kodi to a PVR add-on. * diff --git a/xbmc/pvr/addons/PVRClients.cpp b/xbmc/pvr/addons/PVRClients.cpp index 5863360f4815f..ac0990545042d 100644 --- a/xbmc/pvr/addons/PVRClients.cpp +++ b/xbmc/pvr/addons/PVRClients.cpp @@ -15,6 +15,7 @@ #include "addons/addoninfo/AddonType.h" #include "guilib/LocalizeStrings.h" #include "messaging/ApplicationMessenger.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/PVREventLogJob.h" #include "pvr/PVRManager.h" #include "pvr/PVRPlaybackState.h" @@ -290,7 +291,7 @@ void CPVRClients::OnAddonEvent(const AddonEvent& event) std::shared_ptr<CPVRClient> CPVRClients::GetClient(int clientId) const { - if (clientId <= PVR_INVALID_CLIENT_ID) + if (clientId == PVR_CLIENT_INVALID_UID) return {}; std::unique_lock<CCriticalSection> lock(m_critSection); diff --git a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp index 9f9ebf268ae98..394f88646afac 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp @@ -13,6 +13,7 @@ #include "guilib/GUIMessage.h" #include "guilib/LocalizeStrings.h" #include "messaging/helpers/DialogOKHelper.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/PVRManager.h" #include "pvr/addons/PVRClient.h" #include "pvr/addons/PVRClients.h" @@ -888,9 +889,10 @@ void CGUIDialogPVRTimerSettings::InitializeChannelsList() // and for reminder rules another one representing any channel from any client. const CPVRClientMap clients = CServiceBroker::GetPVRManager().Clients()->GetCreatedClients(); if (clients.size() > 1) - m_channelEntries.insert({index++, ChannelDescriptor(PVR_CHANNEL_INVALID_UID, PVR_ANY_CLIENT_ID, - // Any channel from any client - g_localizeStrings.Get(854))}); + m_channelEntries.insert( + {index++, ChannelDescriptor(PVR_CHANNEL_INVALID_UID, PVR_CLIENT_INVALID_UID, + // Any channel from any client + g_localizeStrings.Get(854))}); for (const auto& client : clients) { @@ -977,7 +979,7 @@ void CGUIDialogPVRTimerSettings::ChannelsFiller(const SettingConstPtr& setting, for (const auto& channelEntry : pThis->m_channelEntries) { // Only include channels for the currently selected timer type or all channels if type is client-independent. - if (pThis->m_timerType->GetClientId() == PVR_ANY_CLIENT_ID || // client-independent + if (pThis->m_timerType->GetClientId() == PVR_CLIENT_INVALID_UID || // client-independent pThis->m_timerType->GetClientId() == channelEntry.second.clientId) { // Do not add "any channel" entry if not supported by selected timer type. @@ -987,7 +989,7 @@ void CGUIDialogPVRTimerSettings::ChannelsFiller(const SettingConstPtr& setting, // Do not add "any channel from any client" entry for reminder rules. if (channelEntry.second.channelUid == PVR_CHANNEL_INVALID_UID && - channelEntry.second.clientId == PVR_ANY_CLIENT_ID && + channelEntry.second.clientId == PVR_CLIENT_INVALID_UID && !pThis->m_timerType->IsReminder() && !pThis->m_timerType->IsTimerRule()) continue; diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp index da92dc8d2f74e..e993c4b34dc5f 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp @@ -14,6 +14,7 @@ #include "guilib/LocalizeStrings.h" #include "guilib/WindowIDs.h" #include "input/WindowTranslator.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/PVRManager.h" #include "pvr/addons/PVRClient.h" #include "pvr/addons/PVRClients.h" @@ -693,7 +694,7 @@ bool GetTimersSubDirectory(const CPVRTimersPath& path, for (const auto& timer : timers) { if ((timer->IsRadio() == bRadio) && timer->HasParent() && - (iClientId == PVR_ANY_CLIENT_ID || timer->ClientID() == iClientId) && + (iClientId == PVR_CLIENT_INVALID_UID || timer->ClientID() == iClientId) && (timer->ParentClientIndex() == iParentId) && (!bHideDisabled || !timer->IsDisabled())) { item = std::make_shared<CFileItem>(timer); diff --git a/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp index 033ab0102ba90..5074e38a5ae87 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp @@ -19,6 +19,7 @@ #include "input/actions/ActionIDs.h" #include "messaging/ApplicationMessenger.h" #include "messaging/helpers/DialogOKHelper.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/PVRItem.h" #include "pvr/PVRManager.h" #include "pvr/PVRPlaybackState.h" @@ -233,7 +234,7 @@ bool CPVRGUIActionsChannels::HideChannel(const CFileItem& item) const bool CPVRGUIActionsChannels::StartChannelScan() { - return StartChannelScan(PVR_INVALID_CLIENT_ID); + return StartChannelScan(PVR_CLIENT_INVALID_UID); } bool CPVRGUIActionsChannels::StartChannelScan(int clientId) @@ -246,7 +247,7 @@ bool CPVRGUIActionsChannels::StartChannelScan(int clientId) CServiceBroker::GetPVRManager().Clients()->GetClientsSupportingChannelScan(); m_bChannelScanRunning = true; - if (clientId != PVR_INVALID_CLIENT_ID) + if (clientId != PVR_CLIENT_INVALID_UID) { const auto it = std::find_if(possibleScanClients.cbegin(), possibleScanClients.cend(), diff --git a/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp b/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp index 42cc4e7e0b83f..fdaf93420ac34 100644 --- a/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp +++ b/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp @@ -10,7 +10,7 @@ #include "XBDateTime.h" #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" // PVR_CHANNEL_INVALID_UID -#include "pvr/addons/PVRClient.h" // PVR_ANY_CLIENT_ID +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/epg/EpgInfoTag.h" #include "pvr/timers/PVRTimerInfoTag.h" #include "utils/RegExp.h" @@ -92,7 +92,7 @@ bool CPVRTimerRuleMatcher::MatchChannel(const std::shared_ptr<const CPVREpgInfoT { if (m_timerRule->GetTimerType()->SupportsAnyChannel() && m_timerRule->ClientChannelUID() == PVR_CHANNEL_INVALID_UID && - (m_timerRule->ClientID() == PVR_ANY_CLIENT_ID || + (m_timerRule->ClientID() == PVR_CLIENT_INVALID_UID || m_timerRule->ClientID() == epgTag->ClientID())) return true; // matches any channel from any client / any channel from a certain client diff --git a/xbmc/pvr/timers/PVRTimers.cpp b/xbmc/pvr/timers/PVRTimers.cpp index 8339da75b7756..ccf719091aed8 100644 --- a/xbmc/pvr/timers/PVRTimers.cpp +++ b/xbmc/pvr/timers/PVRTimers.cpp @@ -9,6 +9,7 @@ #include "PVRTimers.h" #include "ServiceBroker.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/PVRDatabase.h" #include "pvr/PVREventLogJob.h" #include "pvr/PVRManager.h" @@ -1243,7 +1244,7 @@ std::shared_ptr<CPVRTimerInfoTag> CPVRTimers::GetTimerRule( const auto it = std::find_if(tagsEntry.second.cbegin(), tagsEntry.second.cend(), [iClientId, iParentClientIndex](const auto& timersEntry) { - return (timersEntry->ClientID() == PVR_ANY_CLIENT_ID || + return (timersEntry->ClientID() == PVR_CLIENT_INVALID_UID || timersEntry->ClientID() == iClientId) && timersEntry->ClientIndex() == iParentClientIndex; }); From 3f73165d83159b4a2cf3e0a95fc6831a92c5eee6 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:43:43 +0200 Subject: [PATCH 410/651] [PVR] Cleanup: Get rid of magic numbers for client id; use PVR_CLIENT_INVALID_UID. --- xbmc/pvr/PVRDatabase.h | 2 +- xbmc/pvr/PVRPlaybackState.cpp | 6 +++--- xbmc/pvr/PVRPlaybackState.h | 5 +++-- xbmc/pvr/addons/PVRClientUID.cpp | 7 ++++--- xbmc/pvr/addons/PVRClientUID.h | 2 +- xbmc/pvr/addons/PVRClients.cpp | 2 +- xbmc/pvr/addons/PVRClients.h | 2 +- xbmc/pvr/channels/PVRChannel.h | 4 +++- xbmc/pvr/channels/PVRChannelGroupMember.h | 5 +++-- xbmc/pvr/channels/PVRChannelsPath.cpp | 7 ++++--- xbmc/pvr/channels/PVRChannelsPath.h | 3 ++- xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp | 3 ++- xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h | 3 ++- xbmc/pvr/epg/Epg.cpp | 3 ++- xbmc/pvr/epg/EpgChannelData.h | 4 +++- xbmc/pvr/epg/EpgContainer.cpp | 2 +- xbmc/pvr/epg/EpgSearchFilter.cpp | 4 ++-- xbmc/pvr/epg/EpgSearchFilter.h | 3 ++- xbmc/pvr/recordings/PVRRecording.cpp | 2 +- xbmc/pvr/timers/PVRTimerInfoTag.cpp | 2 +- xbmc/pvr/timers/PVRTimerInfoTag.h | 2 +- xbmc/pvr/timers/PVRTimerType.cpp | 9 +++++---- xbmc/pvr/timers/PVRTimerType.h | 3 ++- xbmc/pvr/timers/PVRTimersPath.cpp | 3 ++- xbmc/pvr/timers/PVRTimersPath.h | 4 +++- 25 files changed, 55 insertions(+), 37 deletions(-) diff --git a/xbmc/pvr/PVRDatabase.h b/xbmc/pvr/PVRDatabase.h index 8784741801064..93bd10c4ed111 100644 --- a/xbmc/pvr/PVRDatabase.h +++ b/xbmc/pvr/PVRDatabase.h @@ -115,7 +115,7 @@ namespace PVR * @brief Get the numeric client ID for given addon ID and instance ID from the database. * @param addonID The addon ID. * @param instanceID The instance ID. - * @return The client ID on success, -1 otherwise. + * @return The client ID on success, PVR_CLIENT_INVALID_UID otherwise. */ int GetClientID(const std::string& addonID, unsigned int instanceID); diff --git a/xbmc/pvr/PVRPlaybackState.cpp b/xbmc/pvr/PVRPlaybackState.cpp index 4879a119a64d4..a14009c2242d8 100644 --- a/xbmc/pvr/PVRPlaybackState.cpp +++ b/xbmc/pvr/PVRPlaybackState.cpp @@ -69,7 +69,7 @@ void CPVRPlaybackState::ReInit() Clear(); - if (m_playingClientId != -1) + if (m_playingClientId != PVR_CLIENT_INVALID_UID) { if (m_playingChannelUniqueId != -1) { @@ -123,7 +123,7 @@ void CPVRPlaybackState::ClearData() m_strPlayingRecordingUniqueId.clear(); m_playingEpgTagChannelUniqueId = -1; m_playingEpgTagUniqueId = 0; - m_playingClientId = -1; + m_playingClientId = PVR_CLIENT_INVALID_UID; m_strPlayingClientName.clear(); } @@ -217,7 +217,7 @@ void CPVRPlaybackState::OnPlaybackStarted(const CFileItem& item) CLog::LogFC(LOGERROR, LOGPVR, "Channel item without channel group member!"); } - if (m_playingClientId != -1) + if (m_playingClientId != PVR_CLIENT_INVALID_UID) { const std::shared_ptr<const CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_playingClientId); diff --git a/xbmc/pvr/PVRPlaybackState.h b/xbmc/pvr/PVRPlaybackState.h index 63a721c124ad6..34384fe25eafe 100644 --- a/xbmc/pvr/PVRPlaybackState.h +++ b/xbmc/pvr/PVRPlaybackState.h @@ -8,6 +8,7 @@ #pragma once +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "threads/CriticalSection.h" #include "utils/ContentUtils.h" @@ -181,7 +182,7 @@ class CPVRPlaybackState /*! * @brief Get the ID of the playing client, if there is one. - * @return The ID or -1 if no client is playing. + * @return The ID or PVR_CLIENT_INVALID_UID if no client is playing. */ int GetPlayingClientID() const; @@ -289,7 +290,7 @@ class CPVRPlaybackState std::shared_ptr<CPVRChannelGroupMember> m_previousToLastPlayedChannelRadio; std::string m_strPlayingClientName; int m_playingGroupId = -1; - int m_playingClientId = -1; + int m_playingClientId = PVR_CLIENT_INVALID_UID; int m_playingChannelUniqueId = -1; std::string m_strPlayingRecordingUniqueId; int m_playingEpgTagChannelUniqueId = -1; diff --git a/xbmc/pvr/addons/PVRClientUID.cpp b/xbmc/pvr/addons/PVRClientUID.cpp index 72b5cbed026c3..f15ab873106a6 100644 --- a/xbmc/pvr/addons/PVRClientUID.cpp +++ b/xbmc/pvr/addons/PVRClientUID.cpp @@ -8,6 +8,7 @@ #include "PVRClientUID.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/PVRDatabase.h" #include "utils/log.h" @@ -40,14 +41,14 @@ int CPVRClientUID::GetUID() const if (!db.Open()) { CLog::LogF(LOGERROR, "Unable to open TV database!"); - return -1; + return PVR_CLIENT_INVALID_UID; } m_uid = db.GetClientID(m_addonID, m_instanceID); - if (m_uid == -1) + if (m_uid == PVR_CLIENT_INVALID_UID) { CLog::LogF(LOGERROR, "Unable to get client id from TV database!"); - return -1; + return PVR_CLIENT_INVALID_UID; } s_idMap.insert({{m_addonID, m_instanceID}, m_uid}); diff --git a/xbmc/pvr/addons/PVRClientUID.h b/xbmc/pvr/addons/PVRClientUID.h index d3acec2dcfc63..a3c5fd31fc471 100644 --- a/xbmc/pvr/addons/PVRClientUID.h +++ b/xbmc/pvr/addons/PVRClientUID.h @@ -26,7 +26,7 @@ class CPVRClientUID final /*! * @brief Return the numeric UID. - * @return The numeric UID. + * @return The numeric UID, or PVR_CLIENT_INVALID_UID on error. */ int GetUID() const; diff --git a/xbmc/pvr/addons/PVRClients.cpp b/xbmc/pvr/addons/PVRClients.cpp index ac0990545042d..1f188ac0452ce 100644 --- a/xbmc/pvr/addons/PVRClients.cpp +++ b/xbmc/pvr/addons/PVRClients.cpp @@ -394,7 +394,7 @@ int CPVRClients::GetFirstCreatedClientID() const std::unique_lock<CCriticalSection> lock(m_critSection); const auto it = std::find_if(m_clientMap.cbegin(), m_clientMap.cend(), [](const auto& client) { return client.second->ReadyToUse(); }); - return it != m_clientMap.cend() ? (*it).second->GetID() : -1; + return it != m_clientMap.cend() ? (*it).second->GetID() : PVR_CLIENT_INVALID_UID; } PVR_ERROR CPVRClients::GetCallableClients(CPVRClientMap& clientsReady, diff --git a/xbmc/pvr/addons/PVRClients.h b/xbmc/pvr/addons/PVRClients.h index edc720ffc5b61..a7724b9640df2 100644 --- a/xbmc/pvr/addons/PVRClients.h +++ b/xbmc/pvr/addons/PVRClients.h @@ -148,7 +148,7 @@ struct SBackend /*! * @brief Get the ID of the first created client. - * @return the ID or -1 if no clients are created; + * @return the ID or PVR_CLIENT_INVALID_UID if no clients are created; */ int GetFirstCreatedClientID() const; diff --git a/xbmc/pvr/channels/PVRChannel.h b/xbmc/pvr/channels/PVRChannel.h index defe9ec9a8d74..119b47c4d80a8 100644 --- a/xbmc/pvr/channels/PVRChannel.h +++ b/xbmc/pvr/channels/PVRChannel.h @@ -12,6 +12,7 @@ #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h" #include "pvr/PVRCachedImage.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/channels/PVRChannelNumber.h" #include "threads/CriticalSection.h" #include "utils/ISerializable.h" @@ -544,7 +545,8 @@ class CPVRChannel : public ISerializable, public ISortable */ //@{ int m_iUniqueId = -1; /*!< the unique identifier for this channel */ - int m_iClientId = -1; /*!< the identifier of the client that serves this channel */ + int m_iClientId = + PVR_CLIENT_INVALID_UID; /*!< the identifier of the client that serves this channel */ CPVRChannelNumber m_clientChannelNumber; /*!< the channel number on the client */ std::string m_strClientChannelName; /*!< the name of this channel on the client */ std::string diff --git a/xbmc/pvr/channels/PVRChannelGroupMember.h b/xbmc/pvr/channels/PVRChannelGroupMember.h index d6ddd666942cd..9639d7072c8fc 100644 --- a/xbmc/pvr/channels/PVRChannelGroupMember.h +++ b/xbmc/pvr/channels/PVRChannelGroupMember.h @@ -8,6 +8,7 @@ #pragma once +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/channels/PVRChannelNumber.h" #include "utils/ISerializable.h" #include "utils/ISortable.h" @@ -79,8 +80,8 @@ class CPVRChannelGroupMember : public ISerializable, public ISortable private: int m_iGroupID = -1; - int m_iGroupClientID = -1; - int m_iChannelClientID = -1; + int m_iGroupClientID = PVR_CLIENT_INVALID_UID; + int m_iChannelClientID = PVR_CLIENT_INVALID_UID; int m_iChannelUID = -1; int m_iChannelDatabaseID = -1; bool m_bIsRadio = false; diff --git a/xbmc/pvr/channels/PVRChannelsPath.cpp b/xbmc/pvr/channels/PVRChannelsPath.cpp index 2dfca994a25ee..681895bda0eb4 100644 --- a/xbmc/pvr/channels/PVRChannelsPath.cpp +++ b/xbmc/pvr/channels/PVRChannelsPath.cpp @@ -66,7 +66,7 @@ CPVRChannelsPath::CPVRChannelsPath(const std::string& strPath) { m_kind = Kind::GROUP; // pvr://channels/(tv|radio)/<all-channels-wildcard>@-1 m_groupName = segment; - m_groupClientID = -1; // local + m_groupClientID = PVR_CLIENT_INVALID_UID; // local break; } @@ -79,7 +79,7 @@ CPVRChannelsPath::CPVRChannelsPath(const std::string& strPath) if (groupClientID.find_first_not_of("-0123456789") == std::string::npos) { m_groupClientID = std::atoi(groupClientID.c_str()); - if (m_groupClientID >= -1) + if (m_groupClientID >= PVR_CLIENT_INVALID_UID) { m_kind = Kind::GROUP; // pvr://channels/(tv|radio)/<groupname>@<clientid> break; @@ -195,7 +195,8 @@ CPVRChannelsPath::CPVRChannelsPath(bool bRadio, int iChannelUID) : m_bRadio(bRadio) { - if (!strGroupName.empty() && iGroupClientID >= -1 && !strAddonID.empty() && iChannelUID >= 0) + if (!strGroupName.empty() && iGroupClientID >= PVR_CLIENT_INVALID_UID && !strAddonID.empty() && + iChannelUID >= 0) { m_kind = Kind::CHANNEL; m_groupName = strGroupName; diff --git a/xbmc/pvr/channels/PVRChannelsPath.h b/xbmc/pvr/channels/PVRChannelsPath.h index d5545148a83b2..94c624a6f3638 100644 --- a/xbmc/pvr/channels/PVRChannelsPath.h +++ b/xbmc/pvr/channels/PVRChannelsPath.h @@ -9,6 +9,7 @@ #pragma once #include "addons/IAddon.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID class CDateTime; @@ -71,7 +72,7 @@ namespace PVR bool m_bRadio = false;; std::string m_path; std::string m_groupName; - int m_groupClientID{-1}; + int m_groupClientID{PVR_CLIENT_INVALID_UID}; std::string m_addonID; ADDON::AddonInstanceId m_instanceID{ADDON::ADDON_SINGLETON_INSTANCE_ID}; int m_iChannelUID = -1; diff --git a/xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp b/xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp index 5af1b8d789d95..90faa68af2730 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp @@ -297,7 +297,8 @@ void CGUIDialogPVRGuideSearch::UpdateSearchFilter() m_searchFilter->SetMaximumDuration(GetSpinValue(CONTROL_SPIN_MAX_DURATION)); auto it = m_channelsMap.find(GetSpinValue(CONTROL_SPIN_CHANNELS)); - m_searchFilter->SetClientID(it == m_channelsMap.end() ? -1 : (*it).second->ChannelClientID()); + m_searchFilter->SetClientID(it == m_channelsMap.end() ? PVR_CLIENT_INVALID_UID + : (*it).second->ChannelClientID()); m_searchFilter->SetChannelUID(it == m_channelsMap.end() ? -1 : (*it).second->ChannelUID()); m_searchFilter->SetChannelGroupID(GetSpinValue(CONTROL_SPIN_GROUPS)); diff --git a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h index d3e9e3eadf344..06c5b6bce95dd 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h +++ b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h @@ -10,6 +10,7 @@ #include "XBDateTime.h" #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" // PVR_CHANNEL_INVALID_UID +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "settings/SettingConditions.h" #include "settings/dialogs/GUIDialogSettingsManualBase.h" #include "settings/lib/SettingDependency.h" @@ -148,7 +149,7 @@ class CGUIDialogPVRTimerSettings : public CGUIDialogSettingsManualBase std::string description; ChannelDescriptor(int _channelUid = PVR_CHANNEL_INVALID_UID, - int _clientId = -1, + int _clientId = PVR_CLIENT_INVALID_UID, const std::string& _description = "") : channelUid(_channelUid), clientId(_clientId), description(_description) { diff --git a/xbmc/pvr/epg/Epg.cpp b/xbmc/pvr/epg/Epg.cpp index 5b895ad042aec..d1c39edcd3b24 100644 --- a/xbmc/pvr/epg/Epg.cpp +++ b/xbmc/pvr/epg/Epg.cpp @@ -533,7 +533,8 @@ bool CPVREpg::IsValid() const { std::unique_lock<CCriticalSection> lock(m_critSection); if (ScraperName() == "client") - return m_channelData->ClientId() != -1 && m_channelData->UniqueClientChannelId() != PVR_CHANNEL_INVALID_UID; + return m_channelData->ClientId() != PVR_CLIENT_INVALID_UID && + m_channelData->UniqueClientChannelId() != PVR_CHANNEL_INVALID_UID; return true; } diff --git a/xbmc/pvr/epg/EpgChannelData.h b/xbmc/pvr/epg/EpgChannelData.h index a2a63ec9edfb3..237bc2ff5548b 100644 --- a/xbmc/pvr/epg/EpgChannelData.h +++ b/xbmc/pvr/epg/EpgChannelData.h @@ -8,6 +8,8 @@ #pragma once +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID + #include <ctime> #include <string> @@ -46,7 +48,7 @@ class CPVREpgChannelData private: const bool m_bIsRadio = false; - const int m_iClientId = -1; + const int m_iClientId = PVR_CLIENT_INVALID_UID; const int m_iUniqueClientChannelId = -1; bool m_bIsHidden = false; diff --git a/xbmc/pvr/epg/EpgContainer.cpp b/xbmc/pvr/epg/EpgContainer.cpp index 72521d1593e22..d3c7d42c192d6 100644 --- a/xbmc/pvr/epg/EpgContainer.cpp +++ b/xbmc/pvr/epg/EpgContainer.cpp @@ -40,7 +40,7 @@ namespace PVR class CEpgUpdateRequest { public: - CEpgUpdateRequest() : CEpgUpdateRequest(-1, PVR_CHANNEL_INVALID_UID) {} + CEpgUpdateRequest() : CEpgUpdateRequest(PVR_CLIENT_INVALID_UID, PVR_CHANNEL_INVALID_UID) {} CEpgUpdateRequest(int iClientID, int iUniqueChannelID) : m_iClientID(iClientID), m_iUniqueChannelID(iUniqueChannelID) {} void Deliver(const std::shared_ptr<CPVREpg>& epg); diff --git a/xbmc/pvr/epg/EpgSearchFilter.cpp b/xbmc/pvr/epg/EpgSearchFilter.cpp index 87056db5f216a..f262c07725a20 100644 --- a/xbmc/pvr/epg/EpgSearchFilter.cpp +++ b/xbmc/pvr/epg/EpgSearchFilter.cpp @@ -45,7 +45,7 @@ void CPVREpgSearchFilter::Reset() m_bRemoveDuplicates = false; /* pvr specific filters */ - m_iClientID = -1; + m_iClientID = PVR_CLIENT_INVALID_UID; m_iChannelGroupID = -1; m_iChannelUID = -1; m_bFreeToAirOnly = false; @@ -363,7 +363,7 @@ void CPVREpgSearchFilter::RemoveDuplicates(std::vector<std::shared_ptr<CPVREpgIn bool CPVREpgSearchFilter::MatchChannel(const std::shared_ptr<const CPVREpgInfoTag>& tag) const { return tag && (tag->IsRadio() == m_bIsRadio) && - (m_iClientID == -1 || tag->ClientID() == m_iClientID) && + (m_iClientID == PVR_CLIENT_INVALID_UID || tag->ClientID() == m_iClientID) && (m_iChannelUID == -1 || tag->UniqueChannelID() == m_iChannelUID) && CServiceBroker::GetPVRManager().Clients()->IsCreatedClient(tag->ClientID()); } diff --git a/xbmc/pvr/epg/EpgSearchFilter.h b/xbmc/pvr/epg/EpgSearchFilter.h index 89473c10eee54..cf937b426a3d1 100644 --- a/xbmc/pvr/epg/EpgSearchFilter.h +++ b/xbmc/pvr/epg/EpgSearchFilter.h @@ -9,6 +9,7 @@ #pragma once #include "XBDateTime.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/epg/EpgSearchData.h" #include <memory> @@ -158,7 +159,7 @@ namespace PVR // PVR specific filters bool m_bIsRadio; /*!< True to filter radio channels only, false to tv only */ - int m_iClientID = -1; /*!< The client id */ + int m_iClientID = PVR_CLIENT_INVALID_UID; /*!< The client id */ int m_iChannelGroupID{-1}; /*! The channel group id */ int m_iChannelUID = -1; /*!< The channel uid */ bool m_bFreeToAirOnly; /*!< Include free to air channels only */ diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp index f9269bdd841e9..6d99c6ad105c9 100644 --- a/xbmc/pvr/recordings/PVRRecording.cpp +++ b/xbmc/pvr/recordings/PVRRecording.cpp @@ -252,7 +252,7 @@ void CPVRRecording::ToSortable(SortItem& sortable, Field field) const void CPVRRecording::Reset() { m_strRecordingId.clear(); - m_iClientId = -1; + m_iClientId = PVR_CLIENT_INVALID_UID; m_strChannelName.clear(); m_strDirectory.clear(); m_iPriority = -1; diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.cpp b/xbmc/pvr/timers/PVRTimerInfoTag.cpp index 100cfa6674901..58e7403a8663d 100644 --- a/xbmc/pvr/timers/PVRTimerInfoTag.cpp +++ b/xbmc/pvr/timers/PVRTimerInfoTag.cpp @@ -529,7 +529,7 @@ std::string CPVRTimerInfoTag::GetWeekdaysString() const bool CPVRTimerInfoTag::IsOwnedByClient() const { - return m_timerType->GetClientId() > -1; + return m_timerType->GetClientId() > PVR_CLIENT_INVALID_UID; } bool CPVRTimerInfoTag::AddToClient() const diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.h b/xbmc/pvr/timers/PVRTimerInfoTag.h index 4098c067c7665..44826ba6fdcb0 100644 --- a/xbmc/pvr/timers/PVRTimerInfoTag.h +++ b/xbmc/pvr/timers/PVRTimerInfoTag.h @@ -227,7 +227,7 @@ class CPVRTimerInfoTag final : public ISerializable /*! * @brief The ID of the client for this timer. - * @return The client ID or -1 if this is a local timer. + * @return The client ID or PVR_CLIENT_INVALID_UID if this is a local timer. */ int ClientID() const { return m_iClientId; } diff --git a/xbmc/pvr/timers/PVRTimerType.cpp b/xbmc/pvr/timers/PVRTimerType.cpp index 9a68e49305cab..a9a4bc3f30053 100644 --- a/xbmc/pvr/timers/PVRTimerType.cpp +++ b/xbmc/pvr/timers/PVRTimerType.cpp @@ -127,10 +127,10 @@ std::shared_ptr<CPVRTimerType> CPVRTimerType::CreateFromIds(unsigned int iTypeId if (it != types.cend()) return (*it); - if (iClientId != -1) + if (iClientId != PVR_CLIENT_INVALID_UID) { // fallback. try to obtain local timer type. - std::shared_ptr<CPVRTimerType> type = CreateFromIds(iTypeId, -1); + std::shared_ptr<CPVRTimerType> type = CreateFromIds(iTypeId, PVR_CLIENT_INVALID_UID); if (type) return type; } @@ -153,10 +153,11 @@ std::shared_ptr<CPVRTimerType> CPVRTimerType::CreateFromAttributes(uint64_t iMus if (it != types.cend()) return (*it); - if (iClientId != -1) + if (iClientId != PVR_CLIENT_INVALID_UID) { // fallback. try to obtain local timer type. - std::shared_ptr<CPVRTimerType> type = CreateFromAttributes(iMustHaveAttr, iMustNotHaveAttr, -1); + std::shared_ptr<CPVRTimerType> type = + CreateFromAttributes(iMustHaveAttr, iMustNotHaveAttr, PVR_CLIENT_INVALID_UID); if (type) return type; } diff --git a/xbmc/pvr/timers/PVRTimerType.h b/xbmc/pvr/timers/PVRTimerType.h index a17cf5f402938..a59925f235a9d 100644 --- a/xbmc/pvr/timers/PVRTimerType.h +++ b/xbmc/pvr/timers/PVRTimerType.h @@ -9,6 +9,7 @@ #pragma once #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include <memory> #include <string> @@ -400,7 +401,7 @@ namespace PVR void InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& type); void InitRecordingGroupValues(const PVR_TIMER_TYPE& type); - int m_iClientId = -1; + int m_iClientId = PVR_CLIENT_INVALID_UID; unsigned int m_iTypeId; uint64_t m_iAttributes; std::string m_strDescription; diff --git a/xbmc/pvr/timers/PVRTimersPath.cpp b/xbmc/pvr/timers/PVRTimersPath.cpp index ea2265ca5654f..53602c1ec1975 100644 --- a/xbmc/pvr/timers/PVRTimersPath.cpp +++ b/xbmc/pvr/timers/PVRTimersPath.cpp @@ -8,6 +8,7 @@ #include "PVRTimersPath.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "utils/StringUtils.h" #include "utils/URIUtils.h" @@ -69,7 +70,7 @@ bool CPVRTimersPath::Init(const std::string& strPath) if (!m_bValid || m_bRoot) { - m_iClientId = -1; + m_iClientId = PVR_CLIENT_INVALID_UID; m_iParentId = 0; } else diff --git a/xbmc/pvr/timers/PVRTimersPath.h b/xbmc/pvr/timers/PVRTimersPath.h index 01fd5f092f577..18128fab96c89 100644 --- a/xbmc/pvr/timers/PVRTimersPath.h +++ b/xbmc/pvr/timers/PVRTimersPath.h @@ -8,6 +8,8 @@ #pragma once +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID + #include <string> namespace PVR @@ -44,7 +46,7 @@ class CPVRTimersPath bool m_bRoot = false; bool m_bRadio = false; bool m_bTimerRules = false; - int m_iClientId = -1; + int m_iClientId = PVR_CLIENT_INVALID_UID; int m_iParentId = 0; }; } // namespace PVR From ec724fbc24345b6465fe665ecde33a158ca8b7f4 Mon Sep 17 00:00:00 2001 From: phunkyfish <phunkyfish@gmail.com> Date: Tue, 27 Aug 2024 08:37:34 +0100 Subject: [PATCH 411/651] [pvr] Add flag to indicate if the callback to GetChannelStreamProperties() resulted from PVR_STREAM_PROPERTY_EPGPLAYBACKASLIVE being set from GetEPGTagStreamProperties(). Also, remove optional arguments from StartPlayback call to make the code more readable --- .../kodi/c-api/addon-instance/pvr/pvr_general.h | 1 + xbmc/pvr/PVRPlaybackState.cpp | 11 ++++++----- xbmc/pvr/PVRPlaybackState.h | 5 ++--- xbmc/pvr/addons/PVRClient.cpp | 5 +++-- xbmc/pvr/addons/PVRClient.h | 2 ++ xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp | 11 ++++++++--- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h index 326c6f02cb341..420a95c1b8b31 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h @@ -215,6 +215,7 @@ extern "C" /// /// // On PVR instance of addon /// PVR_ERROR CMyPVRInstance::GetChannelStreamProperties(const kodi::addon::PVRChannel& channel, + /// PVR_SOURCE source, /// std::vector<PVRStreamProperty>& properties) /// { /// ... diff --git a/xbmc/pvr/PVRPlaybackState.cpp b/xbmc/pvr/PVRPlaybackState.cpp index a14009c2242d8..6bce1aa24c8c4 100644 --- a/xbmc/pvr/PVRPlaybackState.cpp +++ b/xbmc/pvr/PVRPlaybackState.cpp @@ -341,14 +341,15 @@ bool CPVRPlaybackState::OnPlaybackEnded(const CFileItem& item) std::unique_ptr<CFileItem> nextToPlay{GetNextAutoplayItem(item)}; if (nextToPlay) - StartPlayback(nextToPlay.release()); + StartPlayback(nextToPlay.release(), ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM, + PVR_SOURCE::DEFAULT); return OnPlaybackStopped(item); } -void CPVRPlaybackState::StartPlayback( - CFileItem* item, - ContentUtils::PlayMode mode /* = ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM */) const +void CPVRPlaybackState::StartPlayback(CFileItem* item, + ContentUtils::PlayMode mode, + PVR_SOURCE source) const { // Obtain dynamic playback url and properties from the respective pvr client const std::shared_ptr<const CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(*item); @@ -358,7 +359,7 @@ void CPVRPlaybackState::StartPlayback( if (item->IsPVRChannel()) { - client->GetChannelStreamProperties(item->GetPVRChannelInfoTag(), props); + client->GetChannelStreamProperties(item->GetPVRChannelInfoTag(), source, props); } else if (item->IsPVRRecording()) { diff --git a/xbmc/pvr/PVRPlaybackState.h b/xbmc/pvr/PVRPlaybackState.h index 34384fe25eafe..098ff8a4499b2 100644 --- a/xbmc/pvr/PVRPlaybackState.h +++ b/xbmc/pvr/PVRPlaybackState.h @@ -8,6 +8,7 @@ #pragma once +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h" #include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "threads/CriticalSection.h" #include "utils/ContentUtils.h" @@ -75,9 +76,7 @@ class CPVRPlaybackState * @param item containing a channel, a recording or an epg tag. * @param mode playback mode. */ - void StartPlayback( - CFileItem* item, - ContentUtils::PlayMode mode = ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM) const; + void StartPlayback(CFileItem* item, ContentUtils::PlayMode mode, PVR_SOURCE source) const; /*! * @brief Check if a TV channel, radio channel or recording is playing. diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 172b2efea7f5f..e23ba188fa830 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -1554,11 +1554,12 @@ PVR_ERROR CPVRClient::GetDescrambleInfo(int channelUid, CPVRDescrambleInfo& desc } PVR_ERROR CPVRClient::GetChannelStreamProperties(const std::shared_ptr<const CPVRChannel>& channel, + PVR_SOURCE source, CPVRStreamProperties& props) const { return DoAddonCall( __func__, - [this, &channel, &props](const AddonInstance* addon) + [this, &channel, source, &props](const AddonInstance* addon) { if (!CanPlayChannel(channel)) return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon @@ -1568,7 +1569,7 @@ PVR_ERROR CPVRClient::GetChannelStreamProperties(const std::shared_ptr<const CPV PVR_NAMED_VALUE** property_array{nullptr}; unsigned int size{0}; const PVR_ERROR error{addon->toAddon->GetChannelStreamProperties( - addon, &addonChannel, PVR_SOURCE::DEFAULT, &property_array, &size)}; + addon, &addonChannel, source, &property_array, &size)}; if (error == PVR_ERROR_NO_ERROR) WriteStreamProperties(property_array, size, props); diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h index 2393b6f1fb3f8..226ac719cfef6 100644 --- a/xbmc/pvr/addons/PVRClient.h +++ b/xbmc/pvr/addons/PVRClient.h @@ -589,10 +589,12 @@ class CPVRClient : public ADDON::IAddonInstanceHandler /*! * @brief Fill the given container with the properties required for playback of the given channel. Values are obtained from the PVR backend. * @param channel The channel. + * @param source PVR_SOURCE_EPG_AS_LIVE if this call resulted from PVR_STREAM_PROPERTY_EPGPLAYBACKASLIVE being set from GetEPGTagStreamProperties(), PVR_SOURCE_SWITCH_LIVE otherwise. * @param props The container to be filled with the stream properties. * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. */ PVR_ERROR GetChannelStreamProperties(const std::shared_ptr<const CPVRChannel>& channel, + PVR_SOURCE source, CPVRStreamProperties& props) const; /*! diff --git a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp index 954b46eb5ed55..e81bd0a5b7b5a 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp @@ -164,7 +164,8 @@ bool CPVRGUIActionsPlayback::PlayRecording(const CFileItem& item, bool bCheckRes { CFileItem* itemToPlay = new CFileItem(recording); itemToPlay->SetStartOffset(item.GetStartOffset()); - CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(itemToPlay); + CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback( + itemToPlay, ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM, PVR_SOURCE::DEFAULT); } CheckAndSwitchToFullscreen(true); } @@ -216,6 +217,7 @@ bool CPVRGUIActionsPlayback::PlayEpgTag( client->GetEpgTagStreamProperties(epgTag, props); CFileItem* itemToPlay = nullptr; + PVR_SOURCE source = DEFAULT; if (props.EPGPlaybackAsLive()) { const std::shared_ptr<CPVRChannelGroupMember> groupMember = @@ -223,6 +225,7 @@ bool CPVRGUIActionsPlayback::PlayEpgTag( if (!groupMember) return false; + source = PVR_SOURCE_EPG_AS_LIVE; itemToPlay = new CFileItem(groupMember); } else @@ -230,7 +233,7 @@ bool CPVRGUIActionsPlayback::PlayEpgTag( itemToPlay = new CFileItem(epgTag); } - CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(itemToPlay, mode); + CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(itemToPlay, mode, source); CheckAndSwitchToFullscreen(true); return true; } @@ -313,7 +316,9 @@ bool CPVRGUIActionsPlayback::SwitchToChannel(const CFileItem& item, bool bCheckR if (!groupMember) return false; - CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(new CFileItem(groupMember)); + CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback( + new CFileItem(groupMember), ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM, + PVR_SOURCE::DEFAULT); CheckAndSwitchToFullscreen(bFullscreen); return true; } From d31ae8a0e605d4d2300bc3551d4ea068040bb354 Mon Sep 17 00:00:00 2001 From: phunkyfish <phunkyfish@gmail.com> Date: Fri, 2 Feb 2024 11:31:18 +0000 Subject: [PATCH 412/651] [pvr] add StreamClosed() in PVR Client for streams that do not use both PVRCapabilities::SetHandlesInputStream() and PVRCapabilities::SetHandlesDemuxing() --- xbmc/pvr/PVRPlaybackState.cpp | 13 +++++++++++++ xbmc/pvr/addons/PVRClient.cpp | 6 ++++++ xbmc/pvr/addons/PVRClient.h | 8 +++++++- xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp | 5 +++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/xbmc/pvr/PVRPlaybackState.cpp b/xbmc/pvr/PVRPlaybackState.cpp index 6bce1aa24c8c4..5e5168f8ef057 100644 --- a/xbmc/pvr/PVRPlaybackState.cpp +++ b/xbmc/pvr/PVRPlaybackState.cpp @@ -359,6 +359,14 @@ void CPVRPlaybackState::StartPlayback(CFileItem* item, if (item->IsPVRChannel()) { + if (source == PVR_SOURCE::DEFAULT) + { + PVR_ERROR retVal = client->StreamClosed(); + if (retVal != PVR_ERROR_NO_ERROR) + CLog::LogFC(LOGERROR, LOGPVR, "Client error on call to StreamClosed(): {}", + CPVRClient::ToString(retVal)); + } + client->GetChannelStreamProperties(item->GetPVRChannelInfoTag(), source, props); } else if (item->IsPVRRecording()) @@ -367,6 +375,11 @@ void CPVRPlaybackState::StartPlayback(CFileItem* item, } else if (item->IsEPG()) { + PVR_ERROR retVal = client->StreamClosed(); + if (retVal != PVR_ERROR_NO_ERROR) + CLog::LogFC(LOGERROR, LOGPVR, "Client error on call to StreamClosed(): {}", + CPVRClient::ToString(retVal)); + client->GetEpgTagStreamProperties(item->GetEPGInfoTag(), props); if (mode == ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index e23ba188fa830..3170e5e44c492 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -1608,6 +1608,12 @@ PVR_ERROR CPVRClient::GetStreamProperties(PVR_STREAM_PROPERTIES* props) const { return addon->toAddon->GetStreamProperties(addon, props); }); } +PVR_ERROR CPVRClient::StreamClosed() const +{ + return DoAddonCall(__func__, [](const AddonInstance* addon) + { return addon->toAddon->StreamClosed(addon); }); +} + PVR_ERROR CPVRClient::DemuxReset() { return DoAddonCall( diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h index 226ac719cfef6..2d111ebaefb1d 100644 --- a/xbmc/pvr/addons/PVRClient.h +++ b/xbmc/pvr/addons/PVRClient.h @@ -149,6 +149,12 @@ class CPVRClient : public ADDON::IAddonInstanceHandler */ PVR_ERROR GetStreamProperties(PVR_STREAM_PROPERTIES* pProperties) const; + /*! + * @brief A stream was closed or has ended + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR StreamClosed() const; + /*! * @return The name reported by the backend. */ @@ -589,7 +595,7 @@ class CPVRClient : public ADDON::IAddonInstanceHandler /*! * @brief Fill the given container with the properties required for playback of the given channel. Values are obtained from the PVR backend. * @param channel The channel. - * @param source PVR_SOURCE_EPG_AS_LIVE if this call resulted from PVR_STREAM_PROPERTY_EPGPLAYBACKASLIVE being set from GetEPGTagStreamProperties(), PVR_SOURCE_SWITCH_LIVE otherwise. + * @param source PVR_SOURCE_EPG_AS_LIVE if this call resulted from PVR_STREAM_PROPERTY_EPGPLAYBACKASLIVE being set from GetEPGTagStreamProperties(), DEFAULT otherwise. * @param props The container to be filled with the stream properties. * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. */ diff --git a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp index e81bd0a5b7b5a..a3c294bc073f9 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp @@ -214,6 +214,11 @@ bool CPVRGUIActionsPlayback::PlayEpgTag( return false; CPVRStreamProperties props; + PVR_ERROR retVal = client->StreamClosed(); + if (retVal != PVR_ERROR_NO_ERROR) + CLog::LogFC(LOGERROR, LOGPVR, "Client error on call to StreamClosed(): {}", + CPVRClient::ToString(retVal)); + client->GetEpgTagStreamProperties(epgTag, props); CFileItem* itemToPlay = nullptr; From 01e254c0b66d319bcba9d6d387df55c83130c560 Mon Sep 17 00:00:00 2001 From: phunkyfish <phunkyfish@gmail.com> Date: Tue, 20 Feb 2024 13:35:17 +0000 Subject: [PATCH 413/651] [pvr] Allow playing back live stream as epg tag from now --- xbmc/pvr/PVRPlaybackState.cpp | 10 ++++++++++ xbmc/pvr/PVRStreamProperties.cpp | 8 ++++++++ xbmc/pvr/PVRStreamProperties.h | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/xbmc/pvr/PVRPlaybackState.cpp b/xbmc/pvr/PVRPlaybackState.cpp index 5e5168f8ef057..4ab53cec7e369 100644 --- a/xbmc/pvr/PVRPlaybackState.cpp +++ b/xbmc/pvr/PVRPlaybackState.cpp @@ -368,6 +368,16 @@ void CPVRPlaybackState::StartPlayback(CFileItem* item, } client->GetChannelStreamProperties(item->GetPVRChannelInfoTag(), source, props); + + if (props.LivePlaybackAsEPG()) + { + const std::shared_ptr<CPVREpgInfoTag> epgTag = item->GetPVRChannelInfoTag()->GetEPGNow(); + if (epgTag) + { + delete item; + item = new CFileItem(epgTag); + } + } } else if (item->IsPVRRecording()) { diff --git a/xbmc/pvr/PVRStreamProperties.cpp b/xbmc/pvr/PVRStreamProperties.cpp index 272b33aa2506e..c5bcaa10eeb44 100644 --- a/xbmc/pvr/PVRStreamProperties.cpp +++ b/xbmc/pvr/PVRStreamProperties.cpp @@ -38,3 +38,11 @@ bool CPVRStreamProperties::EPGPlaybackAsLive() const }); return it != cend() ? StringUtils::EqualsNoCase((*it).second, "true") : false; } + +bool CPVRStreamProperties::LivePlaybackAsEPG() const +{ + const auto it = std::find_if(cbegin(), cend(), + [](const auto& prop) + { return prop.first == PVR_STREAM_PROPERTY_LIVEPLAYBACKASEPG; }); + return it != cend() ? StringUtils::EqualsNoCase((*it).second, "true") : false; +} diff --git a/xbmc/pvr/PVRStreamProperties.h b/xbmc/pvr/PVRStreamProperties.h index 6690660601798..34430db2317bb 100644 --- a/xbmc/pvr/PVRStreamProperties.h +++ b/xbmc/pvr/PVRStreamProperties.h @@ -38,6 +38,12 @@ class CPVRStreamProperties : public std::vector<std::pair<std::string, std::stri * @return true if it should be played back as live, false otherwise. */ bool EPGPlaybackAsLive() const; + + /*! + * @brief If props are from an channel indicates if playback should be as a video playback would be + * @return true if it should be played back as live, false otherwise. + */ + bool LivePlaybackAsEPG() const; }; } // namespace PVR From 6d2b8a49063b1aabd42ff36672ebd2afe22e8f59 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:24:23 +0200 Subject: [PATCH 414/651] [PVR] CPVRClient: CAddon* classes: Zero-init base struct's members in ctor. --- xbmc/pvr/addons/PVRClient.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 172b2efea7f5f..c91cf693b0b8b 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -69,6 +69,10 @@ class CAddonChannelGroup : public PVR_CHANNEL_GROUP public: explicit CAddonChannelGroup(const CPVRChannelGroup& group) : m_groupName(group.ClientGroupName()) { + // zero-init base struct members + PVR_CHANNEL_GROUP* base = static_cast<PVR_CHANNEL_GROUP*>(this); + *base = {}; + bIsRadio = group.IsRadio(); strGroupName = m_groupName.c_str(); iPosition = group.GetClientPosition(); @@ -87,6 +91,10 @@ class CAddonChannel : public PVR_CHANNEL m_mimeType(channel.MimeType()), m_iconPath(channel.ClientIconPath()) { + // zero-init base struct members + PVR_CHANNEL* base = static_cast<PVR_CHANNEL*>(this); + *base = {}; + iUniqueId = channel.UniqueID(); iChannelNumber = channel.ClientChannelNumber().GetChannelNumber(); iSubChannelNumber = channel.ClientChannelNumber().GetSubChannelNumber(); @@ -128,6 +136,10 @@ class CAddonRecording : public PVR_RECORDING m_parentalRatingIcon(""), //! @todo m_parentalRatingSource("") //! @todo { + // zero-init base struct members + PVR_RECORDING* base = static_cast<PVR_RECORDING*>(this); + *base = {}; + time_t recTime; recording.RecordingTimeAsUTC().GetAsTime(recTime); @@ -201,6 +213,10 @@ class CAddonTimer : public PVR_TIMER m_summary(timer.Summary()), m_seriesLink(timer.SeriesLink()) { + // zero-init base struct members + PVR_TIMER* base = static_cast<PVR_TIMER*>(this); + *base = {}; + time_t start; timer.StartAsUTC().GetAsTime(start); time_t end; @@ -270,6 +286,10 @@ class CAddonEpgTag : public EPG_TAG m_parentalRatingIcon(""), //! @todo m_parentalRatingSource("") //! @todo { + // zero-init base struct members + EPG_TAG* base = static_cast<EPG_TAG*>(this); + *base = {}; + time_t t; tag.StartAsUTC().GetAsTime(t); startTime = t; From 38242095aeeabebfb68f8d61a14bd47754afdd2e Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:55:11 +0200 Subject: [PATCH 415/651] [PVR] clang-format pvr/timers/PVRTimerType.(h|cpp). --- xbmc/pvr/timers/PVRTimerType.cpp | 138 +++-- xbmc/pvr/timers/PVRTimerType.h | 838 ++++++++++++++++--------------- 2 files changed, 502 insertions(+), 474 deletions(-) diff --git a/xbmc/pvr/timers/PVRTimerType.cpp b/xbmc/pvr/timers/PVRTimerType.cpp index a9a4bc3f30053..af7d95a3c0ce0 100644 --- a/xbmc/pvr/timers/PVRTimerType.cpp +++ b/xbmc/pvr/timers/PVRTimerType.cpp @@ -34,71 +34,52 @@ const std::vector<std::shared_ptr<CPVRTimerType>> CPVRTimerType::GetAllTypes() int iTypeId = PVR_TIMER_TYPE_NONE; // one time time-based reminder - allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId, - PVR_TIMER_TYPE_IS_MANUAL | - PVR_TIMER_TYPE_IS_REMINDER | - PVR_TIMER_TYPE_SUPPORTS_CHANNELS | - PVR_TIMER_TYPE_SUPPORTS_START_TIME | - PVR_TIMER_TYPE_SUPPORTS_END_TIME)); + allTypes.emplace_back(std::make_shared<CPVRTimerType>( + ++iTypeId, PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_IS_REMINDER | + PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | + PVR_TIMER_TYPE_SUPPORTS_END_TIME)); // one time epg-based reminder - allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId, - PVR_TIMER_TYPE_IS_REMINDER | - PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE | - PVR_TIMER_TYPE_SUPPORTS_CHANNELS | - PVR_TIMER_TYPE_SUPPORTS_START_TIME | - PVR_TIMER_TYPE_SUPPORTS_START_MARGIN)); + allTypes.emplace_back(std::make_shared<CPVRTimerType>( + ++iTypeId, PVR_TIMER_TYPE_IS_REMINDER | PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE | + PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | + PVR_TIMER_TYPE_SUPPORTS_START_MARGIN)); // time-based reminder rule - allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId, - PVR_TIMER_TYPE_IS_REPEATING | - PVR_TIMER_TYPE_IS_MANUAL | - PVR_TIMER_TYPE_IS_REMINDER | - PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | - PVR_TIMER_TYPE_SUPPORTS_CHANNELS | - PVR_TIMER_TYPE_SUPPORTS_START_TIME | - PVR_TIMER_TYPE_SUPPORTS_END_TIME | - PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY | - PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS)); + allTypes.emplace_back(std::make_shared<CPVRTimerType>( + ++iTypeId, PVR_TIMER_TYPE_IS_REPEATING | PVR_TIMER_TYPE_IS_MANUAL | + PVR_TIMER_TYPE_IS_REMINDER | PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | + PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | + PVR_TIMER_TYPE_SUPPORTS_END_TIME | PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY | + PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS)); // one time read-only time-based reminder (created by timer rule) - allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId, - PVR_TIMER_TYPE_IS_MANUAL | - PVR_TIMER_TYPE_IS_REMINDER | - PVR_TIMER_TYPE_IS_READONLY | - PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | - PVR_TIMER_TYPE_SUPPORTS_CHANNELS | - PVR_TIMER_TYPE_SUPPORTS_START_TIME | - PVR_TIMER_TYPE_SUPPORTS_END_TIME, - g_localizeStrings.Get(819))); // One time (Scheduled by timer rule) + allTypes.emplace_back(std::make_shared<CPVRTimerType>( + ++iTypeId, + PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_IS_REMINDER | PVR_TIMER_TYPE_IS_READONLY | + PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_SUPPORTS_CHANNELS | + PVR_TIMER_TYPE_SUPPORTS_START_TIME | PVR_TIMER_TYPE_SUPPORTS_END_TIME, + g_localizeStrings.Get(819))); // One time (Scheduled by timer rule) // epg-based reminder rule - allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId, - PVR_TIMER_TYPE_IS_REPEATING | - PVR_TIMER_TYPE_IS_REMINDER | - PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | - PVR_TIMER_TYPE_SUPPORTS_TITLE_EPG_MATCH | - PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH | - PVR_TIMER_TYPE_SUPPORTS_CHANNELS | - PVR_TIMER_TYPE_SUPPORTS_ANY_CHANNEL | - PVR_TIMER_TYPE_SUPPORTS_START_TIME | - PVR_TIMER_TYPE_SUPPORTS_START_ANYTIME | - PVR_TIMER_TYPE_SUPPORTS_END_TIME | - PVR_TIMER_TYPE_SUPPORTS_END_ANYTIME | - PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY | - PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS | - PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN)); + allTypes.emplace_back(std::make_shared<CPVRTimerType>( + ++iTypeId, PVR_TIMER_TYPE_IS_REPEATING | PVR_TIMER_TYPE_IS_REMINDER | + PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | + PVR_TIMER_TYPE_SUPPORTS_TITLE_EPG_MATCH | + PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH | PVR_TIMER_TYPE_SUPPORTS_CHANNELS | + PVR_TIMER_TYPE_SUPPORTS_ANY_CHANNEL | PVR_TIMER_TYPE_SUPPORTS_START_TIME | + PVR_TIMER_TYPE_SUPPORTS_START_ANYTIME | PVR_TIMER_TYPE_SUPPORTS_END_TIME | + PVR_TIMER_TYPE_SUPPORTS_END_ANYTIME | PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY | + PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS | PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN)); // one time read-only epg-based reminder (created by timer rule) - allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId, - PVR_TIMER_TYPE_IS_REMINDER | - PVR_TIMER_TYPE_IS_READONLY | - PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE | - PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | - PVR_TIMER_TYPE_SUPPORTS_CHANNELS | - PVR_TIMER_TYPE_SUPPORTS_START_TIME | - PVR_TIMER_TYPE_SUPPORTS_START_MARGIN, - g_localizeStrings.Get(819))); // One time (Scheduled by timer rule) + allTypes.emplace_back(std::make_shared<CPVRTimerType>( + ++iTypeId, + PVR_TIMER_TYPE_IS_REMINDER | PVR_TIMER_TYPE_IS_READONLY | + PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE | PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | + PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | + PVR_TIMER_TYPE_SUPPORTS_START_MARGIN, + g_localizeStrings.Get(819))); // One time (Scheduled by timer rule) return allTypes; } @@ -121,9 +102,9 @@ std::shared_ptr<CPVRTimerType> CPVRTimerType::CreateFromIds(unsigned int iTypeId { const std::vector<std::shared_ptr<CPVRTimerType>> types = GetAllTypes(); const auto it = - std::find_if(types.cbegin(), types.cend(), [iClientId, iTypeId](const auto& type) { - return type->GetClientId() == iClientId && type->GetTypeId() == iTypeId; - }); + std::find_if(types.cbegin(), types.cend(), + [iClientId, iTypeId](const auto& type) + { return type->GetClientId() == iClientId && type->GetTypeId() == iTypeId; }); if (it != types.cend()) return (*it); @@ -145,7 +126,8 @@ std::shared_ptr<CPVRTimerType> CPVRTimerType::CreateFromAttributes(uint64_t iMus { const std::vector<std::shared_ptr<CPVRTimerType>> types = GetAllTypes(); const auto it = std::find_if(types.cbegin(), types.cend(), - [iClientId, iMustHaveAttr, iMustNotHaveAttr](const auto& type) { + [iClientId, iMustHaveAttr, iMustNotHaveAttr](const auto& type) + { return type->GetClientId() == iClientId && (type->GetAttributes() & iMustHaveAttr) == iMustHaveAttr && (type->GetAttributes() & iMustNotHaveAttr) == 0; @@ -167,9 +149,8 @@ std::shared_ptr<CPVRTimerType> CPVRTimerType::CreateFromAttributes(uint64_t iMus return {}; } -CPVRTimerType::CPVRTimerType() : - m_iTypeId(PVR_TIMER_TYPE_NONE), - m_iAttributes(PVR_TIMER_TYPE_ATTRIBUTE_NONE) +CPVRTimerType::CPVRTimerType() + : m_iTypeId(PVR_TIMER_TYPE_NONE), m_iAttributes(PVR_TIMER_TYPE_ATTRIBUTE_NONE) { } @@ -193,12 +174,10 @@ CPVRTimerType::CPVRTimerType(unsigned int iTypeId, CPVRTimerType::~CPVRTimerType() = default; -bool CPVRTimerType::operator ==(const CPVRTimerType& right) const +bool CPVRTimerType::operator==(const CPVRTimerType& right) const { - return (m_iClientId == right.m_iClientId && - m_iTypeId == right.m_iTypeId && - m_iAttributes == right.m_iAttributes && - m_strDescription == right.m_strDescription && + return (m_iClientId == right.m_iClientId && m_iTypeId == right.m_iTypeId && + m_iAttributes == right.m_iAttributes && m_strDescription == right.m_strDescription && m_priorityValues == right.m_priorityValues && m_iPriorityDefault == right.m_iPriorityDefault && m_lifetimeValues == right.m_lifetimeValues && @@ -211,7 +190,7 @@ bool CPVRTimerType::operator ==(const CPVRTimerType& right) const m_iRecordingGroupDefault == right.m_iRecordingGroupDefault); } -bool CPVRTimerType::operator !=(const CPVRTimerType& right) const +bool CPVRTimerType::operator!=(const CPVRTimerType& right) const { return !(*this == right); } @@ -242,23 +221,20 @@ void CPVRTimerType::InitDescription() int id; if (m_iAttributes & PVR_TIMER_TYPE_IS_REPEATING) { - id = (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL) - ? 822 // "Timer rule" - : 823; // "Timer rule (guide-based)" + id = (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL) ? 822 // "Timer rule" + : 823; // "Timer rule (guide-based)" } else { - id = (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL) - ? 820 // "One time" - : 821; // "One time (guide-based) + id = (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL) ? 820 // "One time" + : 821; // "One time (guide-based) } m_strDescription = g_localizeStrings.Get(id); } // add reminder/recording prefix - int prefixId = (m_iAttributes & PVR_TIMER_TYPE_IS_REMINDER) - ? 824 // Reminder: ... - : 825; // Recording: ... + int prefixId = (m_iAttributes & PVR_TIMER_TYPE_IS_REMINDER) ? 824 // Reminder: ... + : 825; // Recording: ... m_strDescription = StringUtils::Format(g_localizeStrings.Get(prefixId), m_strDescription); } @@ -401,7 +377,8 @@ void CPVRTimerType::InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& typ { // No values given by addon, but prevent duplicate episodes supported. Use default values 0..1 m_preventDupEpisodesValues.emplace_back(g_localizeStrings.Get(815), 0); // "Record all episodes" - m_preventDupEpisodesValues.emplace_back(g_localizeStrings.Get(816), 1); // "Record only new episodes" + m_preventDupEpisodesValues.emplace_back(g_localizeStrings.Get(816), + 1); // "Record only new episodes" m_iPreventDupEpisodesDefault = DEFAULT_RECORDING_DUPLICATEHANDLING; } else @@ -411,7 +388,8 @@ void CPVRTimerType::InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& typ } } -void CPVRTimerType::GetPreventDuplicateEpisodesValues(std::vector<std::pair<std::string, int>>& list) const +void CPVRTimerType::GetPreventDuplicateEpisodesValues( + std::vector<std::pair<std::string, int>>& list) const { std::copy(m_preventDupEpisodesValues.cbegin(), m_preventDupEpisodesValues.cend(), std::back_inserter(list)); @@ -440,7 +418,7 @@ void CPVRTimerType::InitRecordingGroupValues(const PVR_TIMER_TYPE& type) } } -void CPVRTimerType::GetRecordingGroupValues(std::vector< std::pair<std::string, int>>& list) const +void CPVRTimerType::GetRecordingGroupValues(std::vector<std::pair<std::string, int>>& list) const { std::copy(m_recordingGroupValues.cbegin(), m_recordingGroupValues.cend(), std::back_inserter(list)); diff --git a/xbmc/pvr/timers/PVRTimerType.h b/xbmc/pvr/timers/PVRTimerType.h index a59925f235a9d..512fb77a87b22 100644 --- a/xbmc/pvr/timers/PVRTimerType.h +++ b/xbmc/pvr/timers/PVRTimerType.h @@ -20,400 +20,450 @@ struct PVR_TIMER_TYPE; namespace PVR { - class CPVRClient; +class CPVRClient; - static const int DEFAULT_RECORDING_PRIORITY = 50; - static const int DEFAULT_RECORDING_LIFETIME = 99; // days - static const unsigned int DEFAULT_RECORDING_DUPLICATEHANDLING = 0; +static const int DEFAULT_RECORDING_PRIORITY = 50; +static const int DEFAULT_RECORDING_LIFETIME = 99; // days +static const unsigned int DEFAULT_RECORDING_DUPLICATEHANDLING = 0; - class CPVRTimerType +class CPVRTimerType +{ +public: + /*! + * @brief Return a list with all known timer types. + * @return A list of timer types or an empty list if no types available. + */ + static const std::vector<std::shared_ptr<CPVRTimerType>> GetAllTypes(); + + /*! + * @brief Return the first available timer type from given client. + * @param client the PVR client. + * @return A timer type or NULL if none available. + */ + static const std::shared_ptr<CPVRTimerType> GetFirstAvailableType( + const std::shared_ptr<const CPVRClient>& client); + + /*! + * @brief Create a timer type from given timer type id and client id. + * @param iTimerType the timer type id. + * @param iClientId the PVR client id. + * @return A timer type instance. + */ + static std::shared_ptr<CPVRTimerType> CreateFromIds(unsigned int iTypeId, int iClientId); + + /*! + * @brief Create a timer type from given timer type attributes and client id. + * @param iMustHaveAttr a combination of PVR_TIMER_TYPE_* attributes the type to create must have. + * @param iMustNotHaveAttr a combination of PVR_TIMER_TYPE_* attributes the type to create must not have. + * @param iClientId the PVR client id. + * @return A timer type instance. + */ + static std::shared_ptr<CPVRTimerType> CreateFromAttributes(uint64_t iMustHaveAttr, + uint64_t iMustNotHaveAttr, + int iClientId); + + CPVRTimerType(); + CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId); + CPVRTimerType(unsigned int iTypeId, uint64_t iAttributes, const std::string& strDescription = ""); + + virtual ~CPVRTimerType(); + + CPVRTimerType(const CPVRTimerType& type) = delete; + CPVRTimerType& operator=(const CPVRTimerType& orig) = delete; + + bool operator==(const CPVRTimerType& right) const; + bool operator!=(const CPVRTimerType& right) const; + + /*! + * @brief Update the data of this instance with the data given by another type instance. + * @param type The instance containing the updated data. + */ + void Update(const CPVRTimerType& type); + + /*! + * @brief Get the PVR client id for this type. + * @return The PVR client id. + */ + int GetClientId() const { return m_iClientId; } + + /*! + * @brief Get the numeric type id of this type. + * @return The type id. + */ + unsigned int GetTypeId() const { return m_iTypeId; } + + /*! + * @brief Get the plain text (UI) description of this type. + * @return The description. + */ + const std::string& GetDescription() const { return m_strDescription; } + + /*! + * @brief Get the attributes of this type. + * @return The attributes. + */ + uint64_t GetAttributes() const { return m_iAttributes; } + + /*! + * @brief Check whether this type is for timer rules or one time timers. + * @return True if type represents a timer rule, false otherwise. + */ + bool IsTimerRule() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_REPEATING) > 0; } + + /*! + * @brief Check whether this type is for reminder timers or recording timers. + * @return True if type represents a reminder timer, false otherwise. + */ + bool IsReminder() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_REMINDER) > 0; } + + /*! + * @brief Check whether this type is for timer rules or one time timers. + * @return True if type represents a one time timer, false otherwise. + */ + bool IsOnetime() const { return !IsTimerRule(); } + + /*! + * @brief Check whether this type is for epg-based or manual timers. + * @return True if manual, false otherwise. + */ + bool IsManual() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL) > 0; } + + /*! + * @brief Check whether this type is for epg-based or manual timers. + * @return True if epg-based, false otherwise. + */ + bool IsEpgBased() const { return !IsManual(); } + + /*! + * @brief Check whether this type is for epg-based timer rules. + * @return True if epg-based timer rule, false otherwise. + */ + bool IsEpgBasedTimerRule() const { return IsEpgBased() && IsTimerRule(); } + + /*! + * @brief Check whether this type is for one time epg-based timers. + * @return True if one time epg-based, false otherwise. + */ + bool IsEpgBasedOnetime() const { return IsEpgBased() && IsOnetime(); } + + /*! + * @brief Check whether this type is for manual timer rules. + * @return True if manual timer rule, false otherwise. + */ + bool IsManualTimerRule() const { return IsManual() && IsTimerRule(); } + + /*! + * @brief Check whether this type is for one time manual timers. + * @return True if one time manual, false otherwise. + */ + bool IsManualOnetime() const { return IsManual() && IsOnetime(); } + + /*! + * @brief Check whether this type is readonly (must not be modified after initial creation). + * @return True if readonly, false otherwise. + */ + bool IsReadOnly() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_READONLY) > 0; } + + /*! + * @brief Check whether this type allows deletion. + * @return True if type allows deletion, false otherwise. + */ + bool AllowsDelete() const { return !IsReadOnly() || SupportsReadOnlyDelete(); } + + /*! + * @brief Check whether this type forbids creation of new timers of this type. + * @return True if new instances are forbidden, false otherwise. + */ + bool ForbidsNewInstances() const + { + return (m_iAttributes & PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES) > 0; + } + + /*! + * @brief Check whether this timer type is forbidden when epg tag info is present. + * @return True if new instances are forbidden when epg info is present, false otherwise. + */ + bool ForbidsEpgTagOnCreate() const + { + return (m_iAttributes & PVR_TIMER_TYPE_FORBIDS_EPG_TAG_ON_CREATE) > 0; + } + + /*! + * @brief Check whether this timer type requires epg tag info to be present. + * @return True if new instances require EPG info, false otherwise. + */ + bool RequiresEpgTagOnCreate() const + { + return (m_iAttributes & (PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE | + PVR_TIMER_TYPE_REQUIRES_EPG_SERIES_ON_CREATE | + PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE)) > 0; + } + + /*! + * @brief Check whether this timer type requires epg tag info including series attributes to be present. + * @return True if new instances require an EPG tag with series attributes, false otherwise. + */ + bool RequiresEpgSeriesOnCreate() const + { + return (m_iAttributes & PVR_TIMER_TYPE_REQUIRES_EPG_SERIES_ON_CREATE) > 0; + } + + /*! + * @brief Check whether this timer type requires epg tag info including a series link to be present. + * @return True if new instances require an EPG tag with a series link, false otherwise. + */ + bool RequiresEpgSeriesLinkOnCreate() const + { + return (m_iAttributes & PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE) > 0; + } + + /*! + * @brief Check whether this type supports the "enabling/disabling" of timers of its type. + * @return True if "enabling/disabling" feature is supported, false otherwise. + */ + bool SupportsEnableDisable() const + { + return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE) > 0; + } + + /*! + * @brief Check whether this type supports channels. + * @return True if channels are supported, false otherwise. + */ + bool SupportsChannels() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_CHANNELS) > 0; } + + /*! + * @brief Check whether this type supports start time. + * @return True if start time values are supported, false otherwise. + */ + bool SupportsStartTime() const + { + return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_TIME) > 0; + } + + /*! + * @brief Check whether this type supports end time. + * @return True if end time values are supported, false otherwise. + */ + bool SupportsEndTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_TIME) > 0; } + /*! + * @brief Check whether this type supports start any time. + * @return True if start any time is supported, false otherwise. + */ + bool SupportsStartAnyTime() const + { + return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_ANYTIME) > 0; + } + + /*! + * @brief Check whether this type supports end any time. + * @return True if end any time is supported, false otherwise. + */ + bool SupportsEndAnyTime() const + { + return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_ANYTIME) > 0; + } + + /*! + * @brief Check whether this type supports matching a search string against epg episode title. + * @return True if title matching is supported, false otherwise. + */ + bool SupportsEpgTitleMatch() const + { + return (m_iAttributes & (PVR_TIMER_TYPE_SUPPORTS_TITLE_EPG_MATCH | + PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH)) > 0; + } + + /*! + * @brief Check whether this type supports matching a search string against extended (fulltext) epg data. This + includes title matching. + * @return True if fulltext matching is supported, false otherwise. + */ + bool SupportsEpgFulltextMatch() const + { + return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH) > 0; + } + + /*! + * @brief Check whether this type supports a first day the timer is active. + * @return True if first day is supported, false otherwise. + */ + bool SupportsFirstDay() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY) > 0; } + + /*! + * @brief Check whether this type supports weekdays for timer schedules. + * @return True if weekdays are supported, false otherwise. + */ + bool SupportsWeekdays() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS) > 0; } + + /*! + * @brief Check whether this type supports the "record only new episodes" feature. + * @return True if the "record only new episodes" feature is supported, false otherwise. + */ + bool SupportsRecordOnlyNewEpisodes() const + { + return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORD_ONLY_NEW_EPISODES) > 0; + } + + /*! + * @brief Check whether this type supports pre record time. + * @return True if pre record time is supported, false otherwise. + */ + bool SupportsStartMargin() const + { + return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_MARGIN) > 0 || + (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN) > 0; + } + + /*! + * @brief Check whether this type supports post record time. + * @return True if post record time is supported, false otherwise. + */ + bool SupportsEndMargin() const + { + return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_MARGIN) > 0 || + (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN) > 0; + } + + /*! + * @brief Check whether this type supports recording priorities. + * @return True if recording priority is supported, false otherwise. + */ + bool SupportsPriority() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_PRIORITY) > 0; } + + /*! + * @brief Check whether this type supports lifetime for recordings. + * @return True if recording lifetime is supported, false otherwise. + */ + bool SupportsLifetime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_LIFETIME) > 0; } + + /*! + * @brief Check whether this type supports MaxRecordings for recordings. + * @return True if MaxRecordings is supported, false otherwise. + */ + bool SupportsMaxRecordings() const + { + return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_MAX_RECORDINGS) > 0; + } + + /*! + * @brief Check whether this type supports user specified recording folders. + * @return True if recording folders are supported, false otherwise. + */ + bool SupportsRecordingFolders() const + { + return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS) > 0; + } + + /*! + * @brief Check whether this type supports recording groups. + * @return True if recording groups are supported, false otherwise. + */ + bool SupportsRecordingGroup() const + { + return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORDING_GROUP) > 0; + } + + /*! + * @brief Check whether this type supports 'any channel', for example for defining a timer rule that should match any channel instead of a particular channel. + * @return True if any channel is supported, false otherwise. + */ + bool SupportsAnyChannel() const + { + return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_ANY_CHANNEL) > 0; + } + + /*! + * @brief Check whether this type supports deletion of an otherwise read-only timer. + * @return True if read-only deletion is supported, false otherwise. + */ + bool SupportsReadOnlyDelete() const { - public: - /*! - * @brief Return a list with all known timer types. - * @return A list of timer types or an empty list if no types available. - */ - static const std::vector<std::shared_ptr<CPVRTimerType>> GetAllTypes(); - - /*! - * @brief Return the first available timer type from given client. - * @param client the PVR client. - * @return A timer type or NULL if none available. - */ - static const std::shared_ptr<CPVRTimerType> GetFirstAvailableType( - const std::shared_ptr<const CPVRClient>& client); - - /*! - * @brief Create a timer type from given timer type id and client id. - * @param iTimerType the timer type id. - * @param iClientId the PVR client id. - * @return A timer type instance. - */ - static std::shared_ptr<CPVRTimerType> CreateFromIds(unsigned int iTypeId, int iClientId); - - /*! - * @brief Create a timer type from given timer type attributes and client id. - * @param iMustHaveAttr a combination of PVR_TIMER_TYPE_* attributes the type to create must have. - * @param iMustNotHaveAttr a combination of PVR_TIMER_TYPE_* attributes the type to create must not have. - * @param iClientId the PVR client id. - * @return A timer type instance. - */ - static std::shared_ptr<CPVRTimerType> CreateFromAttributes(uint64_t iMustHaveAttr, - uint64_t iMustNotHaveAttr, - int iClientId); - - CPVRTimerType(); - CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId); - CPVRTimerType(unsigned int iTypeId, - uint64_t iAttributes, - const std::string& strDescription = ""); - - virtual ~CPVRTimerType(); - - CPVRTimerType(const CPVRTimerType& type) = delete; - CPVRTimerType& operator=(const CPVRTimerType& orig) = delete; - - bool operator ==(const CPVRTimerType& right) const; - bool operator !=(const CPVRTimerType& right) const; - - /*! - * @brief Update the data of this instance with the data given by another type instance. - * @param type The instance containing the updated data. - */ - void Update(const CPVRTimerType& type); - - /*! - * @brief Get the PVR client id for this type. - * @return The PVR client id. - */ - int GetClientId() const { return m_iClientId; } - - /*! - * @brief Get the numeric type id of this type. - * @return The type id. - */ - unsigned int GetTypeId() const { return m_iTypeId; } - - /*! - * @brief Get the plain text (UI) description of this type. - * @return The description. - */ - const std::string& GetDescription() const { return m_strDescription; } - - /*! - * @brief Get the attributes of this type. - * @return The attributes. - */ - uint64_t GetAttributes() const { return m_iAttributes; } - - /*! - * @brief Check whether this type is for timer rules or one time timers. - * @return True if type represents a timer rule, false otherwise. - */ - bool IsTimerRule() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_REPEATING) > 0; } - - /*! - * @brief Check whether this type is for reminder timers or recording timers. - * @return True if type represents a reminder timer, false otherwise. - */ - bool IsReminder() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_REMINDER) > 0; } - - /*! - * @brief Check whether this type is for timer rules or one time timers. - * @return True if type represents a one time timer, false otherwise. - */ - bool IsOnetime() const { return !IsTimerRule(); } - - /*! - * @brief Check whether this type is for epg-based or manual timers. - * @return True if manual, false otherwise. - */ - bool IsManual() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL) > 0; } - - /*! - * @brief Check whether this type is for epg-based or manual timers. - * @return True if epg-based, false otherwise. - */ - bool IsEpgBased() const { return !IsManual(); } - - /*! - * @brief Check whether this type is for epg-based timer rules. - * @return True if epg-based timer rule, false otherwise. - */ - bool IsEpgBasedTimerRule() const { return IsEpgBased() && IsTimerRule(); } - - /*! - * @brief Check whether this type is for one time epg-based timers. - * @return True if one time epg-based, false otherwise. - */ - bool IsEpgBasedOnetime() const { return IsEpgBased() && IsOnetime(); } - - /*! - * @brief Check whether this type is for manual timer rules. - * @return True if manual timer rule, false otherwise. - */ - bool IsManualTimerRule() const { return IsManual() && IsTimerRule(); } - - /*! - * @brief Check whether this type is for one time manual timers. - * @return True if one time manual, false otherwise. - */ - bool IsManualOnetime() const { return IsManual() && IsOnetime(); } - - /*! - * @brief Check whether this type is readonly (must not be modified after initial creation). - * @return True if readonly, false otherwise. - */ - bool IsReadOnly() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_READONLY) > 0; } - - /*! - * @brief Check whether this type allows deletion. - * @return True if type allows deletion, false otherwise. - */ - bool AllowsDelete() const { return !IsReadOnly() || SupportsReadOnlyDelete(); } - - /*! - * @brief Check whether this type forbids creation of new timers of this type. - * @return True if new instances are forbidden, false otherwise. - */ - bool ForbidsNewInstances() const { return (m_iAttributes & PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES) > 0; } - - /*! - * @brief Check whether this timer type is forbidden when epg tag info is present. - * @return True if new instances are forbidden when epg info is present, false otherwise. - */ - bool ForbidsEpgTagOnCreate() const { return (m_iAttributes & PVR_TIMER_TYPE_FORBIDS_EPG_TAG_ON_CREATE) > 0; } - - /*! - * @brief Check whether this timer type requires epg tag info to be present. - * @return True if new instances require EPG info, false otherwise. - */ - bool RequiresEpgTagOnCreate() const { return (m_iAttributes & (PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE | - PVR_TIMER_TYPE_REQUIRES_EPG_SERIES_ON_CREATE | - PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE)) > 0; } - - /*! - * @brief Check whether this timer type requires epg tag info including series attributes to be present. - * @return True if new instances require an EPG tag with series attributes, false otherwise. - */ - bool RequiresEpgSeriesOnCreate() const { return (m_iAttributes & PVR_TIMER_TYPE_REQUIRES_EPG_SERIES_ON_CREATE) > 0; } - - /*! - * @brief Check whether this timer type requires epg tag info including a series link to be present. - * @return True if new instances require an EPG tag with a series link, false otherwise. - */ - bool RequiresEpgSeriesLinkOnCreate() const { return (m_iAttributes & PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE) > 0; } - - /*! - * @brief Check whether this type supports the "enabling/disabling" of timers of its type. - * @return True if "enabling/disabling" feature is supported, false otherwise. - */ - bool SupportsEnableDisable() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE) > 0; } - - /*! - * @brief Check whether this type supports channels. - * @return True if channels are supported, false otherwise. - */ - bool SupportsChannels() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_CHANNELS) > 0; } - - /*! - * @brief Check whether this type supports start time. - * @return True if start time values are supported, false otherwise. - */ - bool SupportsStartTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_TIME) > 0; } - - /*! - * @brief Check whether this type supports end time. - * @return True if end time values are supported, false otherwise. - */ - bool SupportsEndTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_TIME) > 0; } - /*! - * @brief Check whether this type supports start any time. - * @return True if start any time is supported, false otherwise. - */ - bool SupportsStartAnyTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_ANYTIME) > 0; } - - /*! - * @brief Check whether this type supports end any time. - * @return True if end any time is supported, false otherwise. - */ - bool SupportsEndAnyTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_ANYTIME) > 0; } - - /*! - * @brief Check whether this type supports matching a search string against epg episode title. - * @return True if title matching is supported, false otherwise. - */ - bool SupportsEpgTitleMatch() const { return (m_iAttributes & (PVR_TIMER_TYPE_SUPPORTS_TITLE_EPG_MATCH | PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH)) > 0; } - - /*! - * @brief Check whether this type supports matching a search string against extended (fulltext) epg data. This - includes title matching. - * @return True if fulltext matching is supported, false otherwise. - */ - bool SupportsEpgFulltextMatch() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH) > 0; } - - /*! - * @brief Check whether this type supports a first day the timer is active. - * @return True if first day is supported, false otherwise. - */ - bool SupportsFirstDay() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY) > 0; } - - /*! - * @brief Check whether this type supports weekdays for timer schedules. - * @return True if weekdays are supported, false otherwise. - */ - bool SupportsWeekdays() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS) > 0; } - - /*! - * @brief Check whether this type supports the "record only new episodes" feature. - * @return True if the "record only new episodes" feature is supported, false otherwise. - */ - bool SupportsRecordOnlyNewEpisodes() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORD_ONLY_NEW_EPISODES) > 0; } - - /*! - * @brief Check whether this type supports pre record time. - * @return True if pre record time is supported, false otherwise. - */ - bool SupportsStartMargin() const - { - return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_MARGIN) > 0 || - (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN) > 0; - } - - /*! - * @brief Check whether this type supports post record time. - * @return True if post record time is supported, false otherwise. - */ - bool SupportsEndMargin() const - { - return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_MARGIN) > 0 || - (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN) > 0; - } - - /*! - * @brief Check whether this type supports recording priorities. - * @return True if recording priority is supported, false otherwise. - */ - bool SupportsPriority() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_PRIORITY) > 0; } - - /*! - * @brief Check whether this type supports lifetime for recordings. - * @return True if recording lifetime is supported, false otherwise. - */ - bool SupportsLifetime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_LIFETIME) > 0; } - - /*! - * @brief Check whether this type supports MaxRecordings for recordings. - * @return True if MaxRecordings is supported, false otherwise. - */ - bool SupportsMaxRecordings() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_MAX_RECORDINGS) > 0; } - - /*! - * @brief Check whether this type supports user specified recording folders. - * @return True if recording folders are supported, false otherwise. - */ - bool SupportsRecordingFolders() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS) > 0; } - - /*! - * @brief Check whether this type supports recording groups. - * @return True if recording groups are supported, false otherwise. - */ - bool SupportsRecordingGroup() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORDING_GROUP) > 0; } - - /*! - * @brief Check whether this type supports 'any channel', for example for defining a timer rule that should match any channel instead of a particular channel. - * @return True if any channel is supported, false otherwise. - */ - bool SupportsAnyChannel() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_ANY_CHANNEL) > 0; } - - /*! - * @brief Check whether this type supports deletion of an otherwise read-only timer. - * @return True if read-only deletion is supported, false otherwise. - */ - bool SupportsReadOnlyDelete() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_READONLY_DELETE) > 0; } - - /*! - * @brief Obtain a list with all possible values for the priority attribute. - * @param list out, the list with the values or an empty list, if priority is not supported by this type. - */ - void GetPriorityValues(std::vector<std::pair<std::string, int>>& list) const; - - /*! - * @brief Obtain the default value for the priority attribute. - * @return the default value. - */ - int GetPriorityDefault() const { return m_iPriorityDefault; } - - /*! - * @brief Obtain a list with all possible values for the lifetime attribute. - * @param list out, the list with the values or an empty list, if lifetime is not supported by this type. - */ - void GetLifetimeValues(std::vector<std::pair<std::string, int>>& list) const; - - /*! - * @brief Obtain the default value for the lifetime attribute. - * @return the default value. - */ - int GetLifetimeDefault() const { return m_iLifetimeDefault; } - - /*! - * @brief Obtain a list with all possible values for the MaxRecordings attribute. - * @param list out, the list with the values or an empty list, if MaxRecordings is not supported by this type. - */ - void GetMaxRecordingsValues(std::vector<std::pair<std::string, int>>& list) const; - - /*! - * @brief Obtain the default value for the MaxRecordings attribute. - * @return the default value. - */ - int GetMaxRecordingsDefault() const { return m_iMaxRecordingsDefault; } - - /*! - * @brief Obtain a list with all possible values for the duplicate episode prevention attribute. - * @param list out, the list with the values or an empty list, if duplicate episode prevention is not supported by this type. - */ - void GetPreventDuplicateEpisodesValues(std::vector<std::pair<std::string, int>>& list) const; - - /*! - * @brief Obtain the default value for the duplicate episode prevention attribute. - * @return the default value. - */ - int GetPreventDuplicateEpisodesDefault() const { return m_iPreventDupEpisodesDefault; } - - /*! - * @brief Obtain a list with all possible values for the recording group attribute. - * @param list out, the list with the values or an empty list, if recording group is not supported by this type. - */ - void GetRecordingGroupValues(std::vector<std::pair<std::string, int>>& list) const; - - /*! - * @brief Obtain the default value for the Recording Group attribute. - * @return the default value. - */ - int GetRecordingGroupDefault() const { return m_iRecordingGroupDefault; } - - private: - void InitDescription(); - void InitAttributeValues(const PVR_TIMER_TYPE& type); - void InitPriorityValues(const PVR_TIMER_TYPE& type); - void InitLifetimeValues(const PVR_TIMER_TYPE& type); - void InitMaxRecordingsValues(const PVR_TIMER_TYPE& type); - void InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& type); - void InitRecordingGroupValues(const PVR_TIMER_TYPE& type); - - int m_iClientId = PVR_CLIENT_INVALID_UID; - unsigned int m_iTypeId; - uint64_t m_iAttributes; - std::string m_strDescription; - std::vector< std::pair<std::string, int> > m_priorityValues; - int m_iPriorityDefault = DEFAULT_RECORDING_PRIORITY; - std::vector< std::pair<std::string, int> > m_lifetimeValues; - int m_iLifetimeDefault = DEFAULT_RECORDING_LIFETIME; - std::vector< std::pair<std::string, int> > m_maxRecordingsValues; - int m_iMaxRecordingsDefault = 0; - std::vector< std::pair<std::string, int> > m_preventDupEpisodesValues; - unsigned int m_iPreventDupEpisodesDefault = DEFAULT_RECORDING_DUPLICATEHANDLING; - std::vector< std::pair<std::string, int> > m_recordingGroupValues; - unsigned int m_iRecordingGroupDefault = 0; - }; -} + return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_READONLY_DELETE) > 0; + } + + /*! + * @brief Obtain a list with all possible values for the priority attribute. + * @param list out, the list with the values or an empty list, if priority is not supported by this type. + */ + void GetPriorityValues(std::vector<std::pair<std::string, int>>& list) const; + + /*! + * @brief Obtain the default value for the priority attribute. + * @return the default value. + */ + int GetPriorityDefault() const { return m_iPriorityDefault; } + + /*! + * @brief Obtain a list with all possible values for the lifetime attribute. + * @param list out, the list with the values or an empty list, if lifetime is not supported by this type. + */ + void GetLifetimeValues(std::vector<std::pair<std::string, int>>& list) const; + + /*! + * @brief Obtain the default value for the lifetime attribute. + * @return the default value. + */ + int GetLifetimeDefault() const { return m_iLifetimeDefault; } + + /*! + * @brief Obtain a list with all possible values for the MaxRecordings attribute. + * @param list out, the list with the values or an empty list, if MaxRecordings is not supported by this type. + */ + void GetMaxRecordingsValues(std::vector<std::pair<std::string, int>>& list) const; + + /*! + * @brief Obtain the default value for the MaxRecordings attribute. + * @return the default value. + */ + int GetMaxRecordingsDefault() const { return m_iMaxRecordingsDefault; } + + /*! + * @brief Obtain a list with all possible values for the duplicate episode prevention attribute. + * @param list out, the list with the values or an empty list, if duplicate episode prevention is not supported by this type. + */ + void GetPreventDuplicateEpisodesValues(std::vector<std::pair<std::string, int>>& list) const; + + /*! + * @brief Obtain the default value for the duplicate episode prevention attribute. + * @return the default value. + */ + int GetPreventDuplicateEpisodesDefault() const { return m_iPreventDupEpisodesDefault; } + + /*! + * @brief Obtain a list with all possible values for the recording group attribute. + * @param list out, the list with the values or an empty list, if recording group is not supported by this type. + */ + void GetRecordingGroupValues(std::vector<std::pair<std::string, int>>& list) const; + + /*! + * @brief Obtain the default value for the Recording Group attribute. + * @return the default value. + */ + int GetRecordingGroupDefault() const { return m_iRecordingGroupDefault; } + +private: + void InitDescription(); + void InitAttributeValues(const PVR_TIMER_TYPE& type); + void InitPriorityValues(const PVR_TIMER_TYPE& type); + void InitLifetimeValues(const PVR_TIMER_TYPE& type); + void InitMaxRecordingsValues(const PVR_TIMER_TYPE& type); + void InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& type); + void InitRecordingGroupValues(const PVR_TIMER_TYPE& type); + + int m_iClientId = PVR_CLIENT_INVALID_UID; + unsigned int m_iTypeId; + uint64_t m_iAttributes; + std::string m_strDescription; + std::vector<std::pair<std::string, int>> m_priorityValues; + int m_iPriorityDefault = DEFAULT_RECORDING_PRIORITY; + std::vector<std::pair<std::string, int>> m_lifetimeValues; + int m_iLifetimeDefault = DEFAULT_RECORDING_LIFETIME; + std::vector<std::pair<std::string, int>> m_maxRecordingsValues; + int m_iMaxRecordingsDefault = 0; + std::vector<std::pair<std::string, int>> m_preventDupEpisodesValues; + unsigned int m_iPreventDupEpisodesDefault = DEFAULT_RECORDING_DUPLICATEHANDLING; + std::vector<std::pair<std::string, int>> m_recordingGroupValues; + unsigned int m_iRecordingGroupDefault = 0; +}; +} // namespace PVR From f51bdda04014f9c6aa81831d6449fb9fee8d21b5 Mon Sep 17 00:00:00 2001 From: phunkyfish <phunkyfish@gmail.com> Date: Tue, 27 Aug 2024 20:11:37 +0100 Subject: [PATCH 416/651] [pvr] change to use unique_ptr to preserve reference for calls to StartPlayback() --- xbmc/pvr/PVRPlaybackState.cpp | 10 +++++----- xbmc/pvr/PVRPlaybackState.h | 4 +++- xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp | 12 ++++++------ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/xbmc/pvr/PVRPlaybackState.cpp b/xbmc/pvr/PVRPlaybackState.cpp index 4ab53cec7e369..7535e45861012 100644 --- a/xbmc/pvr/PVRPlaybackState.cpp +++ b/xbmc/pvr/PVRPlaybackState.cpp @@ -341,13 +341,13 @@ bool CPVRPlaybackState::OnPlaybackEnded(const CFileItem& item) std::unique_ptr<CFileItem> nextToPlay{GetNextAutoplayItem(item)}; if (nextToPlay) - StartPlayback(nextToPlay.release(), ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM, + StartPlayback(nextToPlay, ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM, PVR_SOURCE::DEFAULT); return OnPlaybackStopped(item); } -void CPVRPlaybackState::StartPlayback(CFileItem* item, +void CPVRPlaybackState::StartPlayback(std::unique_ptr<CFileItem>& item, ContentUtils::PlayMode mode, PVR_SOURCE source) const { @@ -374,8 +374,7 @@ void CPVRPlaybackState::StartPlayback(CFileItem* item, const std::shared_ptr<CPVREpgInfoTag> epgTag = item->GetPVRChannelInfoTag()->GetEPGNow(); if (epgTag) { - delete item; - item = new CFileItem(epgTag); + item = std::make_unique<CFileItem>(epgTag); } } } @@ -422,7 +421,8 @@ void CPVRPlaybackState::StartPlayback(CFileItem* item, } } - CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 0, 0, static_cast<void*>(item)); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 0, 0, + static_cast<void*>(item.release())); } bool CPVRPlaybackState::IsPlaying() const diff --git a/xbmc/pvr/PVRPlaybackState.h b/xbmc/pvr/PVRPlaybackState.h index 098ff8a4499b2..3e41c130eb6c4 100644 --- a/xbmc/pvr/PVRPlaybackState.h +++ b/xbmc/pvr/PVRPlaybackState.h @@ -76,7 +76,9 @@ class CPVRPlaybackState * @param item containing a channel, a recording or an epg tag. * @param mode playback mode. */ - void StartPlayback(CFileItem* item, ContentUtils::PlayMode mode, PVR_SOURCE source) const; + void StartPlayback(std::unique_ptr<CFileItem>& item, + ContentUtils::PlayMode mode, + PVR_SOURCE source) const; /*! * @brief Check if a TV channel, radio channel or recording is playing. diff --git a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp index a3c294bc073f9..a41feec3e7893 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp @@ -162,7 +162,7 @@ bool CPVRGUIActionsPlayback::PlayRecording(const CFileItem& item, bool bCheckRes } else { - CFileItem* itemToPlay = new CFileItem(recording); + std::unique_ptr<CFileItem> itemToPlay{std::make_unique<CFileItem>(recording)}; itemToPlay->SetStartOffset(item.GetStartOffset()); CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback( itemToPlay, ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM, PVR_SOURCE::DEFAULT); @@ -221,7 +221,7 @@ bool CPVRGUIActionsPlayback::PlayEpgTag( client->GetEpgTagStreamProperties(epgTag, props); - CFileItem* itemToPlay = nullptr; + std::unique_ptr<CFileItem> itemToPlay; PVR_SOURCE source = DEFAULT; if (props.EPGPlaybackAsLive()) { @@ -231,11 +231,11 @@ bool CPVRGUIActionsPlayback::PlayEpgTag( return false; source = PVR_SOURCE_EPG_AS_LIVE; - itemToPlay = new CFileItem(groupMember); + itemToPlay = std::make_unique<CFileItem>(groupMember); } else { - itemToPlay = new CFileItem(epgTag); + itemToPlay = std::make_unique<CFileItem>(epgTag); } CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(itemToPlay, mode, source); @@ -321,9 +321,9 @@ bool CPVRGUIActionsPlayback::SwitchToChannel(const CFileItem& item, bool bCheckR if (!groupMember) return false; + std::unique_ptr<CFileItem> itemToPlay{std::make_unique<CFileItem>(groupMember)}; CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback( - new CFileItem(groupMember), ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM, - PVR_SOURCE::DEFAULT); + itemToPlay, ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM, PVR_SOURCE::DEFAULT); CheckAndSwitchToFullscreen(bFullscreen); return true; } From a0beaa0af4167582319dc9c9014a8d6825aba4ac Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:48:08 +0200 Subject: [PATCH 417/651] [PVR] Factor out CPVRTimerType setting values into class CPVRIntSettingValues. --- .../pvr/dialogs/GUIDialogPVRTimerSettings.cpp | 16 +- xbmc/pvr/settings/CMakeLists.txt | 6 +- xbmc/pvr/settings/PVRIntSettingValues.cpp | 62 +++++++ xbmc/pvr/settings/PVRIntSettingValues.h | 49 ++++++ xbmc/pvr/timers/PVRTimerType.cpp | 151 +++--------------- xbmc/pvr/timers/PVRTimerType.h | 64 +++++--- 6 files changed, 182 insertions(+), 166 deletions(-) create mode 100644 xbmc/pvr/settings/PVRIntSettingValues.cpp create mode 100644 xbmc/pvr/settings/PVRIntSettingValues.h diff --git a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp index 394f88646afac..69c5de5b60cff 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp @@ -1091,8 +1091,8 @@ void CGUIDialogPVRTimerSettings::DupEpisodesFiller(const SettingConstPtr& settin { list.clear(); - std::vector<std::pair<std::string, int>> values; - pThis->m_timerType->GetPreventDuplicateEpisodesValues(values); + const std::vector<SettingIntValue>& values{ + pThis->m_timerType->GetPreventDuplicateEpisodesValues()}; std::transform(values.cbegin(), values.cend(), std::back_inserter(list), [](const auto& value) { return IntegerSettingOption(value.first, value.second); }); @@ -1136,8 +1136,7 @@ void CGUIDialogPVRTimerSettings::PrioritiesFiller(const SettingConstPtr& setting { list.clear(); - std::vector<std::pair<std::string, int>> values; - pThis->m_timerType->GetPriorityValues(values); + const std::vector<SettingIntValue>& values{pThis->m_timerType->GetPriorityValues()}; std::transform(values.cbegin(), values.cend(), std::back_inserter(list), [](const auto& value) { return IntegerSettingOption(value.first, value.second); }); @@ -1173,8 +1172,7 @@ void CGUIDialogPVRTimerSettings::LifetimesFiller(const SettingConstPtr& setting, { list.clear(); - std::vector<std::pair<std::string, int>> values; - pThis->m_timerType->GetLifetimeValues(values); + const std::vector<SettingIntValue>& values{pThis->m_timerType->GetLifetimeValues()}; std::transform(values.cbegin(), values.cend(), std::back_inserter(list), [](const auto& value) { return IntegerSettingOption(value.first, value.second); }); @@ -1212,8 +1210,7 @@ void CGUIDialogPVRTimerSettings::MaxRecordingsFiller(const SettingConstPtr& sett { list.clear(); - std::vector<std::pair<std::string, int>> values; - pThis->m_timerType->GetMaxRecordingsValues(values); + const std::vector<SettingIntValue>& values{pThis->m_timerType->GetMaxRecordingsValues()}; std::transform(values.cbegin(), values.cend(), std::back_inserter(list), [](const auto& value) { return IntegerSettingOption(value.first, value.second); }); @@ -1249,8 +1246,7 @@ void CGUIDialogPVRTimerSettings::RecordingGroupFiller(const SettingConstPtr& set { list.clear(); - std::vector<std::pair<std::string, int>> values; - pThis->m_timerType->GetRecordingGroupValues(values); + const std::vector<SettingIntValue>& values{pThis->m_timerType->GetRecordingGroupValues()}; std::transform(values.cbegin(), values.cend(), std::back_inserter(list), [](const auto& value) { return IntegerSettingOption(value.first, value.second); }); diff --git a/xbmc/pvr/settings/CMakeLists.txt b/xbmc/pvr/settings/CMakeLists.txt index 6b32503a69e89..6697b11633516 100644 --- a/xbmc/pvr/settings/CMakeLists.txt +++ b/xbmc/pvr/settings/CMakeLists.txt @@ -1,5 +1,7 @@ -set(SOURCES PVRSettings.cpp) +set(SOURCES PVRIntSettingValues.cpp + PVRSettings.cpp) -set(HEADERS PVRSettings.h) +set(HEADERS PVRIntSettingValues.h + PVRSettings.h) core_add_library(pvr_settings) diff --git a/xbmc/pvr/settings/PVRIntSettingValues.cpp b/xbmc/pvr/settings/PVRIntSettingValues.cpp new file mode 100644 index 0000000000000..01ef45b9e42c0 --- /dev/null +++ b/xbmc/pvr/settings/PVRIntSettingValues.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 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 "PVRIntSettingValues.h" + +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h" +#include "guilib/LocalizeStrings.h" +#include "utils/StringUtils.h" + +#include <string> + +namespace PVR +{ +CPVRIntSettingValues::CPVRIntSettingValues(struct PVR_ATTRIBUTE_INT_VALUE* values, + unsigned int valuesSize, + int defaultValue, + int defaultDescriptionResourceId /* = 0 */) + : m_defaultValue(defaultValue) +{ + if (values && valuesSize > 0) + { + m_values.reserve(valuesSize); + for (unsigned int i = 0; i < valuesSize; ++i) + { + const int value{values[i].iValue}; + const char* desc{values[i].strDescription}; + std::string strDescr{desc ? desc : ""}; + if (strDescr.empty()) + { + // No description given by addon. Create one from value. + if (defaultDescriptionResourceId > 0) + strDescr = StringUtils::Format( + "{} {}", g_localizeStrings.Get(defaultDescriptionResourceId), value); + else + strDescr = std::to_string(value); + } + m_values.emplace_back(strDescr, value); + } + } +} + +CPVRIntSettingValues::CPVRIntSettingValues(struct PVR_ATTRIBUTE_INT_VALUE* values, + unsigned int valuesSize, + unsigned int defaultValue, + int defaultDescriptionResourceId /* = 0 */) + : CPVRIntSettingValues( + values, valuesSize, static_cast<int>(defaultValue), defaultDescriptionResourceId) +{ +} + +CPVRIntSettingValues::CPVRIntSettingValues(const std::vector<SettingIntValue>& values, + int defaultValue) + : m_values(values), m_defaultValue(defaultValue) +{ +} + +} // namespace PVR diff --git a/xbmc/pvr/settings/PVRIntSettingValues.h b/xbmc/pvr/settings/PVRIntSettingValues.h new file mode 100644 index 0000000000000..6299f819601b6 --- /dev/null +++ b/xbmc/pvr/settings/PVRIntSettingValues.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 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 <string> +#include <utility> +#include <vector> + +struct PVR_ATTRIBUTE_INT_VALUE; + +namespace PVR +{ +using SettingIntValue = std::pair<std::string, int>; + +class CPVRIntSettingValues +{ +public: + CPVRIntSettingValues(int defaultValue) : m_defaultValue(defaultValue) {} + CPVRIntSettingValues(struct PVR_ATTRIBUTE_INT_VALUE* values, + unsigned int valuesSize, + int defaultValue, + int defaultDescriptionResourceId = 0); + CPVRIntSettingValues(struct PVR_ATTRIBUTE_INT_VALUE* values, + unsigned int valuesSize, + unsigned int defaultValue, + int defaultDescriptionResourceId = 0); + CPVRIntSettingValues(const std::vector<SettingIntValue>& values, int defaultValue); + + virtual ~CPVRIntSettingValues() = default; + + bool operator==(const CPVRIntSettingValues& right) const + { + return (m_defaultValue == right.m_defaultValue && m_values == right.m_values); + } + + const std::vector<SettingIntValue>& GetValues() const { return m_values; } + int GetDefaultValue() const { return m_defaultValue; } + +private: + std::vector<SettingIntValue> m_values; + int m_defaultValue{0}; +}; +} // namespace PVR diff --git a/xbmc/pvr/timers/PVRTimerType.cpp b/xbmc/pvr/timers/PVRTimerType.cpp index af7d95a3c0ce0..232bb07f7a7e1 100644 --- a/xbmc/pvr/timers/PVRTimerType.cpp +++ b/xbmc/pvr/timers/PVRTimerType.cpp @@ -179,15 +179,10 @@ bool CPVRTimerType::operator==(const CPVRTimerType& right) const return (m_iClientId == right.m_iClientId && m_iTypeId == right.m_iTypeId && m_iAttributes == right.m_iAttributes && m_strDescription == right.m_strDescription && m_priorityValues == right.m_priorityValues && - m_iPriorityDefault == right.m_iPriorityDefault && m_lifetimeValues == right.m_lifetimeValues && - m_iLifetimeDefault == right.m_iLifetimeDefault && m_maxRecordingsValues == right.m_maxRecordingsValues && - m_iMaxRecordingsDefault == right.m_iMaxRecordingsDefault && m_preventDupEpisodesValues == right.m_preventDupEpisodesValues && - m_iPreventDupEpisodesDefault == right.m_iPreventDupEpisodesDefault && - m_recordingGroupValues == right.m_recordingGroupValues && - m_iRecordingGroupDefault == right.m_iRecordingGroupDefault); + m_recordingGroupValues == right.m_recordingGroupValues); } bool CPVRTimerType::operator!=(const CPVRTimerType& right) const @@ -202,15 +197,10 @@ void CPVRTimerType::Update(const CPVRTimerType& type) m_iAttributes = type.m_iAttributes; m_strDescription = type.m_strDescription; m_priorityValues = type.m_priorityValues; - m_iPriorityDefault = type.m_iPriorityDefault; m_lifetimeValues = type.m_lifetimeValues; - m_iLifetimeDefault = type.m_iLifetimeDefault; m_maxRecordingsValues = type.m_maxRecordingsValues; - m_iMaxRecordingsDefault = type.m_iMaxRecordingsDefault; m_preventDupEpisodesValues = type.m_preventDupEpisodesValues; - m_iPreventDupEpisodesDefault = type.m_iPreventDupEpisodesDefault; m_recordingGroupValues = type.m_recordingGroupValues; - m_iRecordingGroupDefault = type.m_iRecordingGroupDefault; } void CPVRTimerType::InitDescription() @@ -252,174 +242,77 @@ void CPVRTimerType::InitPriorityValues(const PVR_TIMER_TYPE& type) { if (type.iPrioritiesSize > 0) { - for (unsigned int i = 0; i < type.iPrioritiesSize; ++i) - { - const int value{type.priorities[i].iValue}; - const char* desc{type.priorities[i].strDescription}; - std::string strDescr{desc ? desc : ""}; - if (strDescr.empty()) - { - // No description given by addon. Create one from value. - strDescr = std::to_string(value); - } - m_priorityValues.emplace_back(strDescr, value); - } - - m_iPriorityDefault = type.iPrioritiesDefault; + m_priorityValues = {type.priorities, type.iPrioritiesSize, type.iPrioritiesDefault}; } else if (SupportsPriority()) { // No values given by addon, but priority supported. Use default values 1..100 + std::vector<SettingIntValue> values; for (int i = 1; i < 101; ++i) - m_priorityValues.emplace_back(std::to_string(i), i); + values.emplace_back(std::to_string(i), i); - m_iPriorityDefault = DEFAULT_RECORDING_PRIORITY; + m_priorityValues = {values, DEFAULT_RECORDING_PRIORITY}; } else { // No priority supported. - m_iPriorityDefault = DEFAULT_RECORDING_PRIORITY; + m_priorityValues = {DEFAULT_RECORDING_PRIORITY}; } } -void CPVRTimerType::GetPriorityValues(std::vector<std::pair<std::string, int>>& list) const -{ - std::copy(m_priorityValues.cbegin(), m_priorityValues.cend(), std::back_inserter(list)); -} - void CPVRTimerType::InitLifetimeValues(const PVR_TIMER_TYPE& type) { if (type.iLifetimesSize > 0) { - for (unsigned int i = 0; i < type.iLifetimesSize; ++i) - { - const int value{type.lifetimes[i].iValue}; - const char* desc{type.lifetimes[i].strDescription}; - std::string strDescr{desc ? desc : ""}; - if (strDescr.empty()) - { - // No description given by addon. Create one from value. - strDescr = std::to_string(value); - } - m_lifetimeValues.emplace_back(strDescr, value); - } - - m_iLifetimeDefault = type.iLifetimesDefault; + m_lifetimeValues = {type.lifetimes, type.iLifetimesSize, type.iLifetimesDefault}; } else if (SupportsLifetime()) { // No values given by addon, but lifetime supported. Use default values 1..365 + std::vector<SettingIntValue> values; for (int i = 1; i < 366; ++i) { - m_lifetimeValues.emplace_back(StringUtils::Format(g_localizeStrings.Get(17999), i), - i); // "{} days" + values.emplace_back(StringUtils::Format(g_localizeStrings.Get(17999), i), + i); // "{} days" } - m_iLifetimeDefault = DEFAULT_RECORDING_LIFETIME; + m_lifetimeValues = {values, DEFAULT_RECORDING_LIFETIME}; } else { // No lifetime supported. - m_iLifetimeDefault = DEFAULT_RECORDING_LIFETIME; + m_lifetimeValues = {DEFAULT_RECORDING_LIFETIME}; } } -void CPVRTimerType::GetLifetimeValues(std::vector<std::pair<std::string, int>>& list) const -{ - std::copy(m_lifetimeValues.cbegin(), m_lifetimeValues.cend(), std::back_inserter(list)); -} - void CPVRTimerType::InitMaxRecordingsValues(const PVR_TIMER_TYPE& type) { - if (type.iMaxRecordingsSize > 0) - { - for (unsigned int i = 0; i < type.iMaxRecordingsSize; ++i) - { - const int value{type.maxRecordings[i].iValue}; - const char* desc{type.maxRecordings[i].strDescription}; - std::string strDescr{desc ? desc : ""}; - if (strDescr.empty()) - { - // No description given by addon. Create one from value. - strDescr = std::to_string(value); - } - m_maxRecordingsValues.emplace_back(strDescr, value); - } - - m_iMaxRecordingsDefault = type.iMaxRecordingsDefault; - } -} - -void CPVRTimerType::GetMaxRecordingsValues(std::vector<std::pair<std::string, int>>& list) const -{ - std::copy(m_maxRecordingsValues.cbegin(), m_maxRecordingsValues.cend(), std::back_inserter(list)); + m_maxRecordingsValues = {type.maxRecordings, type.iMaxRecordingsSize, type.iMaxRecordingsDefault}; } void CPVRTimerType::InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& type) { if (type.iPreventDuplicateEpisodesSize > 0) { - for (unsigned int i = 0; i < type.iPreventDuplicateEpisodesSize; ++i) - { - const int value{type.preventDuplicateEpisodes[i].iValue}; - const char* desc{type.preventDuplicateEpisodes[i].strDescription}; - std::string strDescr{desc ? desc : ""}; - if (strDescr.empty()) - { - // No description given by addon. Create one from value. - strDescr = std::to_string(value); - } - m_preventDupEpisodesValues.emplace_back(strDescr, value); - } - - m_iPreventDupEpisodesDefault = type.iPreventDuplicateEpisodesDefault; + m_preventDupEpisodesValues = {type.preventDuplicateEpisodes, type.iPreventDuplicateEpisodesSize, + type.iPreventDuplicateEpisodesDefault}; } else if (SupportsRecordOnlyNewEpisodes()) { // No values given by addon, but prevent duplicate episodes supported. Use default values 0..1 - m_preventDupEpisodesValues.emplace_back(g_localizeStrings.Get(815), 0); // "Record all episodes" - m_preventDupEpisodesValues.emplace_back(g_localizeStrings.Get(816), - 1); // "Record only new episodes" - m_iPreventDupEpisodesDefault = DEFAULT_RECORDING_DUPLICATEHANDLING; + m_preventDupEpisodesValues = { + {{g_localizeStrings.Get(815) /* "Record all episodes" */, 0}, + {g_localizeStrings.Get(816) /* "Record only new episodes" */, 1}}, + DEFAULT_RECORDING_DUPLICATEHANDLING}; } else { // No prevent duplicate episodes supported. - m_iPreventDupEpisodesDefault = DEFAULT_RECORDING_DUPLICATEHANDLING; + m_preventDupEpisodesValues = {DEFAULT_RECORDING_DUPLICATEHANDLING}; } } -void CPVRTimerType::GetPreventDuplicateEpisodesValues( - std::vector<std::pair<std::string, int>>& list) const -{ - std::copy(m_preventDupEpisodesValues.cbegin(), m_preventDupEpisodesValues.cend(), - std::back_inserter(list)); -} - void CPVRTimerType::InitRecordingGroupValues(const PVR_TIMER_TYPE& type) { - if (type.iRecordingGroupSize > 0) - { - for (unsigned int i = 0; i < type.iRecordingGroupSize; ++i) - { - const int value{type.recordingGroup[i].iValue}; - const char* desc{type.recordingGroup[i].strDescription}; - std::string strDescr{desc ? desc : ""}; - if (strDescr.empty()) - { - // No description given by addon. Create one from value. - strDescr = StringUtils::Format("{} {}", - g_localizeStrings.Get(811), // Recording group - value); - } - m_recordingGroupValues.emplace_back(strDescr, value); - } - - m_iRecordingGroupDefault = type.iRecordingGroupDefault; - } -} - -void CPVRTimerType::GetRecordingGroupValues(std::vector<std::pair<std::string, int>>& list) const -{ - std::copy(m_recordingGroupValues.cbegin(), m_recordingGroupValues.cend(), - std::back_inserter(list)); + m_recordingGroupValues = {type.recordingGroup, type.iRecordingGroupSize, + type.iRecordingGroupDefault, 811 /* Recording group */}; } diff --git a/xbmc/pvr/timers/PVRTimerType.h b/xbmc/pvr/timers/PVRTimerType.h index 512fb77a87b22..c23fbcd373d5b 100644 --- a/xbmc/pvr/timers/PVRTimerType.h +++ b/xbmc/pvr/timers/PVRTimerType.h @@ -10,6 +10,7 @@ #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h" #include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID +#include "pvr/settings/PVRIntSettingValues.h" #include <memory> #include <string> @@ -384,63 +385,81 @@ class CPVRTimerType /*! * @brief Obtain a list with all possible values for the priority attribute. - * @param list out, the list with the values or an empty list, if priority is not supported by this type. + * @return the list with the values or an empty list, if priority is not supported by this type. */ - void GetPriorityValues(std::vector<std::pair<std::string, int>>& list) const; + const std::vector<SettingIntValue>& GetPriorityValues() const + { + return m_lifetimeValues.GetValues(); + } /*! * @brief Obtain the default value for the priority attribute. * @return the default value. */ - int GetPriorityDefault() const { return m_iPriorityDefault; } + int GetPriorityDefault() const { return m_priorityValues.GetDefaultValue(); } /*! * @brief Obtain a list with all possible values for the lifetime attribute. - * @param list out, the list with the values or an empty list, if lifetime is not supported by this type. + * @return the list with the values or an empty list, if lifetime is not supported by this type. */ - void GetLifetimeValues(std::vector<std::pair<std::string, int>>& list) const; + const std::vector<SettingIntValue>& GetLifetimeValues() const + { + return m_lifetimeValues.GetValues(); + } /*! * @brief Obtain the default value for the lifetime attribute. * @return the default value. */ - int GetLifetimeDefault() const { return m_iLifetimeDefault; } + int GetLifetimeDefault() const { return m_lifetimeValues.GetDefaultValue(); } /*! * @brief Obtain a list with all possible values for the MaxRecordings attribute. - * @param list out, the list with the values or an empty list, if MaxRecordings is not supported by this type. + * @return the list with the values or an empty list, if MaxRecordings is not supported by this type. */ - void GetMaxRecordingsValues(std::vector<std::pair<std::string, int>>& list) const; + const std::vector<SettingIntValue>& GetMaxRecordingsValues() const + { + return m_maxRecordingsValues.GetValues(); + } /*! * @brief Obtain the default value for the MaxRecordings attribute. * @return the default value. */ - int GetMaxRecordingsDefault() const { return m_iMaxRecordingsDefault; } + int GetMaxRecordingsDefault() const { return m_maxRecordingsValues.GetDefaultValue(); } /*! * @brief Obtain a list with all possible values for the duplicate episode prevention attribute. - * @param list out, the list with the values or an empty list, if duplicate episode prevention is not supported by this type. + * @return the list with the values or an empty list, if duplicate episode prevention is not supported by this type. */ - void GetPreventDuplicateEpisodesValues(std::vector<std::pair<std::string, int>>& list) const; + const std::vector<SettingIntValue>& GetPreventDuplicateEpisodesValues() const + { + return m_preventDupEpisodesValues.GetValues(); + } /*! * @brief Obtain the default value for the duplicate episode prevention attribute. * @return the default value. */ - int GetPreventDuplicateEpisodesDefault() const { return m_iPreventDupEpisodesDefault; } + int GetPreventDuplicateEpisodesDefault() const + { + return m_preventDupEpisodesValues.GetDefaultValue(); + } /*! * @brief Obtain a list with all possible values for the recording group attribute. - * @param list out, the list with the values or an empty list, if recording group is not supported by this type. + * @return the list with the values or an empty list, if recording group is not supported by this type. */ - void GetRecordingGroupValues(std::vector<std::pair<std::string, int>>& list) const; + const std::vector<SettingIntValue>& GetRecordingGroupValues() const + { + return m_recordingGroupValues.GetValues(); + } /*! * @brief Obtain the default value for the Recording Group attribute. * @return the default value. */ - int GetRecordingGroupDefault() const { return m_iRecordingGroupDefault; } + int GetRecordingGroupDefault() const { return m_recordingGroupValues.GetDefaultValue(); } private: void InitDescription(); @@ -455,15 +474,10 @@ class CPVRTimerType unsigned int m_iTypeId; uint64_t m_iAttributes; std::string m_strDescription; - std::vector<std::pair<std::string, int>> m_priorityValues; - int m_iPriorityDefault = DEFAULT_RECORDING_PRIORITY; - std::vector<std::pair<std::string, int>> m_lifetimeValues; - int m_iLifetimeDefault = DEFAULT_RECORDING_LIFETIME; - std::vector<std::pair<std::string, int>> m_maxRecordingsValues; - int m_iMaxRecordingsDefault = 0; - std::vector<std::pair<std::string, int>> m_preventDupEpisodesValues; - unsigned int m_iPreventDupEpisodesDefault = DEFAULT_RECORDING_DUPLICATEHANDLING; - std::vector<std::pair<std::string, int>> m_recordingGroupValues; - unsigned int m_iRecordingGroupDefault = 0; + CPVRIntSettingValues m_priorityValues{DEFAULT_RECORDING_PRIORITY}; + CPVRIntSettingValues m_lifetimeValues{DEFAULT_RECORDING_LIFETIME}; + CPVRIntSettingValues m_maxRecordingsValues{0}; + CPVRIntSettingValues m_preventDupEpisodesValues{DEFAULT_RECORDING_DUPLICATEHANDLING}; + CPVRIntSettingValues m_recordingGroupValues{0}; }; } // namespace PVR From 8a5a3c6442bcecc346a3c1ed396c100af5399100 Mon Sep 17 00:00:00 2001 From: acidzab <samuele.maoloni@gmail.com> Date: Tue, 27 Aug 2024 22:35:38 +0200 Subject: [PATCH 418/651] [JSON] AudioLibrary: implemented RefreshArtist and RefreshAlbum --- xbmc/interfaces/json-rpc/AudioLibrary.cpp | 56 +++++++++++++++++++++++ xbmc/interfaces/json-rpc/AudioLibrary.h | 10 ++++ 2 files changed, 66 insertions(+) diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.cpp b/xbmc/interfaces/json-rpc/AudioLibrary.cpp index ab08cdf833515..bed043b42167a 100644 --- a/xbmc/interfaces/json-rpc/AudioLibrary.cpp +++ b/xbmc/interfaces/json-rpc/AudioLibrary.cpp @@ -32,6 +32,8 @@ #include <memory> +#include <music/MusicLibraryQueue.h> + using namespace MUSIC_INFO; using namespace JSONRPC; using namespace XFILE; @@ -1354,6 +1356,60 @@ JSONRPC_STATUS CAudioLibrary::GetAdditionalSongDetails(const CVariant& parameter return OK; } +JSONRPC_STATUS CAudioLibrary::RefreshArtist(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + int artistID = (int)parameterObject["artistid"].asInteger(); + + CMusicDbUrl musicUrl; + if (!musicUrl.FromString("musicdb://artists/")) + return InternalError; + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + //checking if artistID is a valid one + if (!musicdatabase.GetArtistExists(artistID)) + return InvalidParams; + + //set the artist id on the musicdb url + musicUrl.AddOption("artistid", artistID); + + //executing the StartArtistScan for refreshing the artist scraped informations + CMusicLibraryQueue::GetInstance().StartArtistScan(musicUrl.ToString(), true); + + return ACK; +} + +JSONRPC_STATUS CAudioLibrary::RefreshAlbum(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + int albumID = (int)parameterObject["albumid"].asInteger(); + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + //check if albumID is a valid one + CAlbum album; + if (!musicdatabase.GetAlbum(albumID, album, false)) + return InvalidParams; + + std::string path = StringUtils::Format("musicdb://albums/{}/", albumID); + + //execute the album refresh job + CMusicLibraryQueue::GetInstance().StartAlbumScan(path, true); + + return ACK; +} + bool CAudioLibrary::CheckForAdditionalProperties(const CVariant &properties, const std::set<std::string> &checkProperties, std::set<std::string> &foundProperties) { if (!properties.isArray() || properties.empty()) diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.h b/xbmc/interfaces/json-rpc/AudioLibrary.h index 9946f9d998177..674bb0a0d3fb1 100644 --- a/xbmc/interfaces/json-rpc/AudioLibrary.h +++ b/xbmc/interfaces/json-rpc/AudioLibrary.h @@ -69,6 +69,16 @@ namespace JSONRPC static JSONRPC_STATUS GetAdditionalSongDetails(const CVariant& parameterObject, const CFileItemList& items, CMusicDatabase& musicdatabase); + static JSONRPC_STATUS RefreshArtist(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + static JSONRPC_STATUS RefreshAlbum(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); private: static void FillAlbumItem(const CAlbum& album, From ac9799a61b74e5bbbb8be8dab6cab4a066fc5027 Mon Sep 17 00:00:00 2001 From: acidzab <samuele.maoloni@gmail.com> Date: Tue, 27 Aug 2024 23:25:09 +0200 Subject: [PATCH 419/651] [JSON] AudioLibrary: bumped api version and updated schema --- .../json-rpc/JSONServiceDescription.cpp | 2 ++ xbmc/interfaces/json-rpc/schema/methods.json | 28 +++++++++++++++++++ xbmc/interfaces/json-rpc/schema/version.txt | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp index 0ce49b9898378..a5b918f64f786 100644 --- a/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp +++ b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp @@ -119,6 +119,8 @@ JsonRpcMethodMap CJSONServiceDescription::m_methodMaps[] = { { "AudioLibrary.SetArtistDetails", CAudioLibrary::SetArtistDetails }, { "AudioLibrary.SetAlbumDetails", CAudioLibrary::SetAlbumDetails }, { "AudioLibrary.SetSongDetails", CAudioLibrary::SetSongDetails }, + { "AudioLibrary.RefreshArtist", CAudioLibrary::RefreshArtist }, + { "AudioLibrary.RefreshAlbum", CAudioLibrary::RefreshAlbum }, { "AudioLibrary.Scan", CAudioLibrary::Scan }, { "AudioLibrary.Export", CAudioLibrary::Export }, { "AudioLibrary.Clean", CAudioLibrary::Clean }, diff --git a/xbmc/interfaces/json-rpc/schema/methods.json b/xbmc/interfaces/json-rpc/schema/methods.json index 19be352bb1105..aa70a2879f9ca 100644 --- a/xbmc/interfaces/json-rpc/schema/methods.json +++ b/xbmc/interfaces/json-rpc/schema/methods.json @@ -3063,6 +3063,34 @@ ], "returns": "string" }, + "AudioLibrary.RefreshArtist": { + "type": "method", + "description": "Refresh the given artist in the library", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { + "name": "artistid", + "$ref": "Library.Id", + "required": true + } + ], + "returns": "string" + }, + "AudioLibrary.RefreshAlbum": { + "type": "method", + "description": "Refresh the given album in the library", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { + "name": "albumid", + "$ref": "Library.Id", + "required": true + } + ], + "returns": "string" + }, "AudioLibrary.Scan": { "type": "method", "description": "Scans the audio sources for new library items", diff --git a/xbmc/interfaces/json-rpc/schema/version.txt b/xbmc/interfaces/json-rpc/schema/version.txt index 4727db30924e8..eecf12c86d0c0 100644 --- a/xbmc/interfaces/json-rpc/schema/version.txt +++ b/xbmc/interfaces/json-rpc/schema/version.txt @@ -1 +1 @@ -JSONRPC_VERSION 13.6.0 +JSONRPC_VERSION 13.7.0 From 53382a962063abbc5c35e1ba37af50cf24b5d329 Mon Sep 17 00:00:00 2001 From: sarbes <sarbes@kodi.tv> Date: Wed, 28 Aug 2024 22:54:26 +0200 Subject: [PATCH 420/651] Estuary: optimize pattern compression --- .../extras/backgrounds/pattern1.png | Bin 26325 -> 20983 bytes .../extras/backgrounds/pattern2.png | Bin 37924 -> 37679 bytes .../extras/backgrounds/pattern3.png | Bin 86557 -> 34567 bytes .../extras/backgrounds/pattern4.png | Bin 22602 -> 19332 bytes .../extras/backgrounds/pattern5.png | Bin 88539 -> 88485 bytes .../extras/backgrounds/pattern6.png | Bin 79518 -> 76217 bytes .../extras/backgrounds/pattern7.png | Bin 19791 -> 19599 bytes 7 files changed, 0 insertions(+), 0 deletions(-) diff --git a/addons/skin.estuary/extras/backgrounds/pattern1.png b/addons/skin.estuary/extras/backgrounds/pattern1.png index 5acfff911e267ce6e18eba80e631ee2400ccb386..fb17dae43cb54e332a7e361f08398b8ab25faa8d 100644 GIT binary patch literal 20983 zcmWJscR1949RJ?H9fx!FzO%QpS2$;ntn7JaN*Se+#N9btTqJvhiXtMVfjhe>BT40# zqhZU+9>4zm{P%gD_w#<uce<m!B|EDSD*yoO)>dZD001Te04NOh?*W+g<h=y|P94@} zCN8mGS8jd@l(fwb$KHtZx8FHDd7-eHq!a>$6SD^HEDjABW-6)vdej_{3z&>0gunL+ ze_yV;GS|6=Jv1<!9M1gQ@?&mqxI5bDTH~$8q0aM%6ywg(gXz!RdnbEUtrPouvvVik zy>?EFPHuF>8@;JGJax)T-2Zx3q~Y}6xK=~Iik5@nlTGZAu=oe#g}swC>~ZS{{*M|b zHX9RPZ)_$$Io=QLJU#tkC({=w=j6D-=U3#HHE-~AXS1YKF8;?Kc4JBZJL?DfK~cm3 zHgk>yShDP)m{|RfvqHvyg>MJ~XSe>Y%Z8C>4!->t31O(FRvukV5Dy1V`M)s<R7p4; zeX$FZo&0;`bN1#nfrM#p3M_E~1C?ES@3g?4Z^^xOgXxW60_Vw6@r~%JwY@;fjL6|W z6JS4mYzX*q1|D4nUY$COTaVZ4=>(-;O`%u!wq2E=rke3Xqgl1xl_=;*X|N|OYxlC; z@+}s+T28mr)IJQC+I0Y=-5%+rS{{@E&-g&G37h?Q<_A0Bu9aU69$<Ilcn^!$xo0KT z*o~|GsXN!kC7WJMTxVte<Y(8n9$?dVSs?zXT#C~napVs_NyjAW;LK#?1QZAY&D+x8 zs=JuqGY|^>+Ok2PgAWEH1$IWMTi^=F;%caUp{Q)?e+3CD2nh~w%bF<ghDbBOPO1V? z=&m%DOw`DFPFg$s{2z3hNAtlcUZJdTT2XQjOh5yhM$+9Sac0;B!;&s<-(Oe#NnocP zm@<5$qxSM@fOy^nu>`q;I55Jc0P~lO)iquF>uHIw5CTgwhLnq78lnFDhiH(S-A_>q zVR<m*Pzn%b73J`b6gOcQB7OFiAV3j9caw@du>e93e}q{i7gqLEDnuzkED;K#%$7JY zuqSH*NHxJx^13d7<s_?d%IfX*Ngdi^1%LtF0jV@VkU1AQZ2Xl=EtdLq{qRs9ER-}Y zocQNY>o6S$&YB&B6p)`Fv(DQofs-W014TWQ!2FMCt}g#Uy2c)k0yQE~Z4;goV!#^B zE9)nn8|<*<IdZldaRkXyb3h3e6FGLwny<o?K@U+F2tkcynnL>%Fmn7KPEzGOu^bBF zTXq!aIZGvTG2cYpwILLfeaGo~v1IX3QJ-7ZpTfrA>n2e!O1NHI3Qg4PKPZ0hIu=la zPV1uy^jf(3&l$DF>nO4WnkJ5>d!=2)L|HIk#t*>u^1JchVB;Dn3zN`n(lhR=44N~C z3OI_>LA;;AP2^_5XbQZ=*7E9D(gm1P&(7R3q1@X&)6PJGN>WL7wA=A(XPs_|U2f|C zz9i3t{y?|Z_}KzGjM2UexTrr<g(iXMZ<UVg{=C;y@5){Cxqn`RY<pFVG?u&uNJ$wb z1)|p^UG9fffuAJfm<o@ddPVGTp9Vz2tlq-7h3S{|*<&PYg1B8gG=H2rMAQ;5lf;8* zVSQlNX~92zJX5VYgJ0UcX5Z$f2!LpYP(R$*MS)VI{G%K9I3=>MH4yTMMe`pM5xB~c zsVQU0y1$*F@fX96ygDr=w`5Vo+}}x-wY441VBybQV=*M^R7HV^XE3_8mnHWr+ZBHP zS47!8o2Pf>H7vfL%@gb_PM&Mm7i@WgeMh?|n`b*pW(k*_V7XX(R02Rx>?cj-D3eK_ zD&S|`yrAWplU_>`WahB4Z%%d0Nk7U5`>gWM0f`EbIcH7oz*QW>oaZkmKz-@-5V0&A zOiOc!S3R@dA2bueWwqh~*{8DVwvs}i%Qn^JM|JF;fhxZk^4vkjG1I`?KcHj|qB`>> zV<T}0ytTqyq<9_%vE+jhl^{W)LbsB3l};IRHpX4+f`+^!i|!U8Jl@mOIqxNknI8oK zcZ{Gapg@jNh?>n9oywTQ7mANSu-3FnCe&+jPQ=i^;er=EWih}o8StG;lCm#s+-us` z0z#$HL~$zca_2g3YDPUeO15;JTQ&e;QcBXXp`}KWhvWs+xs;imExYa{h1*otgH<tW zpGj*>v=Mlp610`*gs9nQDQbmKgXXE<mBA+PwEo*o%t=;hb5eAb6-r^e{+XP<8IgKZ zbPuiICiX}ju!P}7v3Wqi=bbbuvv9Q(Ypd^wRS>zFLj!{pa5DL|1B(~9n*7qR6HKc@ zwgkOBdBypl5ga9E(*=mwT_1<#ZRX50)Zh@`<L^QFyrYv*QGMu?z&WYjmx211LvCpl z$Ec@knidK_uEHfwvwSQ7%^wm2#5PMQo2PZ#V3-+kO1jAVN<{amIs{2Hg?5jNUzkoi z?y`cZFj*r?p9$16J^BUfd`I0kr^hj)ORX!e;AmtJ6mk;#(Bp3U`*${##jNW=^cLaP zw-;d^*KU)joUHC5@H&(SjPy&zCvuu4pb@}kF@3a$X?8(b>v#Lj@EpBnR5gFiB;{f* zeS7*7XqGY(r&}&iZ3i7EzLdncf^|$8mT$1)nq%Xj0T<kmu}p4snv`8@VI2%%-KNWp zFU6jL&ntUarI-XztD-`*;h#xtHbXjPu!^)QSzyz`=T$DO=+7MTTInSMK>`mSKS&RQ zN=X*ogKj-#Pr*%a!=F>Kn9guC8{FmT=<Mt_ifROqFcJ->@fOSSxiYWKQi3r`)!J|8 zrtpx4Wes7ZLJG}u$OF>hr)w>wY3FE}eIebGJ8=7u*!j%@yX(4LVF415QMX(sxx$EG z2l?GOQS5HAP_8`b90x~4y>+^b0)wXTz3~!CqR{HU+m3_u8ws@S{F@>#h-+}1HrK;f z=m_!(g$ldrFdMzALy(x|3%M5>^;|fm<emxCPW{iGQ_f32Gp)+Abnfl(;IUBHVgALo z@MQ{s-NrF&v|m92<e9i-B=T{e5ZM}-SJ2Xs_M*f#7I7p?_o5HPNXUDPOSr3<f9kP9 zOlL-xf6$$5;AiL|Jzc2Vzx&-r;Y|n8wY{}K>G86!wW0xw(1%>erI8EV{qKy8;x^39 z*^)57>Zmb>zjVGu9f5(wj$4&bpQu35)9Hz4W|b{*#w<CRI3+VSMf}JK5xf~6o7yvT z?<J4Or?BU>X*!gh2l$BZc3bl0P7t&T$ktv-6Dr&MM^Q+uO7SrCbBt|eQ#Y|lSDUK& z*Q9WECw|F*)7XmzKQ!F<4@3hIr9xfzrAe(uRwp-IVvpQ_jC?a|?w?_)t=#8TZ4v3o z(N`EX%qhOVe9|8*T|dY1eW-KfUZb+whs-ELXhNLa(GT7bfY8h>BiNT^1ns$bQz@8h zEs7WY*UUJ2;3_vpBg;QyQCQq`;6}*Lw05iHXpDz|gTHXrHxt6aK5D#!181Jp%8Lyr zw=D|;>Guntin7FErgHyS53rFsm@g=T)}TL1>)K65IANQ<b&r?yo?M&l5MjL`NbKP1 z2Xqhl?Z+5GQF43b@@+k4T~D+F1~O7y%<*@)m+IF&rnv3eg46E}ZfI!WBbDHdS7$p` z_m%{KGRd!>NS9@QCievSWQt>SwN22=Detg_yj23q2+vXq6q>!X{+Mem|2B)nn^6v~ zY^Xxd_<L9hB-%ej?j|QORt>FH<z*db)FxH^EUdFE2@@63dmW`D!T7Q;ue)*DMPPzB zl6Svm{(_}23*)`QmivXrz7R*P*(Q!O6-)qAXiZldMPyU}=2MrXm>4YwkN8!X{L}N> zjky4xo%wp7kLP0ouu@vT+zq1r`SM?ww83ce=B~bN*6Rjf36d`x(eZRL8WCu~$fEHw zZ`gG`61q_4M)F9h14Y3}FHR>dpKP?pA3|_)Jv{BTP2guHo0pyb47`?b%-r4;rln4_ z=m{dHEH^CXtw}{(J7cxtbKa>>)M~-$vV+sNl7fnEv@;B@ZGJBe<<`KL!nuU9zNS)N zayqgaC7|`yZdVLznnu;0)JODZXcVEr&8wDaH#AJ__oICfc6V@Bu`0sv4hw}(lF$eM ziVXG!ak?{KLV>HwS(705=>O@?^e>K3g@iG|0-|d<daI`~x3j~@SJQTar2!YcLdiAT zYcs1;`j^4TS5ERk4s(PAh~<2x7$~b!aan@!=1cw{R`e%9ofGk3*opObGhs5gqe;=# zkNvv<Z11aU&Fji?Fm?GS>>@*(Khfc#E9^3yTd-L->bK7KjFw&YN9MS<IOOc_7lv#m z<J8S|YOrQR1AqLd>=W}&I|tDGS^V0~8bT6A$MeeGPFGNuS*diW9HiHAzzZr8p+wQf zA`<ezn`wCx1aGHk&4~NFJM8SgAxF<U*m390se^=PyYXq|X-<}Q9;3`h#8M_p#jYe_ zCrQ>DFA+oA!PX8j`usi?jBDrmkA1H~adq8m)jn9nD-U<Jq-bx|uj3`=xR?CQ^+_aT zNs@n}3(K7cCyLe4ZM^QTAmfGZS9!5p2;zCwQAZZB)4EP)A!zx%+1CV=wrPseJ+r)` zngUi`)!$d7C^d|o$64uSsExuldzb1K(Tn+RQ~YzI?nZ+O%oRZCA09BUe}9+VUn;me z^33lm<39@RlEw>iRJ3ZikdKMMl9sRpZr1MC-|;ceTJi1*luA@!Gcp!(wCmX426XLQ zUq=uFMS^nyZl*Z<H|JQ5)>5(s6BWyloq{88u|eUcOq~qt`GMOb>h20f?`!l5F>4&K zgb$lOkVi+?!oP%SkEI5wXDud*$Z{wbK5J%zZ>HFMfrC<6i&%AyGH*`TG2daIVY!b< zKtD1U!xz-32yS$iTwoJ-`;sjU8t{&`%(Snkk=u8{U5w3!v!8W+K5nTaKG0IYl{E41 z*69H{GC_3~sjt}yzSmp1oxPnubiTdjm-n*p@__<SEsh0aOJ#b*1RRJEj%YECrMMzl z<LNBoXNg_EHN6bDlWqqa&T;v%5`snb+3eb-jc+>O?~hx8^%;q(wOCo^bnb8hJ^L3& z02YnP3&^JF7O$#2&=5q{_ZD|Es!w0vMkE9YAl&FT0U14A7t)f%5DV^0<jsnsYm3Qm zpM&)U`&tOjkUs3LSM?+3rhflu87V3wim$9L@ywd!YS@MJqPnOHeaX@!Tg5nc%iA4c zINuwe+W|8Jr2VIm_^WdMc407`k<7=r$f#<zbJ%Q1PE$CigqiVxi5|lW*-iK4c!F6o z{p@1Q(FFz+6}e(LRdSI%HnobMcHiTh?y3Ljx&3~G7PZ(#uut|c(p|PO7YTmBD|?0Z zdZ{Bb&Q@N08ibpHWPRB&`&rLnKbTpH>OM6ZqsO&Eff`iC1kPa7Fts59Z*ddaH8KiA zskpW;VQ<ysK(M>jWBF$9Sy_ih0|gK($ZHXAhm+fo1J}$p+hK8-iTc+)XWOJt4Prn2 zg|*h{2AJ)3iM?AYdT~~LNF7LG)wPKoNzpemEG^uN-(R-Lzgk${%xKFC@=NLMM#m=- z=wa3q*j%Mh{KmZ+CdHxtBCAfeBzfSrxKPo(hX!aru#^<^{X3F5$v*9F30I|b#go+4 z?&ilYMu!!#XwJ<AWUysaxU&w>riR>M*WDKWeh4Or*y%)CnYS|piN*m@eq=3Lb{=(< zM{WXo_tYN4=6y}qsbE>CQ%=7kb8gC731{cM+U?7f_J+Uq1HI>{ob;#u*O!cdQBQ^r zkk7m__LLt|Vf!CSQ07W^+wSbP#3`Y&l%Ox4jfGRSGOMF6c5*w<(kglx7JuF8XeLJR zTYhgnnmx{#S;V7hxMQ=icsvXP?Gl-i1WU5eU%;!8qWYLvyviamnMo~Q<V`Qhw(x0Y zt?xi{yG(amL{kYo9e{f4RdKBaOWZ$IYX49tQ7PCYCBT&a!wxp$1@PQQ$%Sf21B3W2 z{&$_W+jFi64!}$nKkrvHijXVwc#16b>SEEd_~_;{1}7zq>mvDO)J5SlN*?n>zqF38 z8H4>MM&;KI4AC|C01n)In%Fs*cv@z_>e?^yLgW)Fb;Ctuio+^~riNhU%PQN8(w%G= zP-vZX)|mJ#I8eUr9RH0M;Akgcvwrq;XfS4eV+r&&sM%vK4@Hij1ZzB*lVlPNuH=Ud zpYrkGVIO90e7)Yu+?BuINT#a;pRNmW;G_<c<3?sa<xyw6oD=G8zk=E^{w)kp#!Fen z`u7fA(Sar*aFRc0N_o~6Jme3ep}S*O5juahzsX?i(^hwlM0C0wMUf5ikCwx13(Rh? zHcuxXB&;f*eze62j+!)6fv*9!giMRaRY`)Fr$O?3Qzf0YkPEeR)>sg6rxrUMg5Ei& zx|O62>|V}Af{-uQL4b7<M<IAL<@p62mVa#0t+XJZl4CwOOj}528~?y>?G*3lPYrEF zDY4M+{kY{1_EFP=eg4d7RMo@iE6UxKhI?<X)`wXow-YKM5&(G6$Pf90Q|7degxA!W zj;gNZU0_9#)ovO_`3%gn*Lee_`Aac+UD*hYr_{CrIV;86tQx?Fqc)A>0AS(<{T0{Y z%Fi3=SGG#52Hq4x{u?M=_DF}y0lMG9yIx8~q_48+qrbl!&9jFw5v*vN<=^Rf(*5eD zutQ%=ROjr{XR9>f^w%uJ5k67vzxD=S<5gAWiQPIG&D5qce(w!EVD5GRGtH<hDXiV- z&Ym)B;Lg>VeHrFNPiQ^3Nyd!Z^t>Q3`ow~X7@!VsfQ?5Kwx-v(a%bJtJKFw*T1(Uf z=w#Xlk<mB2JBlE=5ZbKfl<J30_`n!0mIc0N%z|4S$W5S8DTXX{aZZhaBTvlQ%O*en zS)m*@+R!T?=uOCmceFm#PYGE00rv=?@^ko8141pk3V>Z+)CLHc;PUup`1codihq`| zc{&~Q_cC4+V8k{DxTdJ_053uchrv-%-6Qr^zwk4uAJ@pB&MX6YGk1_&96i}>s-F_J zWNu7wO6$Pkp8&{QMh*MMg65RFS{o8J_jE|Lh|3K$nc$=t5fym7<h;=xO-R&Fx)!g6 z?)rfaYp3$|Xn~q1Gou`HCE@oJb}*Fk^&XY8n2WUx$wYHW&DJX%A_t!x_(Kbw9;CGw zCK!Ox<*=8`v?0#4yMGgC*~y^7$)_6&Tf!hKJmWM|<$vOb8S769VrWf*CJwy~o*Pdk z<3_1*0SX+#&I_U1px3ZZS9yu?TE7gL2!hw2e@cGLu;(QOYFS=UYPlqN+3xi<&yB~A z_1dJ3Un5zD_L4AHpy*fJxe#mvVC((%8ujcqr^tJ2ipg|gU=qL1+^4Q#Yj;HsJ{|r( zIjpVfjpiK}kM}bU-^YQpArA_apAC0_6|J-A?4V}3-{nqz56h=-EffuQ90#+D3@Uwn zD#iBXR)~HseL@muo4`esgQ>KdL=4iO+ne+ZgA>00QuTu};_LDoe&b%R+m>+giH;&k zR7w+ydJV6Btmer7rbYye^|2eaAYWs{y*5T{|5|gm?!RPv{6-w>KP*$gUAH_q{9qc7 zzw3V4>ey*2*B?X(5Q}I9KWji-_%vM|nQFZ8G+BfFZ9cW`a>_(YujuGJ0pf43xx{#z z?dNXk>upcjz(z^Ae9){1Q5_&`@spyFet*bH(9RD-ur<fi7cDjaqq2U`B+_GIGz;~r zbi!$hOJArG|FG)u4f>ul<%S~eAce<+#pfYLQ$%qJO1R7`(!p_Q+YGUqqE{TCihvWJ zaZ8-1z2rZ|_O?TEZjPKRI#{+?1@zc*O}yD1ocwl+yf<~7Z(%Gw4#4U0H9wc0Hb4>9 zx|hEE<{1K4b`~ZiOh;)5d`}PW&d0|VJh=3L{$A2>tU-j7?DGf+uj!6J5dRiCJb5n0 z>2?s5%$9n-S%dwDh8$?te&9G|Jp0sWkZED#@5;GzWPS@okKx3NOD+nypJ&e>Otw5$ z5fEL9!{{z%E&#;$ZmQCIuGrm((!Ddj?$^vRgQ8#Y<HzTpSdvwrBy+J2h5XrMCplV; zppvWw;y)7+#As`yDNeZ<+&v43IQeU?zs<k3QCvD0qt8ft$ap6~kK7$F*hc*qXgWZ6 zUOg-&Hh**ZiaSIFvi61tMF~^wZZaPo;PUO@y=KXJ6%qKI`Y1{Ki|x<D6fGbF>EU|s zBl4Oav)g>6|Mg4HXOA`hngHxi{OpWJ+8KBOk?-&RaSGFDzD~_DpGQUf!J|o^jxXrv z14oy}%U2&LohSMV;~Jp7r<lFG8^GMFT;*Go3^c%ps7n%`@z7v;BCaShg&ohrHvBFO z%n%zIe=$o`svYi0Hb^!Hz><s2KoFbb8&xnXVnfHSjLJi#vqMz0(#(HZ%>5`tXaLvo zN33v+KHB*6z74%cuu$Y(jC9D-uG#61+%RquYy9&Ih4Onyo4@|Uk8uY%#@zUwpTp%! zPat5_7)OV8#TC4b6lFDqYtHLqc(Ul!!`1Tw_EV3_K?3}ciLBb?Ay+-nw^m?4i>AM8 zCAtg2Y4eNZ3&g8><RRl93vF=nJFUmPTdaG6`ASiU>fj%``^z$C==aktD6tN<Qv1W^ zZ$T>VwFT*h5)HHM!TMXt41_hK@3a)9+r8yWSN+b;ZEYnDmVr?&TYo@7q!(CTNfihN z5cc{(atgGNqh1BMHyuwd8Lp`zzb1N3WdW?Xad;7;F5jCSE123##4)>YVgE2$YTzB* zehp#;PHEn1=>HrVriO6lCD;TEKY^+99-Jd<y-#HUHX^W5P;K!OUAAbuptOVy8~!Zf zlMsOveB6BU&~C|Qq~n?xgrCH(`8opP@LHY?_{kI83MdhWpWGqcSZ|blc^#-+gP@Fu zH2J#hLnX9?Y6vfQWHO1H9x`6f4ePKUJxa5PTwx~L!m=i7x7*NhFBAZ7I5vljj4OC+ zm7i!e{#17Wr2EMS4OcwFh-xjaVMNJr+#y{S4?gB@XkiF|B#BLi)29Pwe;_!BoR={6 z2ZUWGs`S>EI2pgB8NZ)P3QWV%Os%u=I=ahYlIL`_<3|rF)=>*1ri+|sh0Ihv=CM;b z#CWw0yv$f-qCMYT1)3RH=|kt2jTUp{qJRfk7=lS7#%N#O^U1B0d<92IR7(D`NiMR+ zj)G<~l#nZ1`m`dOf}Ce6^-ySI^g8D$Md?6k8J>1`)J0!j^0xTdx5IX$JpYK+wicu0 zz+S2+dp*e!76UVj5HKwKE`r$gv5IUr)@KrVW0$A=+ZW0xW?>8QfOof>`2?Zo&C8S2 zfMj{0523FV{$(A{w5q<+EZN%Id{L<9m;Wg^fkvOJPppXiasy;g-+Ueq`LRL9Y{ioo zh$wxIY#_!2c@^)Y+QXibCUv3GK;8M*WkpLRkb`j#4@g!6ycq;P>|GdA8~XL%3*_O* zEk?W~%W!8tI0}CAYNn`(1}9+jm$V%jUL}}%Ve~dr(qVdDl-OaR;2H}l(!g!XVc`<~ z^Nn{`bh52J9<6H_Z-kW<0ZA;F@`v{T3NHZz^GY#!>`Hz?&q>1OQWG6g#*3w!1lN8g z{pS{DlV?e5<M~~vCEyHxu=E8BKPQ)CRIIr5{Sn*mj_*wKWgZA(p0IJ+i0%F$$9~3Y z{W!%n>n5x;Qgn?SSDKWs_!mLpN-jls3xVe>pMXECmAG$>(Frxs2|OVmsQ7%ll3nq8 zh(|t<+S&Zxf%ROQ2;}16^|eE9kDcKeP7fBqCEVFN20C_iKv$fM)M5M7$WNv`yRLUv zT~aC==YXe$ee+>=-p_=sRiD<I90q@k-pYWc?Iqo?_G`~gc?>T!H{3oH1eF8;xuO@@ zsYuRF?VQ3+=XKD1!4=9VHztG05{j3OGlV|xKlQQb1xXMfab;Q)HP6{hj$SAgennxG zLt&G>=A>%&M23{r1_V|N;m$eR`7=mJD=Yi<X4lc3_mQ%J55h`GiCM7s?7sL|j!sq= zA}!su2L1PACo0DJ)DXHY!+2v-Lw#&{`XUFZPSRx6Mq1uOIw&~l-aom(+vmcZ?OjzZ zis6Zwy!`3l@rkejjh@E&m4+g^vIcs>>WNWW@M<sYJ<$#JO+^8s6N{Z&4`Z)Q3LzFy zjnI+EVhCT3deK9D=d;)F5n-1k_Dfvs=7O`0Zl=7Op4+2NeN@2~?HS0O{YLEH&LiQs z#28%<k3a%zf}+x^?UKZRn7}frG#|KHhy}6a-Iqr;v$|=2IA4%9WvoJzJWGt#g+L7` z<`53PABAuJj_#c)&cFbohce>0pO_2Qsb?i<AX96r5yH63tNWl4WrZAhU4lbXF3mV) z<IiX?w-gg01NYWYjz<rsV@kk)w-JTLs^5Vj84bw(NZS7BBRi=ct5Q;txcI?66IFrw zbPnMR87t6FI&3nAd$-=;qm0#QP?l#NIg*_u3@kT<{Pj!e(F4m=(=S+$zCNS2fecrB zDv<<spfmAeWii#rkBT-~n-9V7a|D1H-?lm^&&PQ!e)9VM@VMk7RyE!L<AFNRWA~~G zn_eXGuVMZ(@v{wl$6vV;xpc>pS9ZHN1+1+uucV*zd{p85hkR$m7t3+O39R(ZF%OF9 zD!w1)VpsF@Zs>1(1LHK|--v6LaD)iFc#xJ(a>~R+tEL$LHoqd21tU@@r8}>RBTF#I zt{A(foiHbGjqUn0crOrC8^b?HwfaS~QKVQCg=s-t|2SB%pxkLSPnc9vAw8YxEP19# z$O-aF)bq*3Z}$zN#M*1Xni+`{QY`SB6@6}lmGRUrtH_FuD&wNEqo02-J_>yVGvhbK zBZi&{oa4E`5`s4N)ZI22uydvc(85A%KgA=Z+WiDh^nF=sKXV-cNHn(m<7xFKCy_wx z6Q>zJaAo`eHmvh>T#9SI#+82_#nK({H+uK<8^LKE@+kSz59!M1k}&0z*^$P;aC*u+ zA7cLC{N@X<x?u^hmz#isL@w}@?|2~GF>Mc2`I8>(t(ip{DYxOi6r@argb`J=AJ^%6 zLGj8|ySn~I*z`ma(9-ZPqQ;k#F#C&GpS}t(L1^#Q6X?ndIfw<|_6=i_^F-<gbN$Fb zrcfDn;tuk3CN8;CPUFV{=fe{W_r#cbVe)=XN_#%qBeGBac#v9<Lfdrz7U`Ib!4JKu zOnPQY)pKQhAZ&>sCx2R%x*igBp?z2KtESk<|KizL?(-cv&1jRn4<#W2L#;)WRHV)` z7)b*1QBD`qG=_-&=3J*d6E-@_QWdb<#JBBSWi(|*{s^0l$1S$W9)_j;#-~i(y~=eC z2;?M7{}nf>K%{EI`1qdvVnt+=B;-DDA?FD3&#A-xbaltMfNA`>CiX1dC=8gHZ?+_Q zy!flf(u-h|!UP=^lSVxtWcB^`+-K%)Hnb7w0_*kN{2ke+(_Y=#G@o01E|U#kew8Mr z7*8>$q{N5663nu}Wf{M?aO&su+n#9#+Bx$g;~9%{6cD)3yKV_L`Xu~e3M&WN5rQ!> z`zuXe!_(eNooBoA0r&It+AG90dST=nB=1O=>SW>{1D<Mn7V68*ldv%M+a-9^ujyts zp)t;I&0mW;41u;LxkO09p=e|02^`m#WXktp9JdDhlddF72<|KH$Zn@5xpLL2tA!t< zAmmfF9)ak>gfys9K-(GUU0d4dk%^=@MsmAxAu>phYo@QhdImOsXTk9x1U`8Vo5_V- zolDy-vHVGxf4rXaHl-Mhv>HiEh($b@Z?P+u*QS^sc7M6@T;l$2__nH?nfrZQs^OpC zl6Ez}4!aNkm|8_eSk&^8s@0m*`YX)D&yg#u5mz{~M*Nn+&}jCsUW<PN=DPbH`7H4| z?}P8z+y*y<yZBitPWl(31fVhmZm=7R^{!>Qpk-chpZJr5S8tdlWJU|T&>4qu+W}jy z_gEjOQ9Yf4K$AZ<5t(P9BCdta(lR@}ngoqr`9c{+!7R*Wp6VFG>Q-%iQUA$WnBczS ziRmr6v|*bW(Y}?R?Nl2s^K*=VBj9`tBFJIFPP*_he~EFkwqXiCUTGc3a7VR6d0<lJ zjQFXf(Em6dsBQvQH=;yH@DnVYo@RF19EIGTP(L13Lm04URHHVT33fComhi}De8Fj} z_}$d^$+9NsRSHhfLM@;k7Wer`67SGZ<s;S&g;HWUaBUj-x8dA%6dW&Ly}EKAJL#1O zrf$!6#ryCvaXvFFCaS;_&kSC->BjU2n_{>He3)f+H@R&CK$_HwP2W3UqWQ52n~@O5 z85s$+0SF;7e`;`4cbwS7Uoe7<!cX>m)y_84X13JC)q-blG<fPnuqNg7@tB03A~M$> zD(#~{PwzT$ZrG{i)(GQGCi9p@Ono0-nH2>6a6n07F8NEJ8@$)X-{sGJKw#KY?iO<) z;p=_#SaAv5+<kt2yE^CqD2+>(|18s@6GYM-tf)`uO+fnnEYbx#>Y`{XLws%6)XIQS z2iuWNT^wZTdP5;uo_*cg1H@GhiIC=WyTWR=?k_p5u#wToTe5eW{Elxo5Z?Y3fPYje zGu_N0o|q+?$}u$$YTbvWN%OxojvxK9`!puJ2PL|JF0?-nPTU+<tr835@(vK}NUC%K z=b9O3cRN0<=OrnDRiMU{L6eldv#>vIS9;st@72Oe`8+lTWCf!xywhb4^kfx_iw@9- zKZ~(;3q|_)r1)cKb~lT{uef8GT4I%0OlO?ah+{-1ZY4|1eDXJA;icaDA6#MwUWFh< z`m!}o2ISJDQ3<L7n@+lrdMS<$(k$nq>RWTh9TIUKz*pG%1jNKuq{i$9DeJ1D#j%(Q z`L<D{2TygngkdbV+^_#m(^enu9W_Jbzy7gIds3ew=_89=estpGO5lSvIr~}-INql6 zsJhMfm@sFZ@0iRy>)H0jSa`L!AsIJh9%G)?RRK!bOc-66yXTph)Z|SKc-1<Q_&jJQ zqTM^OL#Tc)2tLPqS01B-rfF(HvLe9zO2nrpY`dxQ%e_?H;~uXYH`n8C7~0FK3=Jn6 zoy<>oFAZBR&26tQ`~^i$+*CC}udlEiJ{&1<Bcs3gM|3@;vJoeod8xu*Ud7z+f))1Q zb#HP+s12B7O974Ja9r)JO_G~3bHLt(5Z+haPlZpE#*LK(CynVj#rbydLiPhaF4Q{q z6y%|`E6x`pVd|Dk!Ti?WNAn(E<q??e;JoRa{?ZldEH)N!`OM$xSE_M1;Zu)B9e_SB z$^B;9icq$(rKNqFXLp}*pYeRUZnaiSWE!)n5Go7mvSfPmg`ANp2mQ(`Vz(7D9|)}J zB#H2ZK5NyETE37KmBb3z(c5nUJg$V_gW&TBihmGH>JTEuAXsRXlT0*Jvg)r)@RHI! z7luEi_JS*SaFS=_CPG8T2REV~FewN=c=2G$y-bP`%guUdrw=rD$~S=X&Bf0kCa<&z zSe@gR8m7&Je@gVQcDT_cxEQA>MHmD536nI;jcin1gML!{b?$d>rAX5vo~^O9NoG&Y zoosVjwPk5=*=k!ZIXf<@sDt{Sfi<jD6-Z6U@mjWl8=IUuWcgIwd2m+5E9)Lo*ua+( zWh&JLKy)RZIXR+I6U}<x4uAq8GH2Y)@M6C|K<*N|k3?*Kg)wtX0){h)SdZ)SYdH#l z^^S7m@rWs+z|OcSUdK}PEMcv{-Lvg)=t(7S>KM4U68pDSVqDZEKHVZLJWTJ=TLdCO z?h4AjIRYwfHoY$s&%H11>>KqE2T@bM&5i<pQ9a#xioer(z_|EAa*Z8yi3AR^@VjUY zw(z%Z_k7Cjki~MZvKv`=`=nWZs2N-qGp)PP<fS;jX2Y?nS*Ka%_r<nya2Tjh#yN1> zIVfm-gk6Y&o%6FwQV`m1UBa^}jgu<T7sldvk5tuxP;kuhP=Sf%y@BF+CWrHpcfi$+ zDt+ZJ(l+O@X+cTgcgOlP@|`$J+X4z}MQ0LB`6TOm0TEmj`}_mXJL6VZvG0t!N}6(& z{erqKi+3-(=trB}8;YL0a9p6CqHqhA+v)OUwEFW5=CB4wh>ei?%8>YT*sUL3w@{qZ zv3bAzLBs)2*hNwtIzNkbJkyn+VEQ9MHc3oBZ;A!@*9~Lx&rhe_>VU<4{3o1==Oia< zZ(5!1*Sd4&hJf$G_ndFYlHW}A3U3twzT7>5A=yS&Q2Lk$?1jnFn1gxuQ{Ej;>GsdX zUDR~V!5BfnrIfhfhdW~+{mO5Ybho!Q#_nZgYODQkV7VDI>(yDvhG%$sF`ajJlV;L@ z>-Uyf(Z6n943R<69%dQ_7#DuVAPsF5Yj;z5&1e={MjN@$jkODC*roPe4s1Y`2fXVO z%FXGX#$V3bC+Drpj>_i$*xll@gW6||BPT~h^p($Ls3U6cJk`qY#~s>yPx#h^OHz=# z1*|_3AMDRagQ}W2I7hLZegIyq#1j(5Pk3bFNefz6LhnSp`OWt7bD~AD?Eq(_?0ZHo z%2a%<UwUP_m!nDRKg9ioyO#LxX8v;E5PoP{ylD4ru9?upIi|EUvJtN=pR&Np)N8Ag z)3qZHE{f!)=yF}R>jsnG-E1r%<gZ)uyI||jDWz;A7jM24eek#<Omrp6Q6$lE<<HwK ze<O5PK_PdA+mDymqAD}UUYg{54(<+x4p`Pm2}$s|fAoPyJ4cZ)GHTUYhY3k=mgQ0i zs&@hN)pSBhL5iz??t|(NS-Ad2!4;W%AOnf$rCN)or>frWkH30I>-L=zmnz#D<9h#? znZ|l7ad%`jxKKBS>5NrZ`vPlIe?@p2e)4|&LxMz?dyRl&leslB#_`;+4f(m|^(`BN zHfOesp26*tzrD8gY`fg8oN*84qh>PYE@2lF!zQh(wxN|G;AdkWo`+s2GBU435N|!7 zaXXI-8{#}PNy3(k-P!eyBKXq%fB8?C=ROoZZ(pb)h&`86t%Ly8W-+e&x0$bjXuUf@ zY<KGpZ||kWvfd@5uRHbmtAQHl{2i}VS9nfIlJFB`WSJ`we5LGKMgrh5i}}<Mbvc_a z3%A93ix{k0iEO$wF~KEj(Zsgi7-;++QprOImqD)m&0W-N462vv!a+I^2|w8qj(5P7 z%~kRWfxCsbOnI*jRj#J~jK1^)sq>>}SjnoJBZ}jiVcq%FzUi$~@%QM%h7K4(&VVvx zM`{`@vCQQQ75;5{M9S_9+lMl^Mhy%hypnK7KCMBDqf9fZxvc8@+?qQUO&5=``7i96 zii6asF8u*9c@<C6JsWV~9U_?iNik0u+V%VriSBx)KM*G6=^pjw^;$ohXr&%{nmM2O ze(^ucFz#t(U&`QvN4NvwlfZ%7(0i~xj%b3Ag^*kZXwzcNW^tepX6hSxZ8m4i_~lGv zZm8EINhPg2DBdP$hoc_LAoWPOjHP~wbj*V7qv1{q72}#SGE|}U9et*S){a#UlbkB7 zSh@bRlG_R>8_P&%h%>L_nt#=ptbE)3hn2ZSg?c)bZ)fy2%s0CjxW9vwvljxajfns0 zPTrRM$p=^AB8?|{iPN(lUUEuEKX@5Em%jeP`=?UCnybILUh6)zQs3hC<Lc7v5%ICz zd?|KnMhWU=z=)Lla6;^y`1IbAQ26rTZj$lb@~M>F^^o)AOp4j*ft&eR<Vsce`RLij z>vr^8<y8%80n+^+HR(2dgu{z}+KB>*m@6>{U&Kef{`7TTZQwsouYde9Ayr70FdCV< z19wI$=*_&Wk8fw@X*qs%cl!!sX@&_GjjD8H=Hp6Bw7Z{C;Yz%|C>NXdPROl*n}c7( z<M!-cNHFTM2>F?$r3$;iG{PfNJ|LQHD(4@%_Rx|~4SAWf76h=G#jZDKkG#sksbFg1 zsZ59KP$g}kW|upr?W^-wwdY!lGu=ubN&=S~WtL`xm6d9`tUtoAA*Uc0VmF`K)1KY3 zkAG7~espU`c=scuaM|+ZWT5O|D_fu6<M}-;`AoQnXgqwS5yzr*POVCIj3AxWSv3V* z+{<{)E?&Y)UX~v@j-XfA(Bd|Dv(;UEF0wtZdg3au%L~4E+7SP7xv?SY?&vd#YKx%e zOF5{8&)o;t5>zH5LL0nWjyGQ)ND4Q8_V;eB;A}jR9XM1%P)fGA{Z-43tz>a7K<QnE z(;}&_@jttO`GEuRdD(|IZt5j=4BR}{H!8W*?s+2-^!n&<#b0sy>>YZ_?{V)8+4$wg zi@c<l4i%DAYnAkfM`b@{t}XWX=D)Kpzj~^2uROKWUR$jon{swnvPEay>+NgI{RO<W z3HhbZvg)>cq0-qd*LT|B66{5fkap`<PE-uir+dVinsc@8KwK!1m3W3tMbF0EvYqu@ z?9DcdtK*}*KD+=)EY^Z1Ldqmp7M6OlB?K0;`My}>m6oZP4-~LVQ9T&rsDdYje}?sl ziCH=~b-e5IR-Vqd?~T@7Z#K_>0DI(Dfr5pCJU*!fBl$i_o(}ZbhQLp*A1M4^kI=i_ zY?b$qOMUJuC{G5YGKU$oJzL~fWR7YAaa``@seebCs_@Dr<$qzCIB9R1RC(I+NoFs3 zi`?zsxPtrit)eHu`Ay2xb>;qeT+JP_D>H|8&K{A!7QCYBW=1fomw$0L7}5|^4fT&i ztlB=}&N0eSiqs0Ij?7o2)j?dP{Eb{|9qi<<xzWATg`+QZRB-T(dB?p!CacKa<<L@* zz$X1Xw%|4#Q{@lmzm`dF4R3EKHEGms`cPqWhp70?xTHp6p<Ukgq4(b>4%!S(x3BF9 zX)CIg`XEfxOx{2<%EwcR-YuR%e4M$m<>G$vVbz<fPL2Sb=?U+V`G3;NzORU~mMs!Z znWrn(=4pD}#=|Ws=-3AYm+}F1s=utH8n$6~{O(SCyEdxopRR23y{lyOy-R!r_-LS% zzX|!W(R<<J88YrUk84(Iin2lM!)5vILngifXLh?BbGm?>Oh|L`)hT#e+-z$3GvC+G zn!hL7>PLOQ(Rzu<By!4Bw!qv{K<3W74B0B(rBu(f>tO1$q`5z@^b6k<X8h9c)jtkq zeh~PqV^y-{PkbnDrZEpK3Y`!BugvZZBC!bx_2U2UvZed7l8WcgUdBS&F`IR#3p*4x z6l~J&<u;M9*S_awyne<|&D9{LuO+eotO+Mx=A8ViGY)_Dt~X>f`?Uw;ZlL&ACGGo! zly;e6?OeRRfv97R*rQ|bbYfKJ^DaWd`DfkZ_8GW5fO06=2`pr@ryZ7-`99A;NM^6* zyIstD{XG&0h53vGDSU>;fG1cH7kg$-SvAzXRcn7@ubj7B0w><fCVeb;wjaK~U;V}) z-~UaeI-+7OkPGY6ZLa@U1k{M+%Bt`ZP?~b}G>*Qjx~nwxiiu@=;@4cVs7U@kXZgO{ z3vtUl#4G*QJ~D_2bSozcxV285@NO}#wl(BOPA<+3HA_}MWvWIsnIpF1UsXsrBBw9y zC>POPWG=<egHt+YSDZRGHkF#>!J8#Hfvrlf>z6&(DoRGBveMJWY7}dvZpt|kEKJa% zR=H8jA8w-u-mBhpCEjKy_<$EbPV<^L4q`W!8@6UtMZJpd;|?xcg*`y*Ur180r?JgY zo2O5+=FaAbh!7fUf3USH>jKFT=BY|qvMohSx@Yrf&kP={HgX2}rXyQKJm0zU=w$=O zv2^2kvA3=J%}-!Qa~dAT<DOXg`(d-{g#Td$7NDLZ9$%vQY>GCzi)>{t^AwlnD7FPG zo-Od6W3e*j3QLTDlAeblFU<P9FcT&DEs{O6h#Il4RA4U``zNN^j%}|I?){gsP9dru zpIYR|=`3RZxYCl#tbr2o!S4QE5jhMEvv0u|1NG=ACn^^hBRMDtk@@>B4yu%5!qE36 z`Co?%HvlF_mSP-tD%yQJN3v|sU>}_vN_)dPqZY2((H&Kit#wT+Lv%VGGlJlgUaqMN zRibz@`cBEcEoZWS47#@NJOs4H{@MZ^i?!1(qr83a&MYNBHnRPDKc`B+#J}eHYh<D3 z0&4!AjBOvzu1YmDAg%Vh8C}-9)0ZFg091$~pg<bPa_hD(3cqvIr4?=baGC${^;i}Y zW?X}%tQs|%Rdg%uO`9+f4a<tI&*UphOK6Wi9vpUTgpD-LslCn?=s|BObLmt;mtogc z1>SS!9<wBi%Yml>3Wj<mnfq}<$Drh1&e>@%puT`4A;>aeI~=(Vr|#ylDZ|!gn^XMF z{M6td{a`PGtYy*8$G{evvmDqx4ds)lD5SJZoIlkTiRCJeJ-4NZq=xp2V%z>4q*NvO z;wrkQP4ciNXnVR31$0=zjIU)bmomFcx&f=F=$n43Zpr*{@GeFA1#{znDYW}!g8bg2 z`GOm3biT5eEG)@$0t<{Am8iT0tT_s=DbSaw26k8+i)%41YJRdJNxgUaO@^_kD}hxX zJ(<@#>y>c|dHC+1>GYWbk;xcFFTMxeX5@@e;bg1K)m#eVJyHtSf3_C$Ic{lN*3ltE zLxP+x1ykZlklV50{Tj@ahHYUs2|QX-mPGfQF0vvTF^RwLRx$N&V%vw+TBXgb=tqaS z<`YqAkrIben8fp$Fc&o{EvL@_SK9uI?oN8gmel1edmc^WPp-lFM}~`1hmw#7ooNvi zgp!oWizYd#nad6eF?aJUrt0x*v^s&w1i*@`ErN@On$a2{y%WkrFv_P?iCfi!uRPug z3Sk4c5cF6PqdTk#E5M!})a1`Qyk6lV(Sy0P7%jhw1+O@)h(XVnAUJQqT0OR93<C63 zDo%%!6!>L_B>yDxJRGqGF4V4b;inCtak{9(dlGAB-S-5m=WJ$gzN=P5A&NT`Ve#O# zY$Xw{La_3!o7P9}`2ilrW)`nLkY%(wic7&CQW&}?X}!~+a=P6iYn4f+g_(e>dKasy zR-q2el`r8h{vY+uAXUn{;|bCt3NXH6v`^|xAR6Lhe@g$LJr|?v1AH(fyf?=EG^IgJ zu(zu&3qGi!BUD8W$$D4N7y;6SfYfY~W=)-T<4F1XA3xUsz<2(-RL{KPC}#jE`i9&O zj>0pb@x!338$Y}~krbOO$u6hV0Im^1=bLNYYE72;U+)u0G_sSiCHhXG`<QAqO;HN! zaw&6dso^3&5&2KxCONNxT2y1j$b>+<PwyFe;0$e_`c1J_Jrz(YtSuRrcDKr=;VH{w zd!(=a#p-Wc($D-XD?gvRv@xnI4!$h|on{I+;uPN+L+gGC90?oSWOZ$sc>kH${$=t} zyt0g?FTrt@bV12#paEv*<l*86JMy*eYM<c71WV@m>3@`HK;RU>ALJ+5R90Umx@*>b zIMV(3_H3L{69mj8!>nJJVF-3WHIr~}^g-!w1-~nNad#!kjH%wk=>3lK4ixW#bWcPz zNsGv^(HxoftJbV~=4+uSm@_Nh&V&1_m;{im76m389`glNR!N@PzdzGxZvthLIYn`c z6@8?g`M2Wa5!DdukqDRn7d(Q@F1+6#4XlxMm89xB8G_BxrSW?j06&+UBMkilxv#me zz(aX3m}ZpRqqS`!V8wyMo*3LX0e|4|he@19en`E;t`?+@mMkJsC~Z5$W(FGiRoq(p z+bvQrx;Fak1%*n6shnB>&yP6UK>x^J`#B(L`iSZN#qUv{Jl<%ugj(7S(}Xnc&w$A4 zP&Zc^7lo4-b~f_3h5F3+xvB(p<G)J>1vj2%U3P)vg;wQu@(n82m9&D;vGCNLV*BwV zGuoVi6-GB)O@-yhmw)v*1r!-%xv@T<`qcP&jwJFI;iCQsALSBk$QkP}a3@~rQ(`Pa z_dBS_uC^AErM?PnT{w#Tkj3|c{xQ0UL*UB1E}S1sSSq`d^ur_V=yt1`G`P;SqQ?1$ z=F2SO_PhUht;E+Wf7!HIaWGs!H`$_7gHKE($NoW4E6oi?8sMDwqB|#-<I5j`hmVM0 zq9RO+XF}oSb<(lTaR5~6CI-Pb`uXiJamvgOM3^{P)7#PYhbf1aa;!@QTc?07>fRxX zgRcKLm^KSeBT6-XLKZ#^;4tQ8zP4sL>*D{mXE`XC=h+*$Itcl?$)1P2-S4p0(q-(I zIfIGHr_39{U_`GBJxRt{q(@dt>(}A0`fPm+Pf&m_@o5cQ0;HRPjbIe881FD1(MLE0 zCqka0Mrtr$-$Twot5~L};igcZw_WFN;_rHJ|2rwTMU)|q<=J2z-n4t2<m=q4QX_ak zP&`j4G^d5=a@<0HRRm52F9}T{N6q(^0rkB&ixhr%y3)ZcEGonJOhcPg)j7oZy__OP z+ay{PB6ba6K31brO;b$jp&v903pXxn3>}D!*^yoM?{hV208jd9qJDOxvgBzn3m<PO z2Ssb;x@Qtcf7|bpkD7;H_KnjO*@T+IWTD~%2>Hp)oYZ=V|4BCBQwQNU6HM3o_~~Z{ z^JFe;qD80XorZ0vOA<%ke0dTLgKP=C`pk2&rMvixgFsAgfQTQeyNJwu28H07ANym< zlQfs80{%{>N$hKS1+4u8YXw?2@!gC^?nxWi3P{tQ5ih5hz5aA3BJ&CXYw<dKCd|Z* z<!ZeXsgD&wDP>D*7!J>fhfaOy;)ULAS#1YVnKa6C^!D{FJM{Z1K?ASQw7A7%y#y~h zOKWIv3ar$2R(|qg_%(xjCLGy_e`ib&1@9^M(`u0Qp+8UUMzbw~56zU_DSr1)+`jkF zFV+&67iz@*{h<2GkTGirK|@DmE)QFcp^|lF!LpcjuDIsnqXMfiPsAG*Qt|$I;b$v& zhhH<T`<!i#@}P*jS|(kv-;bXgEU3bgxH+GzU9-ek2sS$lhDJb>)Y?RnKIDa*+#f3I z6(}@a3tVpbITr*<dd255vb1$g8*f5pqq-4m;S!%ZB~_tCiX*rB;Ls{z>o9Syf9^|` z`cPu47BnzSE~J=~caZbfFMkOzUk^>>64O!gbf7PY5L4xKX5!;D)^f>8ZsxP5L-%$h z+b~#FyNmfm&IjfW>@(Y0#D+V`A!|WCQkjXpANsX8B*=tm=yWkO4X|>bVOeQx&os8w zQF;UF!`b7T-K_fC2_#&WffLk7vQp%!N0kIBK4W#v;SvkeXxn1Z`F3ScZugo1@iH4! z*gg*{u;SGsn<-B!k?MI&tUf#p%S~(JsSiK63H@zXZDsge-s-{qJM_<ck*|K@8P!%( zOt7dFtATP9>_vB5do!IqNjorkwO{gfpgpQt48kj{;`l>i)eP*%Np1(@8x9t>UK?dB zT#Fu3p>5~Irq<5Q@D~m|Hpbs1P4Ozn(V0;7=W*|n9>UrLApETh`n#}$Rtj<AhrfB@ zmoficc_&t!PDXGK_#%#^ms^_kjV^ecbqV>WpYzB3)5D<av9(}XAOS`y$?*a-;b{rV z`~`!?g&>BQ$0aH#M<Qe!Vrj_3O|r53!qF{*C-O(dqVggvH!&C{)w2Obs!JgO_K&!a z1H#uGf*R&JORq5|<9b*DLpCE9c7bk2v~$SvJFuqIb9^mAV}#noyEYmIP9T_NcgAHA zORg})klYlR!=Owi*|5iioXj)xmGjzwHIK*)f2GG(@XRQ|2Pz?C$DHf9xkCp_)PZ(_ z2-0bv;zaLUwc7e(b7*^eg8yUWX^PR3%5{!~u&0ONg^44xk<TOOdogWwBFeh!S7~Z2 zH(>VH`R@JUN~Y;CxpP@15&V#TRvq4~Ltf;S*#b{jaPZvQP{7`AJjt$WhGVTX*415L znC116$iHAL92aT6rogDaG9i3D5ye7tKsUWRBe!jK6664{?`<#tJH5!uXuG4ZYKpu_ zmGKg96RK&!n5A<lWNF1H=Be~$Xcrb`nu&Ui6NUc|vJp-02;7HYt?9PvLR|%7Z*3z3 zCoMQ~lgk0n9Yl{YP}YDv40`u8Rs!Q_dZ`ogLSTfT<R8%>J@<n1AM$b#EWNrUJ6r9? z<6;P0QD3+OC>K&j0M4Bd*#w!Ob_TyG47x%QrC^G`)Q%N56LAvqm}8;W1DOo7iNInI zvz#+8w14m6!2)+Z`Lf_mFuMQ@yNH4Z1_nPC26-pMup2><5QV=++rS=-10*LU8v*d< zG+aXB9s#yX$SXrnekq^`4Ucl=0CbypUk_tkJD=k)c>b!q8CTzoAV~;F!9(za?GPrz zRKktb5m<v@sqWhffOZq?3I(9@!MJH)K8$J2a2IY$!tI#Tv48)Qx(vqXxT%eWK;MfK zB!qhb0}7QC-Ruvxk3b=o*p=Yeii<exa{67X{FDGHW<mA=IE<jXSck)4pu%HlWphbO zGa+4c;yMm~6D;RV=B;)6L%RimiBTqvg5~nh@;_`DVD)!dxd{wK=p6te7oZ~{;~S_a z>tHO{>(GlScu2zlev*kyGa-K{ep9X7A+gP<m|^b@8nTDRtOW-uz)b)iN`#@=*VTyg zSQ6hAtx_<#)k?1;IrzQNhqS?&g#3YiGq;1FwOuIg#6P<TytN!`sR*qTFaV&x2$Hse z=j;nQ#M3Y*JJ*m2fxQ*YFro&;RCp#Ke<)`*Ys*0}ho0(t+{1sjA=hoK30zu8bOC@t z0>qY>DQ&A(2)q?iuwwxkW}?9F7KqEfNQgcGxQxJBC8%OsDf8M5;jfOno~K?e0T#}3 zWdOuua1Ld@`~!J7PSphxoZ}CvfWQxZ+OeDt2*V&EA)iIy67sC2<ku<)Sq*oK_1}gF zJuIbM2jE=(J)gxqoX^G$Nto1t?$*HQ$&bqc=j$Ld{9-To;V0vM+%SJw;+NMTxEk~} zO;|Uj;Bo~RhcQ9?D*`a?Hkb(RL15>_z^Ne&ug0}VgHt<}(*eOq`Xg3>)Rj80;Cm5R zih@<uIUhu_kHfBvKve;z0Gt=14owi?fB^uU$#Q{&Eb4%KJ1=nKKbRK7Sn!I*w!lSn zs9S*EtrTPxm{yVi^)Sd40Q!b6F8@Y=4i9q-oU~}RpDoh?1qpHR>o^Q@0N#ec3&2~5 ze%8L0g>6xUo;MPe-1q^2p%v!<$kgbG1s`cIc!h+t>C%y#NvBfu*${p-D#NTKxg`W% zi+vXf$c;JJ6atYOddkB005IVnfQc{`Og01}64H4u4%(qXEAD}((-8oRWmW}(8+a=w z;G0wMy%eN=99;u2w1FuAfjeuR1&3xly3$aXNCL2nuGIDbI^YEaZbiWqf)`uBcLYUC zG-x>scQ*h}E#?Klf(}RlIQI!RIw6T05XmqPuq8DDa3>8H+H?yti`9SM3Ml)Hkq=uc z&@jkn0C+OqOo)cPV9*Sn0-z-v1WaXyEbN_FUI%<^0&pP>=eSyV`n$-$wm?C2BbY8_ zs{+tfezYMR0C;><h9Np&$jG7-l74@041$HEo6`^bwGb3e=wAasFGRn?;5qiW1VFqV zFec$O0E$k?#UQvC1uKkWsrymDW8ciXNdYJhbIlUqJ^;@MR|nwHMgYoNHQEoDm=T~5 z{a~)tD*(I!fy;#B*XCbKZ7{$M4Of%@PX=IFh)w}GEk{rQ+>C*>y08@QZ0iT&(;yU9 zd~_T5lK|vqgmGKyB><9<kgEWEnYJ_l?+Aj|Ah_Uww(nukXdf<AfNPtf|JMSr_dNh^ zBCw_qR;#`Z3yfVT18oO^xfQUq8~=Bhf`2gpKLEkiD)Cn`VAB+YuH+z_@pAx%+vrzW z@b9Dsya2#^5LhG_wiEg6ix{j^5Uj$4^EAg-0q7?)cUdrzVeYR1*V_LJ8ezHW+w#BI z2SJz^u~Iv}ypQAo7@RHA3V20`X3L3obin&q@Wmi_M_T?`_O**$5Vn{+0ub(HTL54? z^h^j?%>iy^-aMENcVk`D0e6z$O%SY+U;9GNUFw2F84!#k6#y8VSaAtp$&7#jIK|*q z05U(!CpsY?fnXhjx3@qpBe6t5$ay3IKwkoSD`3eFxMjz>JQs`|S<#G;_JWW68@P?Y zg(z5veBUhqZ4ZIOj{5G$g)`L<fMhXNW=KT<oX!Sken12G%l&}Rj<o>5{qO8L1Q$_f zAB35$eTW)I&j2{=rN0Egf+fG;h@P6$ZP^jMY)Exb^meSH^v$TtAU8wsD*}wIFc5fn zmN~Hk0GD=CzEM~kN~+C-5KI2VusYCT=9!S)!E^$^k0S6&ZCGwl+=Ihzdu_M~K)jcT z0dP|6#Bx+Og&C*f%;Z#@+l`P+#nJVE)3NHpdN#S5<EVZ!KMBEmx*s3P%~s>UP=YA{ zFl=*H*O8LJbiACX*a_Ly0mE(>N`<H89L)jnP6Xai9A2yc>{bX`-UyDZIIcv`Iy662 zjZP8HVj$X3L^mTGO<3{~0xO4-lEL&?{>8D(<2uW<&A@0IxV;5ow)!y(DvVkS<BqiI z6Cr3jmfQ`)gy?2qOFkS*T4lj(GPAfDq4oY`!R-@4u!JVtO6d1xT-)oyfwfrNjK!}6 z$3wyNMvYpIs;A;^oD25N5Fx|JR@}T9;kc#Fs^&|(?St+t{0ebaA~0PG!wso#M*;>Q zor)W>uhIdh+apc}Ot<7m62eMAuood+PJ{vYaRe68wO;_*tvEE*2WAumEk&ppQpY0= zcre{8+l+9w@Az^erbE}K;?!;!q60=D`ll6l+{HD&0l_7F*&P_P%!!Z!5DW!tLu$BF z*do>CJ_N>sCla!HEpy&Z$+zUs8&c`LF5d}R{KhR7W!5eL?)C!EmOms}$bl9E(q8aX z1!_Yo>4enw1DZP6Di>5fU-lV<tP`?!sw>d}KNbWp)Q9yp#l1L`PSjcm%mJv}Fhv~@ zv)~E|nf8=3M}BQZuUu1JSW<p$eP^#n!NoeyZqK&$KX7RvxCFqe8>X<L7w)Evy<ju! zh1vE41_aJ1#MhD3xz=OvJGz=1uS4*Ic9^UrSg^vtUhur4d=Y?^8#S7QJYP1a!|c0Z z%9oPDtGNtwN8jr*1aHW?b^``&*$PuIBcyH^wrU(-%k=MhZPeF?Y9<}<CkrDpLeUMA z?&kp80nZ)L$M*MdU7Wiu>Dn!McAEmjwQNv_c84;PUa-QSV!>)lK4=7Q9js=K)M-g7 z-q99FNWy|Y7X(+6uHBq&mveY3!e||u_JT1F_Cj>f0mZeNrjHfEOG!3UclXyH9nrOW z!HN1;XTi7BTy{fnto$kV%D(DvmpNfdy<lcWh^;uiqF3i(kHU7Ki|I~=(JXjjGQE+z zaPzBG7Q8Q-eFTD^Vjq=1NX3gq+<+A`!Y~|+x8ze_KJ!7xV}<NmX336KIap0tNaquo zISc+M0zX&w*)1T<H5i?PwHYDrgfwo)av@Kh3ZB%(upV`-69NrcKOmG4jApD>CLwn~ zaHG7tPw=z-4^;c))^+Hl6_*Inh6c}x0t3fEVhZ*`V3vZ38z%SxsTLED1^;w{a{zt} zf!kG|-I8bP1h~8vCOK6dnjy#9y<l*$Fq{guQv#fgFgqz23ejf>r0Wql_If+*2o(}? z4+Qt3oqZ?Uig#O01q>Xxu%EY49Z+6O_qGF~z|0W#onVlH(_@8cFAn$PX8sX{=qdm| zh`?V!J?g(<&5E^QMxaAUNQQ}8Opu=|Bf(OLR(`;6DJi62r35zQ#F{=u1%T@o_Pg?} zwXcFu52I@lrqBz9osekB*DRQ>LU@1BONK#>kdT6FGa*QdMy9|=INR0nk)%2axs8L` zeaZ1#1XyY?^h6ig&&yhIkp~M$0=36D8OBVAFxrkqNeJr&^J_H?>wv>hXDb8n77%<I zgSWKkqz03(MWAK`KbY=?=#w)L7VJ!aP@d*%VZc<dR$&_64=Aq1Q3%GxSc^5_cOZCg z&1d@`Y3nLrYDQq4kiy0E0u_EUk@s8LJd-K#=T#WPB;+0aKma5xnDpT08gK!CyMo5O z5{q3;vMcmAx)8)_Kyo6ae5%XC&t3jCqPxH*h?+^?A|aCw2tCmeYP1b3hmt;Dj9pgw ztlbSlE5yKhBP835(2x)vWf(FT5@Ba70s9o(fE%W;o(++ZY)fiGF4PHubSUWyQSg_L zjwRe%l7!dXF!)l^Y{`!_WSZ^}R2XzZ=0M8VB2>)?6o1WoVKD0myD7J7z%L^3rr+fP z3#v{87dIn>Ua$*(Z3<Y2?#GQaAV4AC%7NT~p3CcIFe4#Z{F|1-jD3)G0N#g=+j<>$ z_2#UEz^WSt_kw{C?c<+zMF;A^^RG7pCM38R0SO7Ir{Fyu@DrNIo#oywb)i-IAWJ#8 z20+&g!Rrx7J7kWF!3sEQJ!>*dC<4K94%d!rqVjt*=Y?6*0e?*pyr!ote{czbD{h#= zR2*H9HQ5Rdk3vK}<|D28Ry8RB4Z=^;;6@BFNPxax1KwSw-Cpfo1FYqJkWwd(pJ|xw z2>x1)TaN&lh}BZ?n9)xX;_O(9hScP0brphxt8g2DH})p(XB>7j0xikG6#xP=%))fK z@zD>Qg84{Nlwr=95EubH`iLhwOTn`n^->LZe|yZnmSxkAS?z;_PF$Fab6U(h69Z4y zX+}`J5El1*3XUX1%7E%t97Z63A2ubxM(~vy@P7ydw@Lu3nGx2Cz;P@yTNG`Pgv1(9 z8u88Idgx>*lSDvZNcDqp+!8~ZAa~S&pMcHx90%1th}{K1)Crl_6~HyAZHj8h9{8ps z13+8S3ArFdj|v{6!ATyT8zC0~_=_Oe?ltBY5J;-PXvdPh;IJSIc7rXM4#5OCTA_yv z4HF3wZmh7H1MWsh_j8~iILa_rb->R`b@qJ>t|D;Nj+HG5JVF*Otm%k38MT3rIsExE zUDAu-_T-NwSdb8hzR(1j4bjh<_;&`u&uyWsPN(R3kcu6P?8$FdyIP%ivf^dnCm820 z9@@+{JF!Fr1VSL~1$S*q@C0~n#aX+&XW4$~N8TgQ6z4&-3{6JUr^M$)7&4p5)M%A` zLkfl@1Z*g;ML=;dlTL@sqexT5`0@i<yN<vQ)o%99KY?8efv6LwTX9^5v4)0w=a832 z$A(A<z1126;7|k>PPR78KS1nr0kE5GI!nG0u<V<qfGvc;41m!M!#W|RSHzIS8@EA^ z(GsZ8M^iGOU(eRP64(g>!DyU7Adq2B0DcjHAN$ygJV!6)-L_+)R-7D-TPg!bE3|6^ zqY5y1vFeU!k3TvZ2Z1-vq{kZYcMZYM#6jCdAe{=vb}Ya`Kqn;gWI1niOHy#Skr@K7 z-cK1eRHiaai&_in4|^T(T?5{Nz~2i5_Z`)?1i-?EN-`Nd5rU)O$(n{eus!BXz_Sx8 zKz$xaDlRFHy*M@+M~n&6mBK!J9<%mq5csJA?~(_iuonxY-_nLk#6rYqoKT~^J!WW^ zgyRe*ya?i6H5Q`zp6JH;2K1t$8u0rF{450b+=#ZDgVWsz;$R$YhFBC=O8;aj_zoFH zyHeel2$i9y8u0%^u)Z2_-4B>735+rf3VuoV14Tb4Hm;Bmu^~{V;Fx$x{6hfzHUxhy z26x<8RYR&10V_)ay$oY$O0!{4LW4Uw!d|Sb6K603^IlxzLNt?MW(1mD|1N(zP<`;T z+x-BPEQFox0`0hlu-7a;j3i}YF;*c3t3CO6I(^hyAS0gasIdn8H9_#hQFCo4jTlNV zxUoum^2w4wW(pjzRjLR5vR`h8_I5xpmZ_GcYEvqy0q0&Y0pKry;74_tl^vO@0CeBQ z1b}2qW!P1ownI|>VaSX%ElQGxRDG)EEklP+9M}I~4M+j_e<unqEa#P$e7vBtx}|c_ z6bO+Q%&b`oP8|7!1;b6{Ehv3jg;pfwE(Cr$3bs#718V?|#ga8<3ETr9+?3i7&c@bn ttPzzAgE!<?rs84<ELvhX3HhPZ{||RyIk0N|9OeK3002ovPDHLkV1mzyVL$)? literal 26325 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 diff --git a/addons/skin.estuary/extras/backgrounds/pattern2.png b/addons/skin.estuary/extras/backgrounds/pattern2.png index c6af819c11b0db6f49e1a50d680b199fa1552eef..b6c4b61215bd6160392dad7a408a95eb6672a00c 100644 GIT binary patch literal 37679 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 literal 37924 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%0AIm&ViPKIu6oL8re7#=lD!_>11r36q%c504 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 diff --git a/addons/skin.estuary/extras/backgrounds/pattern3.png b/addons/skin.estuary/extras/backgrounds/pattern3.png index 75588b9d95ccb28cdd8977b3dc4a13ef62d14782..2ac483467b02473a8975f4bf18e5793eed43d23f 100644 GIT binary patch literal 34567 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 literal 86557 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 u-Z3?$n&=<5-^37fWQ1(h57;A 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 M07*qoM6N<$f_V8*)c^nh diff --git a/addons/skin.estuary/extras/backgrounds/pattern4.png b/addons/skin.estuary/extras/backgrounds/pattern4.png index 3d2737144c6e903e232d3bd6b7b2056477628ade..8e2ba5f100536893b0e075eb24fc3bcfe20fd743 100644 GIT binary patch literal 19332 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 ASO5S3 literal 22602 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 diff --git a/addons/skin.estuary/extras/backgrounds/pattern5.png b/addons/skin.estuary/extras/backgrounds/pattern5.png index 487e59790425dcf1cffc700b33411ee948137525..a0552bfbe3644de99e6a54d575d4ecd31bac4e35 100644 GIT binary patch literal 88485 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 lb5>B^`l&z{*2m5pk z%H@g&T+(7OoSX`$WG~(~>exL5oQF)m2SB0y7p<J!q4nlz9D50LHfFEr`a3`F!cgmd z*pLdM<Gqu1<5Fa&T<3&JklMsqxj|_a&8ItO#9Bi>9*kf(d3iyo<tz4MB4JX95M+im zT<n39SK-Etz0Wl~EUen(sUH6kICyzGp@}6^oeTYZ7jJv_n`i2Sr04s1Tm7y6=MrHH zPR^+<e^;y@{PTor`bN1+z<5$@l|P;7Wwh-(coXBat5&r^8+~DgkN*c2f|au=Tzcq& z(wkRhEEkzOqChz!$}$AB>Y!~Pjb^*W2^G*BFPWD7k3Ycsg1PNOqpO@_;<nNlf8WJ` z{e5MmVi%?YTR;wn9wuFaP;&+EDn1Fp#LsX8_dOJ=mqixYk&2p$EFB%4@}%zwKHO)T zA%XC?9`vpFqKC*{Gd&?8Yt#El&&L*osBnn2hTS_0N=N4nG<nm8tn6(;8;Pmj)~gSK zvlUDQG-s>%Os7;Isr)!Qy%msgA8JohKcRH~D*lPFfUR)9Vt2GyvQ{Qr()&i6Z16-z zGMcJP?1s2?Qd8Qef+hn@n{lVQAcs=ju+Z-2^{|k+X2AGkvEP#B*H3NybO;=0Tc7p` zr3EuNBH9pcOt62dx-cP_@kE)->Z#}W#t<d)t@BXEofv1Nz!Td>=C(WG0M8@z-1p_| z(az*Ai2k8T)w^CvJzvN@kS62r8}`&pZC;%5@rjQs-0M@fT;z7>?0?vfSZ|y?`O`<Z z={-!erJRae>pvg7$Dr{dzSpSzMdBNnW>+%|lGJ%xA?5xj?|1&S(I>~U_va{(v%k5J zIL_5gWpF&;=+!#IWVFhZ5|*p=pP!xm5eIED1<G=rM{`+jx|S`>5u&;C(V3>;N)E}* zqWWg$Y2X<+U;Of~Ny0lC3=>hanqgG(B=$e?vR_b0)q7+62RgZNf*I{yhK^Lu!5L~_ z;_@*EHQxp9vysZDZso4n42H=D{wWW-{>kI|hsg)dTZr;DaTMrLudUcSBO{ze=;_HD zEp=CN@$$-oft&X!M#jFGr+*o%eDR@qZ^I%!klE8-6o4KoTO_O%-3}C<LkU6N$F`Gg zr4U^9Jyy2VbiWHp%6zV@#mQkM7Yog6KF>$MmeT}MJ3H@i{&coq3i_^C605%WIc+M9 zg{ba<6cCff*&$b0rnjxSG+0-CyTEm?*Va2)C&*?Um9?2(yNt5ceQKj*)}IT+eZ&5g zpx`&4E4}vs=xJ}cs%F^m9?VBRo9lF=9inw^6A)rimHKPom8j(~`0NQ<61~l9p~Vxh zL-R4y)UuUj)SyzE0B0&q5T4v_?d;^Ck3Aj?xpQ4|;LMTWC1BzI(vt*2?~wb+EhLp< z(Ys<lM^&Wv`dX+-FiIZi#b$e|1B9>pw(MV?pXoQG8P_gd0!ffJPx44A^m4L6u8<gO z)gy=HbjTvzn;U<>EvJ$@?DWorAZCqe;f~+xyQOiDklK0uZB}nPoepOrn60(&g|mIK zCf0GXe9&f&V^CYVI4+mt|A@ntgb;(ieC7RLO!N(Bta!hwb<YNA$iFrnE5}6Jxi=Ie zNF!~}nlZ4Em|ph(!iU=*9-{B<xXh(>EA!qvm10fwPfKnx+uLyPMr*uMxM7|RsU(*? zK>&dWzViP<uF<5ut@5cTRus%Jx}dYUEgs@saZL!ea9go*eobyiyg-}s?eQ}C7oG`m zp{g+(+9J=b6RyA?_3S+eBk;TFYwwKW)Y4NugxlRQK~T*p!!Gl3L54jk1)3vocIbZU ztV8>rB7#ahM7)LW$*<aEes5T6qs`XI9QK@Z^p9*=tm7gBR<_xGp@5tdL_GGQ{O#37 znj4n%8r}2pwf9_Tp&lT*A3q$X@&0xv#g^8C8dPX|D(oN^5pEOk;aAu?|Lfp#E5I0~ z3Z6<TpeoOlPKdasMZ)h2uX2Ph?TWgyQ*&k`Z1t5N=mtF|;W4Iz)faT0pDQZwfn_d` z>wN+$<g`fvjBxJaQ21-|#chhv!%3TaK^@;?g+0cS=1>d*UCv@GP6tg!SanDA>J{E? z8h%xyFaAEZ&Wn`kcyAcA!{m9M7N!Lf;q36`INBU&<i>!nWW+|6V7$b;vn_(9Qav{0 zx9CdXa;xF>BOkK5O>+r&8p>ta^0hzb(RqK-@za+=d6%_`MSo}?<PEjIEeZQpyeP7l zmXlaS&+5y}ag{F+l5%<$(Ii{tU=X86dotD;Jzvmi{^p6`<U0xJQ>CUK+Y5#wI6<>_ z3K=ZH4}&N}5Kg$Sk&;qoCp~HtaGWpP9Jd|Jw`+4D1ZPasR)SRz^ky_d#}P6c`MKXD z#MHQ^IjJX~@!M90%`}jA0<?eG0W&HYFwgb>0SHGb=oT`*Y8AyW&cEU(3Jx(|wLg2z z2T6;x-~T@!fw+`tpta$BpS&tO71}BDv+k#e=V%m(@g|BL8}OMwF4~KS4-yX``7({g zA?0!}F8*=!hm&2VV<{4}DUTIWcctoQV0vfXgcH1hkjkZStJIGHaTrd~*z+Gs{z=eG zq~S%$eEqWD*2z(Y1k$72z{%Xdhn%GEq0QkqJAELWVAY`kR8r}ue^{}2TQf9XPPFv; z=F-z8k37wee?%y)c--G;c<f3DrqopZvOck|vS)^89IdB=yv-q!iGy){A&)%r{mSXQ zX5J-wO<*^4KUWmk>&!Fo7BoRsgo~$<jgcO?!%-1DyJN1<sm5V4ZqPwhxuT8))tq!_ zs+T58Si4iBP`eHiU{>CGl{z$6$bIP%n3^JT9x4_HVZ40t&f3Wba$(RV@<mrr_%k@m zp<9IUCv$R<IyQI{r+*EGZc@W8c~1_R@+obh_o+ly|NZuQ5{7PD8X0d|>3w6aqJNDR zP@u|CcKSB|w3%k!WX|Bl={*zIy8FUhv-<bCQFlJl5ul+Bai6V9`-3@wyg>sxZP)Kd zF=3JA95lUT91hwpjNCCakRH%o>uo{!u{$Llrb5)_^w&Q8*by(AF8BmVg1(e<tgmJW zg&R#wCNxoV&?*YiUA=HrnCe(z^N29K#>p6yRpD~ZTA&s7&tB0a!8DI}-<K}<-WZ+T zO;ky2lH=Zdmk}~8uo)acj8d8nWYk5$ywR6y&EzU*$^IACiIEeHPxs0039r^ZDU~cb zN)^8;zt*jFAHJx=tlltHeiJa9_4XNq7p@fd_TK)0jV7Cgs3wnTZP_dVd^xe0YJ<c1 z59#FnnX%56lossArZ?)@7vG~PT>eJsW3ZTthjL?T_=XsoPyn^7f8*pZ+5Jfn?U201 zFG={1H)T@#x-}jf#mnkfNp#pMdw1=(*!M*=ZvVj&6>op<1DB2`jGKIm&8he5u;I&1 z9DC)lZ}Dm&Xqe%a+QKy-%r9DCtx1C<POhj+Ftf`W@HUWoo1Kr+{JPvjv67qX;ZP8g zhxhlyL7#gPBJTes3}Q<nr@pH{V+$-x7?*0-AE{>1mG0GdrL5!sikC=|iruJ5(|YkT zXe3FD*%o?svxZ|duN2BR5!5{!`A9oiP!H3d+{C@!9u1bYj;QugH0*cwc2BnVrT$l# z?Cd?0Zpy>_#N)~v$>PzwzImuoXxXGo$*wzIXMIfL-$<t{`mrT8TL|xNKRT_WSrmI6 zYjGx0_+OSYLtb1%iE4Cfl5kgLWTWymeJW$gx`{~lISI2IxOU!aZyK`lTZw+oa)JT` z?+U!jAwT263wz(M-Gzrtv`ackXt`zpyR*UFI7FdWz=<^rq7+*31<9rh;VawNWt+j+ zRh=tYW)wuxe^BWCSFnslJZfVm#30Di{+FaaDC}^(%l)cKbfqs?tkM*&>dothu;Ss) zVttpv>4H8Msj&#PnH>ku<@e;pF%_>@o`2Kdi1UK<{5Jx`iLbuSMGzT)!zfi(xAry| zws><Gv+Qk8_?Z@?GCAm%a{iF(aFA+>qDip#pI`>!2hrIb-!1zDu5P|R-*y$+?dixz z{FJ_sbPV})Vw}<PtcF;|2OIiWVG8<@(`3_*#rT|A=v!3`D90{WgY%cJ2f=hjUxHYb zhN5HD@oH4n1592r0e`Cra)ICLzEkDtPRYIrcv*ndaQ*A<oVQi?6z??nA@8yA&)0qQ zj(%Grq1fHjEWa4Mr3c|Wx`K1Ggd0eB*>T<BGYz8a_m;}7qQ2`$_kRXsGxNT*jB)d3 zkUS*ee;mV1Zu)J^6|x}7gRt#UuIx}+>yI_dP+-+q!f+Ip?z(904Ajj5yl2U8cW)*1 z%66#*{p$R$DZk0#LA%@4>`RN;sQ4DLixjYDPz|C+NsL9@{9A0vQa<n6xXy~GI+spa z5*<8O69`mZ>@J>vz;<YCi9cJv()ehx5A=ybzc|U78aaWS--|di)P%>@T-Fhw$FeIv zNECgBNKWJq0QA%!>Hf~a02*TjKzX7Lb6yZtLePZ|LEQu)K)rN$N9Y>PmXkas0A2co z0&CTNRe1L&6E~BizxDd_eA~F*M{va^i%kttwpfrcG79|_bEdBw%78DPkJyl7<Nh4` zvZpgJ8=H^Xd{q&d>S2QaOaocYz!?Po!HFqe8hZTh`jQ;M!pSqVMEkvzYJU0A@Vi*u z{-+cVl4um;?Cmn(D^M={u8%pHw=_EB-Lh~G8`cGrZ&%9Eb*~f|l(GIxBc*#PB=DM^ z2#%T=`wjWMJPLSM?-cXjEC|b}-HSEJFyvYBN&m<Xgi&4z3+g~|&}JTl*E}AEp1^+e za4VkDo)ewago#W3cwq@F&;s0@8jFNRgDuIq&UOJ~9RP;wSl|Y>bSI`$8_eN%SZ!t@ z*uLI-vUNXeYY_4arb6a$TF-o9<nF6j{q|1Z$|GA#tS5{&TytVr!&7lin@Tj~;_B;K zj&L)^Q}rpi-h;!Zm8Pl-?LG3gkZ+ZLj*EM<s96rB3#Xq~(mkk>DA)Z(=P6%VS0JV+ z3(|YQllhFTZd1hKaCDCi`!yo*wru3Tv~<2N99};0LFKDR#-3;+OhGJ%*95msLr?3X z4ORS}u<e3~0)UP67k$pxDQ-C0A|1M|7)UmhLIa7vN&yv(FDmmlf)GL>QFFV9i*q9n zmqqnJ%c~x2vHr`)xPxs|P^}`KV^IljVmx~%$JA|88h7FEd)16RDhaMZAm4rhgV%BK zeqv+95F#z$lP^PX{v;P@rAkXs>;`g(RE4&ST-GjIH3wYSKBw~f`vF4~RNlTYq6gz@ zXd2DiD`8PVlyQKhN4h?oaH2boMCx1t3z*;NgyS(CN(FI(=eGqZiwcF`UnbBf!s|{Y z(Ff~)Qew%|jFQ5qdX1Z7$?+{9QVlujqtt7HKc1!e<-o_~1uA@WmGi?JI%;Zga<Yk{ zTQ03$PralA_Gl(t1&BVwTt@ek_S*)3(R*y$C<=$($fA@*ZAxB%CKLty{rJu$ny6K- zq{ZTPR?gfJc>F*Z^wXOw_{)KScQNdFyrW+Q1)-_xLB|J68Te5XP0`&$f_}UDhK*N8 z@_3XfX0+fx`KkHUce`D~or3ZB*6BDO98@=W^>87x%UR(Iw*dc(<j8|7m+=?~!O&#- zP41nrTZ7@|#6>y%L&}v2ttG@OFb6*7i}@?#q^xB{%4Al>vjb8E>N$S!+oW64`g<0K zf+i+^^-#0-G)mq*EXqkc13kapm5T0G+|;Hm&3z!AyxhtU>!I#!HhM4q++o{&0Lx%v zBUYJ7ClvBPKkun#4*AFR?x}FOH4*(@q>qOm_AZKFQd}`EPbYmGq!}$mVrVP24J3Bo z3D0Cj#`G2d$`qt}Ab!C`sFYl{3fwM`%@t}iU^*%tYB~KbHQ%_c*Q9F4KYe06a_9}X zbkgKb4lMhbpV>k0f@~yijasS)hjyl_-EJot<@K70)56JnH@YI<H_)l5O+2%t_lJF@ zRfXLeThLAqa!@8Ca<8C{LrqW<txDy_Zgl&qp_H~EUpH6?D|JWtJQGG)Z(uf>JL#)V z9NU2q;6I~;rl-8O<QeSV2=xQPp@T#%@pF3Le_rPE(NrjSlz7_s5}9>WO97&n-{o;A z0Zf435RM<e`%LtTMu0*q`~L)7<S7Y_D|I!PW>g$^Fk^W}VRtjbdtxn+uW{t>c6nao zric|jCRjVSh25!O3`@DG{;2o3fR5?!V8R^gpUJ+`(3iF7-E%PFDZc5C0A|@?aGFKA zk)<lYkB2t5KT@C`%u!d`FAJ*6NaTwPu$FV#DlNs!-X)5&9?sh;Hg48A6+OKI7u1S< z4;jx2L<oMCs&i7BoPelF!H>yhtGAKs;8PaZMsn@6<wk-ZTc26@dGTW%x)7)kEF+{e zq8odcZ<~gS*b6m3wX+TLmDjP^b+d<h1vRhC;d*C|9Fz*mx19|f&1!zoWV0r}X`Xx% z*F*j~*j!FHN56(#aot_KuwloVNjwj-FLer5ER{M+*$t{DcfG9!`m$uW>5`rLqkyyR z#d5nX1L8h(blX4V>{@2~i?~^=0+lA*o03-^jMG)jFDWj6_>jl&lT*G><ItF=13&i= z6q+LH<ECO3NujFIppw-)pB{U4fIq1vXc*}@nAF<N7Z2b%Fctl;>&O^UTtD+H7GT6@ za+T%P&(nfuP4OW^jizD*YO80Z{v+qGtN(BF2s)0C?VLQ(ZX?LMpY&PKk8)tW`a<_= zno5NMLFdWJ3YCXanfI3`b&<=n4sLq{-7@$snl4%dVo<I)K(4dKI7<MiYG=>Mh5Wiw z1&mn^uKmjWT}W8Yn&!y{RE|sp74?*`TsOs_M;#Fzp8y&HM9uwEssf|_-Q79_qHS}I z|Fs6e9>oDTKKUcZYex}-<<!_Eu79;X8kVQm0iK1fI)!W6-4qfwryyRh8!7^yRt8xC z@0hSfNe9IpMr@`1|IaiO7LTdf|6^jS0=AEOhz&l<l>@InN}_V(O0)hSn$9w;$^U=* z8{MHujZ*2((V-$B-5moY#1YaXhX`1N(gFi%q((Q6h7qGXhDdj#DBSb=|J~2FW7~1; z!L?)W>-~A1uQO2BW6ie9kd1m8?e{Z@m>823Igb^>*aBlI*I-9jSRUPTW~aI)MaKUR zjSL#}sZ4$2*a0MUabz!@fB9OC-$P7NwkFHpD~PX885!a~BT$THqhkdaXUS0|;|_<5 znBYN-yvz83D#xVW(}uOg`T;wllYoRC!XiF+5X;XRp681N2am?6R3=LPj`sK1#Xcj; zVNy4g2N3f1Od+~2>^-Cd)^&d-^icT%4<I4463bwSHCIOeCIvW#X(!3|uDR#8dMk8$ zvi#6b;oX5s@v&CCj1HblGtZ$by!Q15cikAm_5WcQCy^rLi8d@z8IfG2*~<<$+zJLa zd4-}%Jm5WtCcq65fPVeez$wK=ayUj|;@s|Df*Es^r|QL<^!*Lz!yGLDb-e)(`J4w} zwxG-WeY3>T^NSyIoz$LVOA>IAiw2#|FWJkvEsMTcpCs1yZtGG1$O>fE>|?d^_ek=g zDBLs2Hv1&GS2?c@HRtYUv#q(_)g9Q4x(q8+(JXPL<;JlVv{q^TY!Bd6L)=nCB-mG+ z>{L)+#y(sCsWLzWeSeQt@!mWK?@A4B8;0r~s)<T(fI_=Y@jQ%|xQFtzdE*BWism{= zu)?9W@BAE<9f~q<V_*2R%wo^KMMgE`#ZBkN;kGE`Yszo<-VlpYYY~2Yu!kcm^nLO4 zH*Mw_0L>cfQvXLM5ETEj9Yg>r8|cGKGv6j?>Lsum2Xlbbme$ST#L=1~RLikMxY*sk z55T>QZ7R)8=6p56Pr3xXFoeR%8`dQv1n}dD!v1G?@B)uZkR&gDA^4BQh^jm3XNf|W zT}MoTju?5B@iA*8$+nU#^MSSW?K))UB0*p@q17zEk3OhU@$#W@tqG_IX<#hrp46x5 zLo75qH_NEQ!KDUs!P_Ru5pM2Lk$b%S$6p0Z<VBie^sQK388Ozs;2;;5gM%xCac~9T zjP@R7A9EbF(opl^ym9FG3?QiBeTw72%C{eiKeNOMKY#LDsVn~-SPVS-^vzP0InEy- zH+s8f_obLKawHiCSn$ubKPG8Mdypbj=u<Au$XoKQKZxR^h^u@Br^1PL=^kZwuNVb- zv?esb$n{3X7EG6;*GXbT9JcpM8n6Ze;Ssk{;<t7GwyX`z>scHARj+I|0tHkVLXGeE z{l`?CIw<z1F7t;*D;;pln0RT<C}zZcL-o~Z5{;6CaE8VaL|y05!4Evb+_#>mrrAu3 z&zsx}nDdUkaL%r18^C8pQD6%jtb@IzBleq=sBSKB+uRn~zrm!v0#_J$^l#V1-p)|@ z^VYA64OgVCZfo(;87n@ZpxIdVKln}=j(hRes{c{}3L+h?i?8>ozXaR*`CK{U1wT|} zZ<bhCnsIcbQZ7WcvBvugVT`)|P7vOn5!t71{`{@E?ars&H@Cd8KcLMU6iS=&+!Vc0 zvl;QrDhrk<IB>RbI(OiZIMRXmwt?N$o@?EoiyVI+01@nP)3GpnQlZTj-Df>_^LZKW z5qCTPI;%P=TRDRLhRD)J6knM^p;QW&iO&EtHkCK~tzVKAyVSKr(dXlaFAp1zzq~uH zvOB+FllOMfL>2viiWlJm2-Fk>*fY2d;qpkQ>m?&4+;s@~VF(S6<NTnamS>vV!6N4m zK<GHf7d)1aIVa-l9-|E$vL`@7MU%Ra2g6js<PDZ{y3c@-N+2I{f3U)p=^;|u2&@E6 z3bt!no?H9Q$@hNfqfn<bJN-I)2gTl|R|x$T#2t!KHjEoV1+;Z3=`NSXrV_G@vrB3! zyFZdL85!EET}~xXMU!*dk{aNbZUGb)DCCo`jwv{NN{q@k6%!gt6e--9z(wC!;kjC$ z(#OgwcqB?(i4!HuCfTh0NyqyIKZtq#z>dlDmK!^6n|2NiKY}T;=McZ=ffMldRe6<O zreJmb1$@Zpx<n&i<PKrv;dK3dVgX*ONdrb<q}O0JX?EQucVE(abl1@t1QC3ZlueF4 zL&WlU28Bg~ZSQ#n8p%o^5-*do*RDb=P?w;B?9LYeqtQjh1Z4u@*Tn*X2D0I^G(rry zR1wwhHr-eR82(I&TW<jFAW-Lc3ILP)=yREAk73s~eH5@*3m~TNBdFKg&iqO;fc{<w zf~xNff_*jSrzA>ZGcYK6#P}$loN;oy2nH}xh<Np#*(pLjbEuC$TJeUekveBV9l-EU z_n?BEp5;}`d}%{!g{Jt7Tj6RC82`F9>dMUw22Vp?cA1%y!XshFjg&c0uS|g3$tRbK zYGOaE%lUDTxCmKb0~iNkOsG{2hh|F$9NR+ERDKwZz6SnLAO>spemzgFQM>bWRrWQ) zCH<~g2S&ymM7uW-p`!HqB(a?hhhOmbw%fcRCwl)7z(!#$;lK32T3lZ<4zJsxoHOS+ zjYJZ~*`D?c3O8;rLip|Nb7|Q2Ej<$Lzj1_CI?_zi)i0<inuGNP@MkWjJK8A?hH2-E zXOeuz<Lhb3b_gKKX3o4sX$T_cQ35uey8H1YZBo}#f2@%P!{odOsj`=A6j-D&DivK? z_qFukyC6jAWs;&Pr~tlH+)f9&p%XLji>6DUT8J?2JaFR~Wm9qfowinn5CGgG7EOVG zz1&w>;ge@#bJU#I6i}8Sl^-2m;Iq}b&l~R}UWs%yUdO`_;B<#Gh;7vH$mDEG2sLi8 zj-Ku%`ABs0F~vQtsEyZ7vh@d3l^~>umN6cWy*<|n1BDP>adxf8a5=5d!KuT`JG9W> zb7Q~NAXZG+BF5&^cqMd_n6Rm6Ey55J(4o}(%8(FRzxK`JBeIL+OYsS0%N4TW*`#U~ z>K}^{`H9XSjgI6R22|f2c88E@nUd!B{W3PkW$EFyTq#rAn{Dn*(3$v~S2A51c+%rc z&T{OJ8Icl?{}S}_Yg45t;nQyIbX-5`vNM$>OqlBJ?dfO<Wk+>Ea1m|msB6kJ|7+b9 z_f&W7Br|&hQ>745aG206AVMk5-{#$UYa!Gg{@4jIN%VH*tkqb>aZSS2V}^I8i=HJ0 zo!a)LC)#$G*Pu_s9V*!I{D~x0-a+jt_CCDt(+@_KaptQntfa(I53jn>tkYX<;x@Gn z3b+_^BQ9WWw9%28?}*fL{K=bqD7S@oha^zao4}0uo#q3cW`E5tZ8)CIJsOmk*L+DL zZjioI6DR*Adf#1Y@pT#rXX#xbmWFok(jsdsZunazymi4hAWCUV3y;BuW_lS*6aS+C zo5M}7Y1Q^vb`&{!zD5v*u~Y;uiHq@#rbYxTptXN?e2pc+@p8rc^X4L0L&b(qFCJ~% zMG2)vRoQ1ObQ1~nsO>?fBUI_@4T?h6^Ya{O{7zJZ>)pX-x^jvymO<t~m1VS;`@0>f zA;4!_cHW8Y`zeV(Qx4_WoekoGKWDKhcGoD}pmlX^Klt6L??>HI1CkB^V-kSoT|pM% z*uIXeAnS{ZPXbMXtYzQh-of~Nwz;Knfhp;D6LxSwk^|nty*6jt)HwfOk~30|94W>= zC{f1sZo|kMe0hdY%~Y(O&{Y%J<}J}p-oyrKWGK5@=xS|+w<z2UL{9i8ZPF)4J40xz z!`i>nVJ%_)CWk>oz(AVX;4TJ9-ncJ$)3JVB!6FFL9mYE-g1IcnH(o%9APim$A!vDG zX(}c3*PI|=gN=AX5B#k51Dn2}g*<1fTn!Vin35)oWAIlEdTFP-z+!!-e~5<HScJy+ z^K>&)OZY&oKLp<g^z@Y`)YVm|hA-0v-D81uA<lh@6CG?eY5-47Azxtro9aKk(B{IB zb|R6vKsnH_r`W#cO~Q45$_vF^JSB#mUs0R;c=SE8{;?;IVP6Sn9P<QqIL=?<a!fTe zexSL!*zx$ye%s1H<e<LMqk!^KlUc5;lHuOrW@7s)xbU_%4$eU09kd1K%nC;3g{Gt< zLSGS6e0*x&1WWou1ZdE`U5$se%beIdV_PPaZ>+0Vv7RUti9KR2p+@kQ5oGrPy+QPw zrg7?Mb4X(Bvv}=^8j$Cx5~lfNrPmX1g$t63$7mlJDt<UQ^v4sCMmui&D*r}K-v6QQ zI8iOV7V8aF4dbYy9E*od!O*8PORh;j{v$JNrLiZZ0L-$Yt*ipoaGW-<*c;Fv{C92u zM3&X%cc1vVYdPbLYqjqM$t^C%(jwp8sP-SW0mT|!Jq!c-Y-2Hj)jL-t*s{e?QlK`u zC=A(V#(njE$_$WQ=!JVVO$kP}u=I*oah=hQbT~P6mEilG-}g|PLNbMR9+@G9);bU= zvvy&+1ll!$u!S0&=V_*9g?B99Ko@l60KF3ZPL{u>IceiahQY`Xc&#a-9#}shyZiSL z$RfBHM2#fb<ZF!#mwQT*{eVo5upgVPJNBTyl>XJ3Q&O8U7TPR1>_Ny)9i|PzTl<+^ z_viw=jtwG~7l2*ZuXhwG+XX-SIdY@ZY1GcxIB(zxB|uhR+7pAEsGY-2x?KS5nVW;J zV=p1=*xlj4X}<H?*wZtP9F)&Gto=&8RFMDBW>yGxV}1d(w~f)sqp1YHf9X+4IODMP zJkP^3H|~`snZd=XsOqKBx&%CL8zeo)iI9Dx2h5ba0_+tX(Jr676^`0|3=uJN)%PO~ z6ku!SjAnim<~?80j3pCYx-79XABdHYJ{5H@iPvh|o%sP^BOxe)t}eWyQpbOLR*i=r zj)&SudUT!9&;RftjH6HbpvUy)b~=(^?6phY;yO<F6a&j`qAbeXt$BT$Ic_3zfwlX9 zJhWifbVz1Uj3-f$ciM;5-~g)Y^C6px-8mG__2v37p}DiWm%`afg*?@a)o<ZG4qg)h z9L@5GxSvf~95`ZZPQ<USEwfuPb~_l?ZBC2=i{rf;_^O%8ydK~M$K*~06$HKv1`-EM z$bI7cayPAM8o%3L;7wb37QE*37+#+B_S8P^c@zd^!S_)MxnKN6Px#}9di#UxsiEEh z{?q>hI0i2_j|dxGF;pr0zbOlk?lroBQcJ{7E?0X!7G;Bx7a<X?lqvP_LG(`nhPzJn zXa;O9UF<TcTY;N&a$Asdi}KeV!>KlCQeG5{$ZFmDKG>Bjp>fh|{_$Hjp5sM(W{f(v zSRpNR_Kfl3AeD;qp&;0$F*=z1a0B@aJ(U*u9LmE{6MkRrLXk0NauGFDeZniw>Yy0> zhPQJx0*GdTPNy&Y>?&6>l6<^kPiOwdUj#zLE3tN$K2E{hvt<^p=d-T6fJAB*c}bi@ z`_k*gd;H~#i31!QH=$6zhCweDApsobQ9@N6g-3+Wt8ArChLyk%r_^TTF*r4XU!HcU z2QV(ly(3n|4LbQ^HmdkbN6_^b@-a!VCP5vA(k_Cyd(qF1iqNR*lIqBZHl;+S;I@n% zzdID<JR8r_wkBDW%5F^z_%CCIhV}UVi6GuV8BDWzR-XPYsXc}8fBh}zN1lK7bqlX1 z`dI*hZRY`JgKTReA-zq$XZiJCZb9k6K)Q8+u*P{ZwH8<Hm^lf{EXP#x3snho@4i)0 zgM*ahQ>VtE#n#VZI<9Mv7Q8C?XvyABC&O|x<(+H=u~vwfJI|BmN@Lxs5C!1Z^qEsN z?jB38?G+~s$(xI<-oUt210E?%=<^8E`ny5e_y*%$CiO$tAfbkeLHI5u#M1e{!T!6e zXTSGsx1Gw30=E>**<UL2q8EW>0W`6`z^nUtpH8O#u7)0TarByCKZqw5(zid-S*4!a zY$Lflx5&7{!My!sWUz%F2Xbt82v^Qat9-<mT!B=<Z3{TY<Bas9G?qDi;Dx-Z>&e4t zbjWG4V%lQ%FMOJ{mrv#*!y_gCP==^FKmMC~`>Uua3=hM;Q+J{v;l%DA`5_!2<q-@P zt(=Os!#tWQc~xgH>nt`o+;k7bqP9e-`p~B6$d6C;7m#M{fh<55jHuk`y#O@X55_PV z{a}&d$Cl{vOrK3C|2iY3StIZQvLvMwE2alOrt)SI^oBF4!?l<^gtT4Ezci{2-2@q? zJfiFTqv7!vc9iY^(@U&!=<4pQZnM5?<TNzUbP|MS4mUwbVU)kc-U6t0R251alI-ds zSJ{Uhe;5djTgQ3-B&z%n!qmQqoJoS!fm5JI@s<hUIeVgn8*`OsURKvSzB^2Pw`|VD zdW)W2%EnYj2RsP*#-9{aY&b8CHSGBfbrSB6WTAR>So5|&y74w1k548K=O_?>P^aqu z_#U%DHD+(X1PD#y090k)mT0fFpT}Q^rHK_w&FV>=C(z5}CYao7*B6UX*M<bw^w;nS z>IAFapTqkfnW1|ojOO{7yXSd-U?Tq|zPZEA8<w3q@fs7NgsW`Dn*5i4V2caX_C#L2 zcU3E)i>-LV0;l5IDtle9FZKIh#Bp#|Y_i(BYn@r@{0BPC1sh6Rdm^t$`a*-x$gb!x zVAac)$3uV<!dX|7XNv*Bhx~k;h|6MvWtbOhU+DMKiV{OC>+|a>-IYqCw>@K{F6|<} zIP$`S#D16pe4ky36nm^sDL>*t?&e#~wnt10iQ@GU*xZkIBhDy$pfm)9jKA#UgDW)R z<ZsGS%i;=nMFd&a<_C)2RA$V&V{TWDPnuFD^bBk7+|&ZijAq-^n2HBN0trS)n>V#U zCJz&z;$#=o29{3c{Q`uXL3T{4tu`1#l;isgzdYj_`c`NI_CxW@{V)4!cmt6$Khh&i zAD_|Wl~O<v$X8cdAM5eeAnbWF$k1~M<S&Lr($)`SyPN>$6Gy7NWOW!lUw7v$%TNH0 z!4Me6Bd+0*jgEj(t&~X)eQPnle8Eyyl+RKYkM|0lx2+n=z$_dhQ!DmGiUPNESS_I} zX;yBEKIO{<40NBqu13(^kwMxgWh_F~u}n~$w*m&%^1{x6-=F?*F$5u8T=G6SliSaq z?Lj=WEgx_O4e!`Z^|EZh$eGs{1i>3fa?Uo&e4#_zNm@eWHkT4VYwlE+HMN{-Uek$@ zXUUdZ%Sl^}*Yo7_qYul5V>3-n4ylk|g`-ydzkAt!eq&YB?~i&Q@yu=Hz2=^R)7R_I zvS7$*E#^reL9v%8{&Iv_b+4UTE&{LgaRxZXe(p(gPW&4Q+|3Yuy;1GmKmy(da<|y( zi1Pv8!2X7ZDwtGsK(Eb0KaUsg&Gx0*C#`5Olo#I$<NxM>mOlm|0fzxEl1@#WCU^sm zEP<PKUz4RlQK#qh3g&@a2_b=cqyXfH=2)5~%#Wr?ulqQIg~>&KwRxBP!gspHq!mZ_ zQ2uw>Yr*=&v*c)jUnzd`LGMWMU2gV2z{#p?^YjuDB_e))3-F)wh+4KEdRPxp49t&L zV^lPU9Z_C+*?i?w;Eza8C=Bvk?n{Y%wBaN8N7jVxtOaT6jAtmj{_`c3NFYLhdYVYj z{dv0S&6h5@mt>D%N1oW!NHYa|#AanI?k+CeT_EFIuzLD(ajaQv=O8X;LI<)3J_)4H zeMCM_qXQ?-nW;Divkaz-xXh%lbD2L@OgUTN@ws*&_sJOo(DbPQS(Y>{6nVQoZYAva zP2-q`axOE6JTt81(<jG9hwpOpUPmqPdUAW&Oul7@%zX$j)Bv%kSZLy#Jslok9U4-i z_>bcd_#E8B2Kd$*jS9N0Df&|;P$d?jZlEL}5wGRC=3?|eYy*Z)Z&H>TtEu?WbTT~I z+rxWzw42(oi#HYXKGBm}7nJoa{~fy@6X*&6SkHS5b+`UVN%LiEyQ6*c6&?-@65+Te zB={PO@N2^U;x<wDEqn%Sa9i7(QfsmHJ6?*kA@9|@t8vZ=CQ#Zgc4XBsA(?p^dG+GU z0`-_M|2((2L8^s}^m&MW5H|uY%ul)K)>vYx8WnM=saJ7#^r^{=r3@jw&_kAANSo@w z#2u^K<-T;EUk4bFpToWnFoSsV<O#o6$T)U>?<cG%`usW18&J;v5RK5&jihdauAV!B zc2v%|jAS&cml@DHjjfB$)=(J5`}*rGnQNKLy$nYVb;?bn^<Gv#I^NA1ylEB3)TL6! zw@~+=Mov`js=Y)a^Tl|eDm#qjoItJ-Scq}Dx7j5q4^9rAPQG6MmeByA0r86)xZu4Y z3JAr{MZ4A!1>a{z`2iYKqy@XqDGuPv9caO#RHwchur0nkhKy`>0m$M!BoS^B^i55z zj#~YavqypYOB4p+C1a7c|0C$`gXazPzmN(&v^v}tNlYVI0wQzBBSHY@rx{n=o%lH+ z<{fcNUqUdNyPh8F%K8uTXtf3fbzOD$a-As{IotiZ!z+<O&KL}~R*XT{8v9j}^B%e7 zc>!*V33Vo`gs?%APb;4ofBlc%Af`radej^N0shxIjbcY(IM3hWPW+^9r(m;ha+6$~ zk@<{+_ox=pkC$d&-e+}g$jHi@(S`d)m3fjvGJ@h)bu4<=y(t9M!UgOgjzH(|)A{b` zDCGrxn5_ycGvIS2m;s|O*SM{KNoJBcWiGqbI4K>_akr+qN?UmQeSR@esq)@v&_(hL zlz?y&o!U?uw+VKx`&5Q%U0if;ntom1<j!GZ!%;5P5b3AjsEAm~&!c8X{&C`AUGp&m z1%4;WF!-`HX%*Ojr_KaWbtL0JM}Go{H%Sg<EL7b4)CL6zexXpQotP_`hRm_2&M>o) zawrjr?w{dXc43@J!Jc%%Ox}I=q`hKz?x=GrmD(9}(Y<vaB0#*m`K1bd@+#H_kS*~# z(;L`LTpm*Y!UZ4HCj^>$zq!M@4v4#0s>fLZ;+0D%sht5C!}}d~G>B4kKK!zTYCoQg zkzmaY2Kg}oo@b%t=l)vOz5r-V)ft&z+)rU<BUrY;UDU>x*%HslO#Daz$<as{kG7wk zWHQ%<U;a-P@|t+fk-^!42fo1xNrGg>L@Dvl?%daf)WCGG-i%U<cLBC<V!dU5i{>Rt zDEU{<hKnKvpYs-hf721{;im~H#PI=&;{a*`-<wlC`j05Eoq?vTK&<Jsw5wBaf12q* z9VJ&f?+k<wavOb8O^M*uQ@j*ppU_KsCJ+EZ*Aw~yTgvBox$9OT(E%qJ?_y!(&9jUr zFD7FL1i>;C1{e1A=O1m8B<46J)6^@|O8dr)h=Q8vB%#!58J$6PIj9$3h>%I8OzN`7 zJ*X2qBCsRhgRwY+ZYIcSrX#f3Sh=R9m6!wcCdSx9Nn%zz$hV0COLN92%O!RQ2+f}+ zK~fKXnxi7380x0@SjYLEGbhydD<$<bcn2&o2xZHv`2^&B+s6t}D66h|w7<l6$ppVM ze`*uVwma?1*{r^+-~u3XTw8@gQAX?Nj!S=MN1S*=(JVG%A($A+Vz9&n3Rdxd*lH;3 zT9G+s{cDp@kO8aB1KLIGyZKn*EMgNqy$NVX%iM#KyA`o@HYO9wHj@@I=TDMAEh(h} zt%15B>zycf&*Trw^Ne>{|7;V0cl~~kcRYEZXB(k~9}pQ;+R-5f?+uTgS^0=NuKuW3 z0QiV?^?g}7_uO6L$fk>v={sSHG=IZ}sYqr3@}O5evIq*@;<Q|&WCxrs&Av^A_=^Jb zP<z1}=_g0AdgilcY&I#PKE)_ktPW1gXSUiT9<6+ZC%T*h9ddgJpL4n)p9X(7wk?i5 z2H63k<=y`!aWwM)jO?Brm(mb~+EQy%6&;2F;HX<mgCUYUy6XaPybor~K+Ea^ddCAz zt-K;0!mGqZWmRDr0(%Q<_^1hrt<1PlQe7vRr!#M&4grYwf)p%&7ypj8P(w~nCrE!w z1+%t;K%0qkqjeN3voV;_C-^|9U8`sN$vQ<A$n=+^t^fBKX}Ek_Nh_WR@5?K2Lsi)y z9=OP89zU@4+eb5Wz{G2uW9>XBEa7Fx@CFrxRipGsQZ*IJf$%v!T?2AbrLOY)ggoFI zVB-vzgzvkpuz06^PFryAB7~#=gf#esOHM~N#_-F{mQXWEq<--IxTj(!sd2{;$5n`> z@!Zc!`uH#-j+5<7_HCcI+`t32zn8<xEL9fAG?xkyE?|eD)dxy2pJb@GCT;K$haxQV z{T9?EmK)7(Y7wlod2B&lLcBfwHq?AUX8l+;E#C6YCb|^-*)~{7FL^3DmYpyA{Z@5h z$ln&cW;Kh5yi_4Z6a%q(M%8NWh>=p#7^kX@LiS@P9Lf>Hl}0mn{e|)-wEUu@HT%45 zZ>bM8E9KTev_QZdX-J%d|KqeHgC2BVyV?)+4aTYvwX+q8p320LtUesY8#vFGSg1B3 zl30U`+XK}L;#4-f4xWuDh-bX7@gnDB=fOmu)dsStTfJiSss<~KETDzF49A{f^@t@h z=5aymXMEHbZuo_8U~3M`<mudHD`ET^3Fg$IL}C;^gGEWaz-(_iS;0(Eq!i9nI~Eq} zKBK(ma5KwMzJ=mlZI1GuXm&qVFMT(W`$4kpA2-`5Zzc?nYQkb?L~&v3e_#iUcKOph zP?3h!e-H^%pi)sgCJjuhW<7gsXPUri3^CfN=w;$@O(T_A4mH2g01Q5GflVkbr`U7X z-m~!{@{({dcQsGhiUQA*nNhx6Pc+~TCiL3rU;s!vuAK>?_XGxODui&z2Oy+I_fg=@ z!*~)*O`XAK-f-Nd=V_&)?UpB0Rl-7#>?W@8Npm$8Kl*VG$%pfEq~VPu+qg5XE6?iN z3SqZ{mtSx5?@6OM!I}$;MzbbavCZG^T#Y2odI@bkwF&KgXSQasJUL`qK*YlWR2aem z9h+rvqJs>z=@6yi_=_eX4xMSjGxuN=P^Cj|bE?8TDuzBdomqDFT8jd%VLKL8e~K); z<rf9X%p%!4_ojQb%G1_vL()F&;6uG?!FT$P?MI?vy7?RT!)nn8%JNp=OQ)IFja!F% zCx0K72iK#2zhP@3ek4#iJBluP>h#hPPfs!Uz8hBiP;@oCbVxy$Jn-rfaGN;xGZe+r z8{Kfasxsh4PGrz`8b}3Y5h}}m`aifMj7lAZ6gG=an}NuGji*!b6(zq191wg;ke?sm z!*!9TZP!9ySbJ}YQl`4H<;9?Ys8E+CWV@s&Jv2M3E9lzF8QUH9eD$xftPi@dR4Jw= zIy~pkT@a$W^=G~aq>k|PyD9jZZQ?c`&Vusnj*b{+9yE){H_eNSq)85>H&1!T<v-{p z7wf{AwXFUu$i;aNavGK;jQU+GwP2v@?6tV-UPBc3I88hgn54)n2Pn4UDs>NJ-*l|U z(GEED0TA$jo!huv?))A=5T!2@sqdR}MIzYwSK~Os+6_!BJ@%~7lv3MCx%i4?*4l<1 z2YIlO(Ol6e_bsuhh*8jG145pxl5rfW;0gogR5#lR;7|Ur1jqyaeSjd<UFZ;J*Qnk3 zM|NniSvIl9ioakyiL4eo601KlZi@PXLY%M^{v^n8eGu_0ZLx3Z5#&840s8W*H>>Qr zeP!}fSA!-2AkMJ<&7%%Ja{6FTO4A(ku)G0cH{$S6u9Hh&K(2U;L~SZvWnM^?HR{;f zZ%2kPZGu8Yk%I0JsGuSvp?{v^N}Y2sX<rQF_&}~}1UQ%JclqodYjBz4N}RJj8Rq)) zhluPmpd^meS{MI{(xHAY4$!IecYMeY-t!DEht=G)JUNGsp>2D@>e<zQB8q)o1*hS> zNT)~G$i-t;rJ+VV>N&W5Z_i_MCHcsId`I4f5=Dp^)pYnd*&@>i(R6Fl=W$<KGz}B= z(oshz`bRoViF$(C|0y@{G8BFOlrN_92UsCM_}Ld&Me-F_rm&Jdbe?_N#NU^+;PrnO zBcT0VYkmY|pXpB|9ucy{WwC#%hAN62IlH(Q$5LAr@!|QGU&wH@)0j1xK^XcSOJd+g zQEG)~gW_@~lZ&!5VZAz4C*rLS0nV5)ckLcQ-r14aP}ui|T7|F68#YV0@fj%{I*#^t z5D4whz@wcrzTFawQw)B0(EADSF-R*h%1=$mxj_UUg1^g-Q*7W6CvSMxfM8+akqpt1 zOpu701P1EO^>(A4QqO|PiH*S>2Yh&Jz<Sh(H-yjj=4K6Xp<$Wj>Q(NQTf=k<1rhp$ z75*G*+|2vEXh;if>od^`uPI);hp8#fkkkZFD@VmhrP+0GC*+baB~VeouB4q(FSy{s zhOlAMggA<1miwgk;v7C+Riuu@&mTtS-i0@Bjko9M1R}kb3q!vq3+Sq96!2(R!dVk2 z^A5Ow9T{OGzJ9KWzC?LQdL;+E$i{bdJQv(V|7-C`Xtzyn_`eblbAvF#BA3t=tC&Ht zrRZ#`_Ebl*J++N3N{mQS6MDg>=w(;XhHD<Y89_<GzA>f7q@&35LCE}``*a%#r?P5d zPqAY}WMIXh@U{SMpgO^sFW9lze_Y1@v={e;`c%a6#sl1e`6}Yo2QBp`?9_Frf@mhX zLxnVpOPhtDw=qPhoMJawJO^j}OyHKvE$a3g*Xd2VIor5fbtu=H^}pwj1><iA?7a>? z0F1q<I{YZ9s&nPcf4|?U2BcS3liP1wj#VccCMmxykjSsla@KKC#p*e1JD0@A&huv6 z3Iw`ELfhYl7Y)_S=jdQWAYn_qrRYP#JNPPGXnNDlr}%OSauZWYe`t1UJ(%iHJmW5F zqqMtRlb|p0`4Yb;fV;i|F2*bxJljQH>`8c|ihKAI2>i+?V+VPtC*eQ4=sFc*4tqyN zY27<NUetesLPb4-HXEoN2Z->gZfwo!Tc$mi=njey`_6z6O!C4l<IUP!8r{*0prc#0 zblky`jr&m2HN6jr%9*G+R;>zsX+eIm8{sP*>oD<+@^3BNKRH2GV}^Vz%v+?sh4p9> z{nv+V!R}Q|;zrvWW1J}C!U*Hvqx03BA}X?|&Yoc`sjZ!4_?c1ASin(1eEK677L(_4 zIEcx_s7n+n{IbM8#bp2M3Bktre80<e$}ms4(FHEeI+rzIVt&>1ZBe$du8+DuZj1{g zvEIkODAe_maB@G>sO7-o`)}W?V5hJK@bqp|K;8Z5<%kv-IsGn@X|K5d=azX?)26qO zs!iGjxWeVwx_ln#rPIZ)VBR=pRuHvo5~xBxH~CWc%j20((v`VlKHk&7!1{E>7i2bi z&Ot8smZL=RnPPX578PT%jp-e4)FmQmQEjr!djyLatQYhibtfJbEbOa%i7t-nc}>IH z1Zdne{rwHjv4QZU53DG{$=%ScrntG<$_At5rSAu5_OCN|AQk0Kez17gfgYrp?OV^R zL4o5)mkUK68?)b(;2st9PtxhyFprO+f^)X+<bSfDcf`KgOPk(eO)~W-HLL?%-c1wF zd2gKh01wS?C?_pT>(61t>Nri8Rdza}eqdBgLsU)UK^PXd=tMt4*}C$kq&SyEkGg_2 z6BMYDaUbvfcn2M-#S+r#_;aKqp34azKfa>_-9en{=Dj$DQNv!N_NizS7+w*~EuISh zc?_%ds>M4j{o{hplyN%G1#f^I$R}9hXn^sb?4&fO8J*ZK92^^1K+Og+6#0DGpQy$> zWL1Zs$`rcKy<)_e<Ry)~RgNsRB{ToH$fK{q+6iUf%JemfCn4jdT&&gHKKrGrvDy_I zEnpgiuveK|P=exxH>f#&=FbTA0BReh3H>t<erO+!cf_4x+(j*+o~B--Jz}2icl<6S zA$|yFm+B=-xNlGCvz&gQTyPZ7gS%=Gr^1(t@{Qtc;k!+nn_sG_YluboRs^<UQV*L# z1^HKY?}1*TV~w0`Q0Mb${9PS2K?u~}pgaO0*Q5{0DnZ~xwN=psymc5y)O}`r|5%py zy$ahD3KZ7oIMZO{pg*wU+RdP8hhm6LVK%jgaT)LIn*?7YlUT$f-hl%=?qWswkOE45 z7sVMnE(*54Lhfbji@&KowRbe*R*=#j;>djn&)WX_(Ic<i<L&D`u*>T&xEiH@YL4z` z)Va0Ab^~{gC}N5fwTz0Su8BC6hP1*r5p|R{ARc%qCU8Nmct~RX1ukm#58}{OYnpp4 z&FqAi(e;~=zz+bq{EM_x={H!KP(e>83k2TR3<fY2MszD#Ghu37IHXv|#|GK?yeM<* z@ecu<M9#{pOrvvDLYY8~F&*+^>GNhuZwh3-O83E!w%T2bAky3Zbjvriodo}?J=#8U zA4)^4q@mb&zOosfg}PdbBYR2&e>^HMf%fR{W^?svND`-83}%aP2%wewn58{8AZZ3$ zFfU`9{>vR>`Rg<*$hMqvTpC{){PDzD@%s{Q6@$iSJld9B2O#W@#!IAu=_$>yun0sH zB`>?pCqG71Cc-iD-1329JE0a?_fOGmg%3Y)eHr!6i<h-EwiD3Nrhcb)^P+~CSEBUx z>9i-16u-mGwz*!txsi{}mNL%(`LW{9zEFcDYiuf$mCqy)pVQ#AVu%Lpryq1G(~KYn z;=_yH(;^dk5bma9=u=Yd0%%=iY3(Z~js&5)NV4nfGYKSt&`g{cJ0op1>I<~B(C^AA zY5oy~g*$FWn2McDaV2gc*VVtR29ML)=ofMMG>1I)fj7kr8uS;B>9p<#S-*1;B`VLH z#a^yXqFhQ@@HOP{*~OjMoS(UwIu9(_v+%_My6Kl)Ol6;GZ%{QwtH&9_KwCd|xGYM4 zLAfx7xzr8u_W+u5ek52e5_KHz;u};^gD)oA_{pcmkdlYll!!<p;wSy(ESz+UGdnZ- zF!uQqvenutuTu9AXh#-eXy3ex(<=-3M$qkv(+fljsVWC7X>lir7=pi%ic?Gzb?0Zs zs&y@`@JWoDG9ahgIIK_%Xn1+EmCYfF{2W@U%gl9@$*&A1a3<D*YG*8T#uTWG8>H&y zdfdsqgMkS&eSICMHeM=vrAHw8Uf25x*C*<}w$A~hwp}Mz0`jt|LZ&C&>`XE>qO@D0 zF+kOE_N{FOz~n*gm_!by8?dKoj-^>>igUwLb|qSv!El)$SX>e|?(MTT0lWl)Jfmfz zMO&>lmKNn!B0M)neQ|t8?ct1ny&tO*3@KlKfe&aOp4$(<5z4&nKh;_v#yja-KucpI z&32oDr+8x~FaEZwk!D<bBt+_@d}s3mCtQ3m2*3lhz!##omYt+3@zd;aWJgD_%@18d zKlOdKuMA#VQcQU*j2FiG#O#2aQ9f7e-|><}`Bt{)8|4!R>C-c>wQdc5Fp53HHzdjU zDF$T8nC2dTv3!n=vlX`$?8Nmr$ysB6c6>mjVyW~+pmaFKqt)shnlkvX&%*K#4ogvf zy@U%oNMIx*MUEHHVL&^6h917EqcQ}n0C6hKTp$=<EfBK&nma)g$5F4l0a>k0VrG{H zQ=d$NHJuF2%-TBo5xzn=GOKyoW>@ZwqEqVE!d@i@pEltx9Nw|u{E3BX_JdvK=DYc% z;3;7?NN-!JGj@=Lo4OreRMg9_%f~~c7Z2+3m8u>*lqK*Th#C&OF|QSi7zWB_XP_}S zh~uDklW^83>;?!|Cc62ZinM&~g5V*1jy-<pWQ8(A15TLA?xLWRr~Y}f8|~>Tuj68$ zns;)37aQEEgK-Q$Hjb+cWn3Rr@XL3}3&G>QvVWazreFpem6jb&<oXQ4(l#<K-u3wk zC-Tsg<#-bQR7O3qpe7jLu~l@8@D~$a@N&0T`}p!_C3eb%Ad?>ADr0Wh0$AYWU_X9- z|2XBu1sbp@JL_Sgy1rz7H>7tb^h&@KC*;6eG;L<@iYjeR-<;5M7wk|rrce8Ox4eD_ zR%L+a^u1|qVCMk;4?Yj`p_l|ECnfRyot{&!FPYqTqFr|t6CY$29Z{j|36-AulBKvF z0_Frr09$8P4X8fXYC45~V8@5qVJntDPlWrW2FK0}zffes`q}@kN%SvkD+F$a5FjZQ z#@7i2S(!q=Hv(jookiES*nd5`c(#L`&w$BQnK8MMF6T)RD*VD};+pi5q(Z)QvMX8d zvxbC`23wqr;J73j3{=5Wpym*En{q-oP53il7gk`Pi&>}7Bpm00OU&?+i|Rd@G}`!E ziyPqm40t*)5wQ>9k=q&#{?U5iKpjPPz}~P<sSZo)X$l8pAT`7#^<JUaB=3_Whs2W7 zv#*61n;+MyOd#%r%xUh_)(G}%*)p{RBl@dv_S}I@SV@XQ7aLe|%XVnCFyBjzcX@q* z(homE?)`M&W2>z$N6(-H4sU6pVtYf>k0)JyQFzR=&p(SOIko)0Jgz&DD}%ct5kfdu z4kqbTq;v>WcN|?UNyhirbmCq!g5hNsN^MROU51FHuN^Lr@jg>oG6lA&ytkX*5(qsw zFjUaXtEfD0YWXD`hCjV|KtBia@=5ajP%x5_w-{C8#Q|HNYGAB5{|#>O>|73sLp`I7 zUA}Kf-%E!p@F)-sa<Q1&m}IIr)9H(O=6z;iKH&RlN!tFf0xvOw$%DP0MG1csMU-bq zB;4we7F7T*bTJ$!3K2sd6eS0Zt+KQSB2B<ZrOs%4bNe%@r!|Oun!Zmi=#BjxW7Bj7 zsCLqe!o7}WgOO%-%lQpq`kgr(F7JH@gqOIom~3L}=3#?cliX>{jM*|+kV&g}9osyw z*gqt~DkxYr=Gxdt1Pz-^TeF2;I&6l$(DPoaj(O|4-qD%<w_r>33<B4_;L8uvdz!C3 zkOa+BEgQXL1Zob0VBnbZyGrjzUpkTRPVE~!uTD^>Vv`e9u>d66`iUegJJ6{!%6)up zUoCJlgB^`Nx!}-VZSUGzK%&Q9drQZ>$Xy&t8dmu|GH-6W;RI6FH$8q;1s6%SA+PA{ zBW*1xi>pj_=Xe@ZvKd=k(}dN2t46D>kP#ADvdm4(mvJ(WLZhC2q`?U{6n<L()**=r z562cbH@xyiDS#i}P-I$|-<IYY3;!C@q2QZT4NYvEGXAabOPKjzqX*Xv=G)uo`A7+J zv+7OG(<A}IwkW<gK!|jtkzUiZXeuC5Q2VXE10mabRf|vC70DFA^xso3o0cqKJVwiK zDIUxJ4tf~Qv+joLD47k{sXBV^jct0fBe2rj?EU7ou~~s9KZB$}y$KJdCBQB3!&%te z-^m(c9M$oc<n)XDW2tv?6R)d0I-pdgFWz2gUanLR$$JzKQ6j_#t1h-4wmH}e3{q%3 z`R3`U*Cd4|H`9FLcC^~fyX#c`;zx(j*eLGM2}7KlwAb?IE=R9L{iE!p^m$*|Srw`< z_}f>X+O`Lg*YWai)tdX24r|6G%6@VwkV9zJ$d2%3)SYuLyFxrp9{v`!4*@XL0TMyo zZ4s{d0;mA&o&N}rD@u|5SzLk1Z$PiSmkBwtlVxLptTq1+8->&lABV_&#^M2o;@e2q zd_72rUV3053#tMx4QFI;-l5-x0ofwy&%~RBq#E8)<_!t^j|@hrelT#+eR8~(-bSO? z5_tI;NgDFm{a@e+5;);6+sX-*Rr&Z3Rq!!~zEDCi(0(Kn_*_wbtskqYrmr>eJ-Kq6 zn>0GKc<a_`XzWBHtrfrW{Dsi++oHX1RhzG3?oe$I?tdx-U+G}9z{?Q~$@rk~(y|*r zMU4AVc>Yi=MQJ$o_u7u|IbJRBks0AnX?silxnN|h|Hj<P?rQyoyu0j>!^LJ$<I?ZD zj0z@fg=pyI2SV`d+|lh%@zxC(>jCyoz@J!gPyPUr(gUGD@vhwk+;aa<{fX&RtY`2| z#<#BTWt(PRaOzg(+{$DaIU9W>`(UhinGK(BiOSC+d18I8S1bE%rC=AuG>d3aAa7#h z;U8loTy^1vvcN>6=U;Y(W1_t~0114VT)^0!J<L0$=gHj<tn;6(u(Vx|hvKn6X(UfN zbXa@yM<}p69B<QNTq>*@DIWvYwWTQI_o>QfxioUJ0s%gErEIE`=#eqvTpHxPn`%%x zTk`5S%8Oj#izl^7G+9rVY!4md15N||(NUAp5qWKw*wVGe!?u+T9Ms_!5n!u0^GwqB zCY!&fCQ9Pa`{fVUEs2%X;{3DH(x2Net-eaTjCFIXWtheVyc^RBHBXaEH0f@Sh!%zN zEJbd3z1IEG<98TpUWs`bK`X<(MRu$ZL3+XAG#Ohq^lbFmqC3BIZf5Qj`r>a7Z#4R$ zm*(jDU7QYq@+wMv3~c>E=4olNvbJlQGCQvXei4B?@PTNXzQ0L>IjSzmmY*Ydc&T-2 z(eR_%7ppD;f{5<0;(3*M9$W#5_c05Y4Bu5g-R<p|Ra#Mpr!YbnYxB`oSkR)WjU+D_ zu^`-;V=r+U)4|gH^A6*sw}*N@%`PGaMxcCt=d3;GOU4uOngn6-CZZvCeUXeq{uZUB zAvr*mf`>bIW=q5%9}8%9efHJSk}sr|GNO+#ZQ;b(%IO=_eetXvUeTC-I>v0E3xA&b z$7V~m;m3lRZT)f9ZhBP+E1MlJ4ozdGQfz3xbaf{XedqmP`;L1`ev`O#X{^IHk5UAy zbv+=h<>ZDLJiJ6-*hDLNsR@M=e2(3mf3tP>LRH)L-KI4T_t;&uR#<X7Vu+0fW%yO4 zW(HUD%EPtV3LISbEK$qWd}M@hT49j{*&Dvz<umso_Nb`XQ3OG~i}|DMp6JK)>ovp~ z4@)<^+6mbq(V2LG)=>ol508x6S%K>tsg#0w7DK-%3Ku)Q<@NSQXMM<ZZ98|OswsXb z`ns2O{oYc<|8D=#SlBcvod*$m1xYsBk$qz%dL8@uyT?J&>*BngVtJ0$sp5&?_fUmH zZJ$IX*<oQ~-gPUY;%fDjbnMEUaNggBaj$Ye@qY@AeXM1d-0gkwHh*q1dpx)9-+WJU z40@}6)GX@-Mzgt0$bYo>UhZhDPqjh3`lI=tQ5tD}Hfq4cQ?$jS!xDxkp#hO<kFMI9 z>twIy8;Pw1tv~uM6HlfYtovW@$iquSti2&<_vq;EY>>anCHtJYYZ73CA=Y6vUiyo? zPKUDF0q7{ARC=e@6!jAwh&7pHl8HbiYA@X{Q<rd*?OlH^R|AdKO-Wi~<b~&DG-Lfh zV?f@B$c!(dq7UbJf?$GjCiSs<6dvcfUkcr1(dP(WIerPIXIsW|(|FRKJBj?}y#8c% z5cnF5O@FSg8Y748<USJgT&~uVmGT0u53>|rkvbDa=xXW>S9D161UPnea=7KSmJn1> zX3o52M^qzpxoZuUjjJMX-hg$f&n*!cBAmS=+p{x=GVr<c3K7u#tgPs$=CSu_5>Df{ zY~Wl%Y31h<ReO=Ni(4_Csu|R5gk8l}k1k3f@$q#=7uE42wK=MAe#n$N8d?S}YjYh! zPov&7M~<Ja<G$on9hxBgTO_*ysWXiVvA0*{PQ|_Tf5-;u5WrPADmmgVVCxH&fs=wu zK4&nC8bE4xA-(jk@4;CviT=g<bj-R9C!7yHnC|VktYy(vOWDxIiisWi^&u~MkZF&K z^F8Iz2QOA$3{JuqFbIAhV88s@Z>q;gh8n9$^_a%#UgQe$*f2dkVxFxE@@2tnBNp!) zqdsY+X_iMv@+?wIh3oUq`bG+V597V>CD!HYODq)~%OlpTn7<Hr-YhJQH*s1g>*(dK zmwe=sb<}G-f8tB8ZiUm-UykAzt;(}S%E3rk_z?6g@%W_8#JN`iXi=a%e)MW0+H~WJ z0`NIQK*3@$QYY{C(NIlfthmHlBcTAX9RM2`ruRz(TaL$1huFL|oU5eVw9wn@*{!>O z^lr{WHl!d#rgNRm`U53FRh6ZRPXW#@-pYodpkcm?(?ZPdOXrQCs&oDB5?SIO0y!37 zM+##Z(H8sp6}dik5zn&4l_TmBvc}$t*f=$>5`Pa_wuH;J=28UK=zk*C8|k|5!;z!I z$Pivza2#TN3RNpv!&Q+W^j@Natnmh1tZ+poxtZqclxa8jpzeX!cLFW_ZeztU%u!FA zY%oQ2^b|?!sDT%GjkBWc-7^PuCmkK@k0*uR@a_iMzLmScncd}jH!Hx|2Mph+j<{JL z%Jx{MtT@coD$dEvT{s&P&1t&#yLm84eB?4C)k)TgJQxWE@OlF@2uu^_AvJe=17xqf zIMFd)zj{n&mTq>s%*cuDHlCYPZc+m@l)EML?EfC#Jn{Z>h5XXn8)I-I!|b&2obX2S zT3Y$(FDk(H=B?OXir~)g>v0nota?t!@2}P0t!|#&W`!1)Z9*85|M@D^7HhEw{O5lN z05%xO^4J$pk`SAZa`IruT-_hP%J5&X+eSM3e2?3#P6*bhW%)C&=06WqF+q3vGbu4& z@gAa*7ri7EnuvxIIp+p5__+qC0MVKU&pz4pRyhRpaT+TQE)`b(^k5L_VgRsNqnCJ; z0vj5uB=KL(IL&&djxDcO0*d$4VNgFV;OJ(K{Os%%0AB4#?d%_~PR&LMO}c)JD{pl| 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 W>i@DKy-}6`0000<MNUMnLSTaE%<s7X literal 88539 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�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 KMNUMnLSTYx#jA_} diff --git a/addons/skin.estuary/extras/backgrounds/pattern6.png b/addons/skin.estuary/extras/backgrounds/pattern6.png index 2ba32fab1a41dddbb10772e1adf293f2c6a46278..425e434b59df9b797ccf8c6b7fc2ff6d9edfb020 100644 GIT binary patch literal 76217 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%0DZA&ey2t2xwZGHWg|- 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 P00000NkvXXu0mjfzCqiG literal 79518 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≈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 diff --git a/addons/skin.estuary/extras/backgrounds/pattern7.png b/addons/skin.estuary/extras/backgrounds/pattern7.png index 6204b3a8f35084e7a9057f6b0dfd4e43c9eb843c..c55427735e04b864f65a88244bed3de82c69b97c 100644 GIT binary patch literal 19599 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 Hu0mjfv_B?R literal 19791 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 From df97b4b1d61b66d672e369f2bee5bd2851aa935b Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 21 Aug 2024 19:48:32 +0200 Subject: [PATCH 421/651] [PVR] Providers: Add CProvidersPath for simple path handling. --- xbmc/pvr/providers/CMakeLists.txt | 6 +- xbmc/pvr/providers/PVRProvidersPath.cpp | 110 ++++++++++++++++++++++++ xbmc/pvr/providers/PVRProvidersPath.h | 63 ++++++++++++++ 3 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 xbmc/pvr/providers/PVRProvidersPath.cpp create mode 100644 xbmc/pvr/providers/PVRProvidersPath.h diff --git a/xbmc/pvr/providers/CMakeLists.txt b/xbmc/pvr/providers/CMakeLists.txt index f01a35a9e3dca..facab952685d2 100644 --- a/xbmc/pvr/providers/CMakeLists.txt +++ b/xbmc/pvr/providers/CMakeLists.txt @@ -1,7 +1,9 @@ set(SOURCES PVRProvider.cpp - PVRProviders.cpp) + PVRProviders.cpp + PVRProvidersPath.cpp) set(HEADERS PVRProvider.h - PVRProviders.h) + PVRProviders.h + PVRProvidersPath.h) core_add_library(pvr_providers) diff --git a/xbmc/pvr/providers/PVRProvidersPath.cpp b/xbmc/pvr/providers/PVRProvidersPath.cpp new file mode 100644 index 0000000000000..4abe0331f07c9 --- /dev/null +++ b/xbmc/pvr/providers/PVRProvidersPath.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 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 "PVRProvidersPath.h" + +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" + +#include <string> +#include <vector> + +using namespace PVR; + +const std::string CPVRProvidersPath::PATH_TV_PROVIDERS = "pvr://providers/tv/"; +const std::string CPVRProvidersPath::PATH_RADIO_PROVIDERS = "pvr://providers/radio/"; +const std::string CPVRProvidersPath::CHANNELS = "channels"; +const std::string CPVRProvidersPath::RECORDINGS = "recordings"; + +//pvr://providers/(tv|radio)/<provider-uid@client-id>/(channels|recordings)/ + +CPVRProvidersPath::CPVRProvidersPath(const std::string& path) +{ + Init(path); +} + +CPVRProvidersPath::CPVRProvidersPath(CPVRProvidersPath::Kind kind, + int clientId, + int providerUid, + const std::string& lastSegment /* = "" */) +{ + m_kind = kind; + m_isValid = ((m_kind == Kind::RADIO) || (m_kind == Kind::TV)); + if (m_isValid) + { + if (lastSegment.empty()) + { + m_path = StringUtils::Format("pvr://providers/{}/{}@{}/", + (m_kind == Kind::RADIO) ? "radio" : "tv", providerUid, clientId); + m_isProvider = true; + m_isChannels = false; + m_isRecordings = false; + } + else + { + m_path = StringUtils::Format("pvr://providers/{}/{}@{}/{}/", + (m_kind == Kind::RADIO) ? "radio" : "tv", providerUid, clientId, + lastSegment); + m_isProvider = false; + m_isChannels = (lastSegment == CHANNELS); + m_isRecordings = (lastSegment == RECORDINGS); + } + + m_clientId = clientId; + m_providerUid = providerUid; + m_isRoot = false; + } +} + +bool CPVRProvidersPath::Init(const std::string& path) +{ + std::string varPath(path); + URIUtils::RemoveSlashAtEnd(varPath); + + m_path = varPath; + const std::vector<std::string> segments{URIUtils::SplitPath(m_path)}; + + m_isValid = + ((segments.size() >= 3) && (segments.size() <= 5) && (segments.at(1) == "providers") && + ((segments.at(2) == "radio") || (segments.at(2) == "tv"))); + m_isRoot = (m_isValid && (segments.size() == 3)); + if (m_isValid) + m_kind = (segments.at(2) == "radio") ? Kind::RADIO : Kind::TV; + + if (!m_isValid || m_isRoot) + { + m_providerUid = PVR_PROVIDER_INVALID_UID; + m_clientId = PVR_CLIENT_INVALID_UID; + m_isProvider = false; + m_isChannels = false; + m_isRecordings = false; + } + else + { + std::vector<std::string> tokens{StringUtils::Split(segments.at(3), "@")}; + if (tokens.size() == 2 && !tokens[0].empty() && !tokens[1].empty()) + { + m_providerUid = std::stoi(tokens[0]); + m_clientId = std::stoi(tokens[1]); + m_isProvider = (segments.size() == 4); + + if (segments.size() == 5) + { + m_isChannels = (segments.at(4) == CHANNELS); + m_isRecordings = !m_isChannels && (segments.at(4) == RECORDINGS); + m_isValid = (m_isChannels || m_isRecordings); + } + } + else + { + m_isValid = false; + } + } + + return m_isValid; +} diff --git a/xbmc/pvr/providers/PVRProvidersPath.h b/xbmc/pvr/providers/PVRProvidersPath.h new file mode 100644 index 0000000000000..8ff7de067d5a6 --- /dev/null +++ b/xbmc/pvr/providers/PVRProvidersPath.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 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 "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h" // PVR_PROVIDER_INVALID_UID +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID + +#include <string> + +namespace PVR +{ +class CPVRProvidersPath +{ +public: + static const std::string PATH_TV_PROVIDERS; + static const std::string PATH_RADIO_PROVIDERS; + static const std::string CHANNELS; + static const std::string RECORDINGS; + + enum class Kind + { + UNKNOWN, + RADIO, + TV + }; + + explicit CPVRProvidersPath(const std::string& path); + CPVRProvidersPath(Kind kind, int clientId, int providerUid, const std::string& lastSegment = ""); + + bool IsValid() const { return m_isValid; } + + operator std::string() const { return m_path; } + + const std::string& GetPath() const { return m_path; } + bool IsProvidersRoot() const { return m_isRoot; } + bool IsProvider() const { return m_isProvider; } + bool IsChannels() const { return m_isChannels; } + bool IsRecordings() const { return m_isRecordings; } + bool IsRadio() const { return m_kind == Kind::RADIO; } + Kind GetKind() const { return m_kind; } + int GetProviderUid() const { return m_providerUid; } + int GetClientId() const { return m_clientId; } + +private: + bool Init(const std::string& path); + + std::string m_path; + bool m_isValid{false}; + bool m_isRoot{false}; + bool m_isProvider{false}; + bool m_isChannels{false}; + bool m_isRecordings{false}; + Kind m_kind{Kind::UNKNOWN}; + int m_providerUid{PVR_PROVIDER_INVALID_UID}; + int m_clientId{PVR_CLIENT_INVALID_UID}; +}; +} // namespace PVR From b3c42f1e4b686fee8c70c52893a9cb02a0290035 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 21 Aug 2024 20:06:08 +0200 Subject: [PATCH 422/651] [PVR] Providers: Add VFS support for browsing providers structure. --- .../resources/strings.po | 3 +- xbmc/FileItem.cpp | 50 ++++++- xbmc/FileItem.h | 11 ++ xbmc/guilib/WindowIDs.h | 4 +- xbmc/pvr/addons/PVRClients.cpp | 10 +- xbmc/pvr/channels/PVRChannelGroup.cpp | 27 ++++ xbmc/pvr/channels/PVRChannelGroup.h | 16 ++ .../channels/PVRChannelGroupsContainer.cpp | 20 +++ xbmc/pvr/channels/PVRChannelGroupsContainer.h | 18 +++ xbmc/pvr/filesystem/PVRGUIDirectory.cpp | 138 ++++++++++++++++++ xbmc/pvr/filesystem/PVRGUIDirectory.h | 7 + xbmc/pvr/providers/PVRProviders.cpp | 28 +++- xbmc/pvr/providers/PVRProviders.h | 18 ++- xbmc/pvr/recordings/PVRRecordings.cpp | 31 ++++ xbmc/pvr/recordings/PVRRecordings.h | 18 +++ 15 files changed, 386 insertions(+), 13 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 25a1a246bb2ba..f132270627534 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -11722,7 +11722,8 @@ msgctxt "#19333" msgid "Switched to channel for auto-closed PVR reminder for channel '{0:s}' at '{1:s}'" msgstr "" -#. label for PVR backend number of channel providers in system information's PVR section +#. generic label for pvr providers used in different places +#: xbmc/pvr/filesystem/PVRGUIDirectory.cpp #: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp #: xbmc/windows/GUIWindowSystemInfo.cpp msgctxt "#19334" diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 481284a51e40d..f7ab8e37d1310 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -45,6 +45,7 @@ #include "pvr/guilib/PVRGUIActionsChannels.h" #include "pvr/guilib/PVRGUIActionsEPG.h" #include "pvr/guilib/PVRGUIActionsUtils.h" +#include "pvr/providers/PVRProvider.h" #include "pvr/recordings/PVRRecording.h" #include "pvr/timers/PVRTimerInfoTag.h" #include "settings/AdvancedSettings.h" @@ -299,6 +300,31 @@ CFileItem::CFileItem(const std::shared_ptr<CPVRTimerInfoTag>& timer) FillInMimeType(false); } +CFileItem::CFileItem(const std::string& path, const std::shared_ptr<CPVRProvider>& provider) +{ + Initialize(); + + m_strPath = path; + m_bIsFolder = true; + m_pvrProviderInfoTag = provider; + SetLabel(provider->GetName()); + m_bCanQueue = false; + + // Set art + if (!provider->GetIconPath().empty()) + SetArt("icon", provider->GetIconPath()); + else + SetArt("icon", "DefaultPVRProvider.png"); + + if (!provider->GetThumbPath().empty()) + SetArt("thumb", provider->GetThumbPath()); + + // Speedup FillInDefaultIcon() + SetProperty("icon_never_overlay", true); + + FillInMimeType(false); +} + CFileItem::CFileItem(const CArtist& artist) { Initialize(); @@ -504,6 +530,7 @@ CFileItem& CFileItem::operator=(const CFileItem& item) m_pvrChannelGroupMemberInfoTag = item.m_pvrChannelGroupMemberInfoTag; m_pvrRecordingInfoTag = item.m_pvrRecordingInfoTag; m_pvrTimerInfoTag = item.m_pvrTimerInfoTag; + m_pvrProviderInfoTag = item.m_pvrProviderInfoTag; m_addonInfo = item.m_addonInfo; m_eventLogEntry = item.m_eventLogEntry; @@ -578,6 +605,7 @@ void CFileItem::Reset() m_pvrChannelGroupMemberInfoTag.reset(); m_pvrRecordingInfoTag.reset(); m_pvrTimerInfoTag.reset(); + m_pvrProviderInfoTag.reset(); delete m_pictureInfoTag; m_pictureInfoTag=NULL; delete m_gameInfoTag; @@ -896,6 +924,11 @@ bool CFileItem::IsPVRTimer() const return HasPVRTimerInfoTag(); } +bool CFileItem::IsPVRProvider() const +{ + return HasPVRProviderInfoTag(); +} + bool CFileItem::IsDeleted() const { if (HasPVRRecordingInfoTag()) @@ -945,7 +978,8 @@ bool CFileItem::IsPicture() const return false; if (HasPVRTimerInfoTag() || HasPVRChannelInfoTag() || HasPVRChannelGroupMemberInfoTag() || - HasPVRRecordingInfoTag() || HasEPGInfoTag() || HasEPGSearchFilter()) + HasPVRRecordingInfoTag() || HasEPGInfoTag() || HasEPGSearchFilter() || + HasPVRProviderInfoTag()) return false; if (!m_strPath.empty()) @@ -1240,6 +1274,10 @@ void CFileItem::FillInDefaultIcon() // PVR deleted recording SetArt("icon", "DefaultVideoDeleted.png"); } + else if (IsPVRProvider()) + { + SetArt("icon", "DefaultPVRProvider.png"); + } else if (PLAYLIST::IsPlayList(*this) || PLAYLIST::IsSmartPlayList(*this)) { SetArt("icon", "DefaultPlaylist.png"); @@ -1555,6 +1593,11 @@ void CFileItem::UpdateInfo(const CFileItem &item, bool replaceLabels /*=true*/) m_pvrTimerInfoTag = item.m_pvrTimerInfoTag; SetInvalid(); } + if (item.HasPVRProviderInfoTag()) + { + m_pvrProviderInfoTag = item.m_pvrProviderInfoTag; + SetInvalid(); + } if (item.HasEPGInfoTag()) { m_epgInfoTag = item.m_epgInfoTag; @@ -1623,6 +1666,11 @@ void CFileItem::MergeInfo(const CFileItem& item) m_pvrTimerInfoTag = item.m_pvrTimerInfoTag; SetInvalid(); } + if (item.HasPVRProviderInfoTag()) + { + m_pvrProviderInfoTag = item.m_pvrProviderInfoTag; + SetInvalid(); + } if (item.HasEPGInfoTag()) { m_epgInfoTag = item.m_epgInfoTag; diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index a6ffd48cec6e2..15f7f50f04316 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -56,6 +56,7 @@ class CPVRChannel; class CPVRChannelGroupMember; class CPVREpgInfoTag; class CPVREpgSearchFilter; +class CPVRProvider; class CPVRRecording; class CPVRTimerInfoTag; } @@ -123,6 +124,7 @@ class CFileItem : explicit CFileItem(const std::shared_ptr<PVR::CPVRChannelGroupMember>& channelGroupMember); explicit CFileItem(const std::shared_ptr<PVR::CPVRRecording>& record); explicit CFileItem(const std::shared_ptr<PVR::CPVRTimerInfoTag>& timer); + explicit CFileItem(const std::string& path, const std::shared_ptr<PVR::CPVRProvider>& provider); explicit CFileItem(const CMediaSource& share); explicit CFileItem(std::shared_ptr<const ADDON::IAddon> addonInfo); explicit CFileItem(const EventPtr& eventLogEntry); @@ -205,6 +207,7 @@ class CFileItem : bool IsDeletedPVRRecording() const; bool IsInProgressPVRRecording() const; bool IsPVRTimer() const; + bool IsPVRProvider() const; bool IsType(const char *ext) const; bool IsVirtualDirectoryRoot() const; bool IsReadOnly() const; @@ -301,6 +304,13 @@ class CFileItem : return m_pvrTimerInfoTag; } + inline bool HasPVRProviderInfoTag() const { return m_pvrProviderInfoTag != nullptr; } + + inline const std::shared_ptr<PVR::CPVRProvider> GetPVRProviderInfoTag() const + { + return m_pvrProviderInfoTag; + } + /*! \brief return the item to play. will be almost 'this', but can be different (e.g. "Play recording" from PVR EPG grid window) \return the item to play @@ -622,6 +632,7 @@ class CFileItem : std::shared_ptr<PVR::CPVRRecording> m_pvrRecordingInfoTag; std::shared_ptr<PVR::CPVRTimerInfoTag> m_pvrTimerInfoTag; std::shared_ptr<PVR::CPVRChannelGroupMember> m_pvrChannelGroupMemberInfoTag; + std::shared_ptr<PVR::CPVRProvider> m_pvrProviderInfoTag; CPictureInfoTag* m_pictureInfoTag; std::shared_ptr<const ADDON::IAddon> m_addonInfo; KODI::GAME::CGameInfoTag* m_gameInfoTag; diff --git a/xbmc/guilib/WindowIDs.h b/xbmc/guilib/WindowIDs.h index 8c4884205a066..40e4724543eb2 100644 --- a/xbmc/guilib/WindowIDs.h +++ b/xbmc/guilib/WindowIDs.h @@ -134,7 +134,9 @@ #define WINDOW_RADIO_SEARCH (WINDOW_PVR_ID_START+9) #define WINDOW_TV_TIMER_RULES (WINDOW_PVR_ID_START+10) #define WINDOW_RADIO_TIMER_RULES (WINDOW_PVR_ID_START+11) -#define WINDOW_PVR_ID_END WINDOW_RADIO_TIMER_RULES +#define WINDOW_TV_PROVIDERS (WINDOW_PVR_ID_START + 12) +#define WINDOW_RADIO_PROVIDERS (WINDOW_PVR_ID_START + 13) +#define WINDOW_PVR_ID_END WINDOW_RADIO_PROVIDERS // virtual windows for PVR specific keymap bindings in fullscreen playback #define WINDOW_FULLSCREEN_LIVETV 10800 diff --git a/xbmc/pvr/addons/PVRClients.cpp b/xbmc/pvr/addons/PVRClients.cpp index 1f188ac0452ce..89d8de916c96f 100644 --- a/xbmc/pvr/addons/PVRClients.cpp +++ b/xbmc/pvr/addons/PVRClients.cpp @@ -370,9 +370,17 @@ std::vector<CVariant> CPVRClients::GetClientProviderInfos() const for (const auto& instanceId : instanceIds) { CVariant clientProviderInfo(CVariant::VariantTypeObject); - clientProviderInfo["clientid"] = CPVRClientUID(addonInfo->ID(), instanceId).GetUID(); + const int clientId{CPVRClientUID(addonInfo->ID(), instanceId).GetUID()}; + clientProviderInfo["clientid"] = clientId; clientProviderInfo["addonid"] = addonInfo->ID(); clientProviderInfo["instanceid"] = instanceId; + std::string fullName; + const std::shared_ptr<const CPVRClient> client{GetClient(clientId)}; + if (client) + fullName = client->GetFullClientName(); + else + fullName = addonInfo->Name(); + clientProviderInfo["fullname"] = fullName; clientProviderInfo["enabled"] = !CServiceBroker::GetAddonMgr().IsAddonDisabled(addonInfo->ID()); clientProviderInfo["name"] = addonInfo->Name(); diff --git a/xbmc/pvr/channels/PVRChannelGroup.cpp b/xbmc/pvr/channels/PVRChannelGroup.cpp index 1ae0e3e1e8aa9..3f68e31b77d4e 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.cpp +++ b/xbmc/pvr/channels/PVRChannelGroup.cpp @@ -307,6 +307,33 @@ std::shared_ptr<CPVRChannel> CPVRChannelGroup::GetByChannelID(int iChannelID) co return it != m_members.cend() ? (*it).second->Channel() : std::shared_ptr<CPVRChannel>(); } +namespace +{ +bool MatchProvider(const std::shared_ptr<CPVRChannel>& channel, int clientId, int providerId) +{ + return channel->ClientID() == clientId && + (providerId == PVR_PROVIDER_INVALID_UID || channel->ClientProviderUid() == providerId); +} +} // unnamed namespace + +bool CPVRChannelGroup::HasChannelForProvider(int clientId, int providerId) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return std::any_of(m_members.cbegin(), m_members.cend(), + [clientId, providerId](const auto& member) + { return MatchProvider(member.second->Channel(), clientId, providerId); }); +} + +unsigned int CPVRChannelGroup::GetChannelCountByProvider(int clientId, int providerId) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + auto channels = + std::count_if(m_members.cbegin(), m_members.cend(), + [clientId, providerId](const auto& member) + { return MatchProvider(member.second->Channel(), clientId, providerId); }); + return static_cast<unsigned int>(channels); +} + std::shared_ptr<CPVRChannelGroupMember> CPVRChannelGroup::GetLastPlayedChannelGroupMember( int iCurrentChannel /* = -1 */) const { diff --git a/xbmc/pvr/channels/PVRChannelGroup.h b/xbmc/pvr/channels/PVRChannelGroup.h index f75cb00b5eb3a..03348db1bd4c5 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.h +++ b/xbmc/pvr/channels/PVRChannelGroup.h @@ -376,6 +376,22 @@ class CPVRChannelGroup : public IChannelGroupSettingsCallback */ std::shared_ptr<CPVRChannelGroupMember> GetByUniqueID(const std::pair<int, int>& id) const; + /*! + * @brief Check whether at least one channel of this group is offered by the given provider. + * @param clientId The clientId to check. + * @param providerId The providerId to check. + * @return True, if the group countains at least one channel offered by the provider, false otherwise. + */ + bool HasChannelForProvider(int clientId, int providerId) const; + + /*! + * @brief Get the total count of channels of this group offered by the given provider. + * @param clientId The clientId of the provider. + * @param providerId The providerId. + * @return The total count of matching channels. + */ + unsigned int GetChannelCountByProvider(int clientId, int providerId) const; + /*! * @brief Set the hidden state of this group. * @param bHidden True to set hidden state, false to unhide the group. diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp index 13f53da56e26e..1da1ef5acb7c3 100644 --- a/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp +++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp @@ -168,6 +168,26 @@ std::shared_ptr<CPVRChannelGroupMember> CPVRChannelGroupsContainer:: return channelTV; } +bool CPVRChannelGroupsContainer::HasChannelForProvider(bool isRadio, + int clientId, + int providerId) const +{ + if (isRadio) + return m_groupsRadio->GetGroupAll()->HasChannelForProvider(clientId, providerId); + else + return m_groupsTV->GetGroupAll()->HasChannelForProvider(clientId, providerId); +} + +unsigned int CPVRChannelGroupsContainer::GetChannelCountByProvider(bool isRadio, + int clientId, + int providerId) const +{ + if (isRadio) + return m_groupsRadio->GetGroupAll()->GetChannelCountByProvider(clientId, providerId); + else + return m_groupsTV->GetGroupAll()->GetChannelCountByProvider(clientId, providerId); +} + int CPVRChannelGroupsContainer::CleanupCachedImages() { return m_groupsTV->CleanupCachedImages() + m_groupsRadio->CleanupCachedImages(); diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.h b/xbmc/pvr/channels/PVRChannelGroupsContainer.h index f0185f5331971..80dd4653c8ac6 100644 --- a/xbmc/pvr/channels/PVRChannelGroupsContainer.h +++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.h @@ -152,6 +152,24 @@ class CPVRChannelGroupsContainer */ std::shared_ptr<CPVRChannelGroupMember> GetLastPlayedChannelGroupMember() const; + /*! + * @brief Check whether at least one channel is offered by the given provider. + * @param isRadio Check radio or TV channels. + * @param clientId The clientId to check. + * @param providerId The providerId to check. + * @return True, if at least one matching channel is offered by the provider, false otherwise. + */ + bool HasChannelForProvider(bool isRadio, int clientId, int providerId) const; + + /*! + * @brief Get the total count of channels offered by the given provider. + * @param isRadio Check radio or TV channels. + * @param clientId The clientId of the provider. + * @param providerId The providerId. + * @return The total count of matching channels. + */ + unsigned int GetChannelCountByProvider(bool isRadio, int clientId, int providerId) const; + /*! * @brief Erase stale texture db entries and image files. * @return number of cleaned up images. diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp index e993c4b34dc5f..be92565f57ee9 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp @@ -27,6 +27,9 @@ #include "pvr/epg/EpgSearch.h" #include "pvr/epg/EpgSearchFilter.h" #include "pvr/epg/EpgSearchPath.h" +#include "pvr/providers/PVRProvider.h" +#include "pvr/providers/PVRProviders.h" +#include "pvr/providers/PVRProvidersPath.h" #include "pvr/recordings/PVRRecording.h" #include "pvr/recordings/PVRRecordings.h" #include "pvr/recordings/PVRRecordingsPath.h" @@ -107,6 +110,19 @@ bool GetRootDirectory(bool bRadio, CFileItemList& results) results.Add(item); } + // Providers + if (CServiceBroker::GetPVRManager().Providers()->GetNumProviders() > 1) + { + item = std::make_shared<CFileItem>(bRadio ? CPVRProvidersPath::PATH_RADIO_PROVIDERS + : CPVRProvidersPath::PATH_TV_PROVIDERS, + true); + item->SetLabel(g_localizeStrings.Get(19334)); // Providers + item->SetProperty("node.target", CWindowTranslator::TranslateWindow( + bRadio ? WINDOW_RADIO_PROVIDERS : WINDOW_TV_PROVIDERS)); + item->SetArt("icon", "DefaultPVRProviders.png"); + results.Add(std::move(item)); + } + // Timers/Timer rules // - always present, because Reminders are always available, no client support needed for this item = std::make_shared<CFileItem>( @@ -210,6 +226,14 @@ bool CPVRGUIDirectory::GetDirectory(CFileItemList& results) const } return true; } + else if (StringUtils::StartsWith(fileName, "providers")) + { + if (CServiceBroker::GetPVRManager().IsStarted()) + { + return GetProvidersDirectory(results); + } + return true; + } else if (StringUtils::StartsWith(fileName, "timers")) { if (CServiceBroker::GetPVRManager().IsStarted()) @@ -750,3 +774,117 @@ bool CPVRGUIDirectory::GetTimersDirectory(CFileItemList& results) const return false; } + +bool CPVRGUIDirectory::GetProvidersDirectory(CFileItemList& results) const +{ + const CPVRProvidersPath path(m_url.GetWithoutOptions()); + if (path.IsValid()) + { + if (path.IsProvidersRoot()) + { + const std::shared_ptr<const CPVRChannelGroupsContainer> groups{ + CServiceBroker::GetPVRManager().ChannelGroups()}; + const std::shared_ptr<const CPVRRecordings> recordings{ + CServiceBroker::GetPVRManager().Recordings()}; + const std::vector<std::shared_ptr<CPVRProvider>> providers{ + CServiceBroker::GetPVRManager().Providers()->GetProviders()}; + for (const auto& provider : providers) + { + if (!groups->HasChannelForProvider(path.IsRadio(), provider->GetClientId(), + provider->GetUniqueId()) && + !recordings->HasRecordingForProvider(path.IsRadio(), provider->GetClientId(), + provider->GetUniqueId())) + continue; + + const CPVRProvidersPath providerPath{path.GetKind(), provider->GetClientId(), + provider->GetUniqueId()}; + results.Add(std::make_shared<CFileItem>(providerPath, provider)); + } + return true; + } + else if (path.IsProvider()) + { + // Add items for channels and recordings, if at least one matching is available. + + const std::shared_ptr<const CPVRChannelGroupsContainer> groups{ + CServiceBroker::GetPVRManager().ChannelGroups()}; + const unsigned int channelCount{groups->GetChannelCountByProvider( + path.IsRadio(), path.GetClientId(), path.GetProviderUid())}; + if (channelCount > 0) + { + const CPVRProvidersPath channelsPath{path.GetKind(), path.GetClientId(), + path.GetProviderUid(), CPVRProvidersPath::CHANNELS}; + auto channelsItem{std::make_shared<CFileItem>(channelsPath, true)}; + channelsItem->SetLabel(g_localizeStrings.Get(19019)); // Channels + channelsItem->SetArt("icon", "DefaultPVRChannels.png"); + channelsItem->SetProperty("totalcount", channelCount); + results.Add(std::move(channelsItem)); + } + + const std::shared_ptr<const CPVRRecordings> recordings{ + CServiceBroker::GetPVRManager().Recordings()}; + const unsigned int recordingCount{recordings->GetRecordingCountByProvider( + path.IsRadio(), path.GetClientId(), path.GetProviderUid())}; + if (recordingCount > 0) + { + const CPVRProvidersPath recordingsPath{path.GetKind(), path.GetClientId(), + path.GetProviderUid(), + CPVRProvidersPath::RECORDINGS}; + auto recordingsItem{std::make_shared<CFileItem>(recordingsPath, true)}; + recordingsItem->SetLabel(g_localizeStrings.Get(19017)); // Recordings + recordingsItem->SetArt("icon", "DefaultPVRRecordings.png"); + recordingsItem->SetProperty("totalcount", recordingCount); + results.Add(std::move(recordingsItem)); + } + + return true; + } + else if (path.IsChannels()) + { + // Add all channels served by this provider. + const std::shared_ptr<const CPVRChannelGroup> group{ + CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(path.IsRadio())}; + if (group) + { + const bool checkUid{path.GetProviderUid() != PVR_PROVIDER_INVALID_UID}; + const std::vector<std::shared_ptr<CPVRChannelGroupMember>> allGroupMembers{ + group->GetMembers(CPVRChannelGroup::Include::ONLY_VISIBLE)}; + for (const auto& allGroupMember : allGroupMembers) + { + const std::shared_ptr<const CPVRChannel> channel{allGroupMember->Channel()}; + + if (channel->ClientID() != path.GetClientId()) + continue; + + if (checkUid && channel->ClientProviderUid() != path.GetProviderUid()) + continue; + + results.Add(std::make_shared<CFileItem>(allGroupMember)); + } + return true; + } + } + else if (path.IsRecordings()) + { + // Add all recordings served by this provider. + const bool checkUid{path.GetProviderUid() != PVR_PROVIDER_INVALID_UID}; + const std::vector<std::shared_ptr<CPVRRecording>> recordings{ + CServiceBroker::GetPVRManager().Recordings()->GetAll()}; + for (const auto& recording : recordings) + { + if (recording->IsRadio() != path.IsRadio()) + continue; + + if (recording->ClientID() != path.GetClientId()) + continue; + + if (checkUid && recording->ClientProviderUniqueId() != path.GetProviderUid()) + continue; + + results.Add(std::make_shared<CFileItem>(recording)); + } + return true; + } + } + return false; +} diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.h b/xbmc/pvr/filesystem/PVRGUIDirectory.h index 31375ade32271..132b0a227debf 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.h +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.h @@ -96,6 +96,13 @@ class CPVRGUIDirectory */ bool GetChannelsDirectory(CFileItemList& results) const; + /*! + * @brief Get the list of providers. + * @param results The file list to store the results in. + * @return True on success, false otherwise.. + */ + bool GetProvidersDirectory(CFileItemList& results) const; + private: bool GetTimersDirectory(CFileItemList& results) const; bool GetRecordingsDirectory(CFileItemList& results) const; diff --git a/xbmc/pvr/providers/PVRProviders.cpp b/xbmc/pvr/providers/PVRProviders.cpp index 547026a13d904..7cc8eb576d055 100644 --- a/xbmc/pvr/providers/PVRProviders.cpp +++ b/xbmc/pvr/providers/PVRProviders.cpp @@ -82,10 +82,32 @@ std::vector<std::shared_ptr<CPVRProvider>> CPVRProvidersContainer::GetProvidersL return m_providers; } -std::size_t CPVRProvidersContainer::GetNumProviders() const +std::vector<std::shared_ptr<CPVRProvider>> CPVRProviders::GetProviders() const { + std::vector<std::shared_ptr<CPVRProvider>> providers; std::unique_lock<CCriticalSection> lock(m_critSection); - return m_providers.size(); + + //! @todo optimize; get rid of iteration. + providers.reserve(m_providers.size()); + for (const auto& provider : m_providers) + { + if (provider->IsClientProvider()) + { + // Ignore non installed / disabled client providers + const std::shared_ptr<const CPVRClient> client{ + CServiceBroker::GetPVRManager().GetClient(provider->GetClientId())}; + if (!client) + continue; + } + providers.emplace_back(provider); + } + return providers; +} + +std::size_t CPVRProviders::GetNumProviders() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return GetProviders().size(); } bool CPVRProviders::Update(const std::vector<std::shared_ptr<CPVRClient>>& clients) @@ -137,7 +159,7 @@ bool CPVRProviders::UpdateFromClients(const std::vector<std::shared_ptr<CPVRClie for (const auto& clientInfo : clientProviderInfos) { auto addonProvider = std::make_shared<CPVRProvider>( - clientInfo["clientid"].asInteger32(), clientInfo["name"].asString(), + clientInfo["clientid"].asInteger32(), clientInfo["fullname"].asString(), clientInfo["icon"].asString(), clientInfo["thumb"].asString()); newAddonProviderList.CheckAndAddEntry(addonProvider, ProviderUpdateMode::BY_CLIENT); diff --git a/xbmc/pvr/providers/PVRProviders.h b/xbmc/pvr/providers/PVRProviders.h index 8196d3b6d220a..c342e9738e228 100644 --- a/xbmc/pvr/providers/PVRProviders.h +++ b/xbmc/pvr/providers/PVRProviders.h @@ -44,12 +44,6 @@ class CPVRProvidersContainer */ std::vector<std::shared_ptr<CPVRProvider>> GetProvidersList() const; - /*! - * Get the number of providers in this container - * @return The total number of providers - */ - std::size_t GetNumProviders() const; - protected: void InsertEntry(const std::shared_ptr<CPVRProvider>& newProvider, ProviderUpdateMode updateMode); @@ -64,6 +58,18 @@ class CPVRProviders : public CPVRProvidersContainer CPVRProviders() = default; ~CPVRProviders() = default; + /*! + * Get all enabled providers + * @return The list of all enabled providers + */ + std::vector<std::shared_ptr<CPVRProvider>> GetProviders() const; + + /*! + * Get the number of enabled providers + * @return The total number of enabled providers + */ + std::size_t GetNumProviders() const; + /** * @brief Update all providers from PVR database and from given clients. * @param clients The PVR clients data should be loaded for. Leave empty for all clients. diff --git a/xbmc/pvr/recordings/PVRRecordings.cpp b/xbmc/pvr/recordings/PVRRecordings.cpp index b6049ee6fb6b7..363196b1d0bb1 100644 --- a/xbmc/pvr/recordings/PVRRecordings.cpp +++ b/xbmc/pvr/recordings/PVRRecordings.cpp @@ -187,6 +187,37 @@ std::shared_ptr<CPVRRecording> CPVRRecordings::GetById(int iClientId, return retVal; } +namespace +{ +bool MatchProvider(const std::shared_ptr<CPVRRecording>& recording, + bool isRadio, + int clientId, + int providerId) +{ + return recording->IsRadio() == isRadio && recording->ClientID() == clientId && + (providerId == PVR_PROVIDER_INVALID_UID || recording->ClientProviderUid() == providerId); +} +} // unnamed namespace + +bool CPVRRecordings::HasRecordingForProvider(bool isRadio, int clientId, int providerId) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return std::any_of(m_recordings.cbegin(), m_recordings.cend(), + [isRadio, clientId, providerId](const auto& entry) + { return MatchProvider(entry.second, isRadio, clientId, providerId); }); +} + +unsigned int CPVRRecordings::GetRecordingCountByProvider(bool isRadio, + int clientId, + int providerId) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + auto recs = std::count_if(m_recordings.cbegin(), m_recordings.cend(), + [isRadio, clientId, providerId](const auto& entry) + { return MatchProvider(entry.second, isRadio, clientId, providerId); }); + return static_cast<unsigned int>(recs); +} + void CPVRRecordings::UpdateFromClient(const std::shared_ptr<CPVRRecording>& tag, const CPVRClient& client) { diff --git a/xbmc/pvr/recordings/PVRRecordings.h b/xbmc/pvr/recordings/PVRRecordings.h index 7184b6c3e8e7b..4857da36189f9 100644 --- a/xbmc/pvr/recordings/PVRRecordings.h +++ b/xbmc/pvr/recordings/PVRRecordings.h @@ -92,6 +92,24 @@ class CPVRRecordings std::shared_ptr<CPVRRecording> GetById(int iClientId, const std::string& strRecordingId) const; std::shared_ptr<CPVRRecording> GetById(unsigned int iId) const; + /*! + * @brief Check whether at least one recording is offered by the given provider. + * @param isRadio Check radio or TV recordings. + * @param clientId The clientId to check. + * @param providerId The providerId to check. + * @return True, if at least one matching recording is offered by the provider, false otherwise. + */ + bool HasRecordingForProvider(bool isRadio, int clientId, int providerId) const; + + /*! + * @brief Get the total count of recordings offered by the given provider. + * @param isRadio Check radio or TV recordings. + * @param clientId The clientId of the provider. + * @param providerId The providerId. + * @return The total count of matching recordings. + */ + unsigned int GetRecordingCountByProvider(bool isRadio, int clientId, int providerId) const; + /*! * @brief Get the recording for the given epg tag, if any. * @param epgTag The epg tag. From 8ea0b3f2198e3847f07b87be76bef07a3285047f Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 21 Aug 2024 22:39:31 +0200 Subject: [PATCH 423/651] [Estuary][PVR][guilib][input][view] Providers: Initial implementation of Providers window. --- .../skin.estuary/media/DefaultPVRProvider.png | Bin 0 -> 11082 bytes .../media/DefaultPVRProviders.png | Bin 0 -> 10456 bytes addons/skin.estuary/xml/Includes.xml | 2 +- addons/skin.estuary/xml/Includes_PVR.xml | 26 +++- addons/skin.estuary/xml/MyPVRProviders.xml | 78 ++++++++++ addons/skin.estuary/xml/MyPVRRecordings.xml | 1 + addons/skin.estuary/xml/MyPVRTimers.xml | 1 + addons/skin.estuary/xml/Variables.xml | 4 + xbmc/FileItem.cpp | 3 + xbmc/guilib/GUIWindowManager.cpp | 8 +- xbmc/input/WindowTranslator.cpp | 2 + xbmc/pvr/providers/PVRProvider.cpp | 8 + xbmc/pvr/providers/PVRProvider.h | 6 +- xbmc/pvr/windows/CMakeLists.txt | 2 + xbmc/pvr/windows/GUIViewStatePVR.cpp | 35 +++++ xbmc/pvr/windows/GUIViewStatePVR.h | 10 ++ xbmc/pvr/windows/GUIWindowPVRProviders.cpp | 141 ++++++++++++++++++ xbmc/pvr/windows/GUIWindowPVRProviders.h | 51 +++++++ xbmc/view/GUIViewState.cpp | 6 + 19 files changed, 377 insertions(+), 7 deletions(-) create mode 100644 addons/skin.estuary/media/DefaultPVRProvider.png create mode 100644 addons/skin.estuary/media/DefaultPVRProviders.png create mode 100644 addons/skin.estuary/xml/MyPVRProviders.xml create mode 100644 xbmc/pvr/windows/GUIWindowPVRProviders.cpp create mode 100644 xbmc/pvr/windows/GUIWindowPVRProviders.h diff --git a/addons/skin.estuary/media/DefaultPVRProvider.png b/addons/skin.estuary/media/DefaultPVRProvider.png new file mode 100644 index 0000000000000000000000000000000000000000..b5dcd1a8cc78a345f869b219f907cae402edb04e GIT binary patch literal 11082 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 K$W%(12LB&&Z8f6+ literal 0 HcmV?d00001 diff --git a/addons/skin.estuary/media/DefaultPVRProviders.png b/addons/skin.estuary/media/DefaultPVRProviders.png new file mode 100644 index 0000000000000000000000000000000000000000..b9f7008f9394eca47c39f7ccafa6ff626464537b GIT binary patch literal 10456 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 azyKHybKk?Y$Ociq0Lt<ja#b>BLH`fCZ=S;d literal 0 HcmV?d00001 diff --git a/addons/skin.estuary/xml/Includes.xml b/addons/skin.estuary/xml/Includes.xml index 7675dae0e5e86..294f13e800217 100644 --- a/addons/skin.estuary/xml/Includes.xml +++ b/addons/skin.estuary/xml/Includes.xml @@ -1206,7 +1206,7 @@ <label>$INFO[Container.FolderName, / ]</label> <include>BreadcrumbsLabel</include> <visible>![Container.Content() + Window.IsActive(videos)]</visible> - <visible>![Window.IsActive(MyPVRChannels.xml) | Window.IsActive(MyPVRTimers.xml) | Window.IsActive(MyPVRRecordings.xml) | Window.IsActive(MyPVRSearch.xml)]</visible> + <visible>![Window.IsActive(MyPVRChannels.xml) | Window.IsActive(MyPVRTimers.xml) | Window.IsActive(MyPVRRecordings.xml) | Window.IsActive(MyPVRSearch.xml) | Window.IsActive(MyPVRProviders.xml)]</visible> </control> <control type="label"> <label>$INFO[Container.PluginCategory, / ]</label> diff --git a/addons/skin.estuary/xml/Includes_PVR.xml b/addons/skin.estuary/xml/Includes_PVR.xml index f0f5b0338543b..56d9bbb124905 100644 --- a/addons/skin.estuary/xml/Includes_PVR.xml +++ b/addons/skin.estuary/xml/Includes_PVR.xml @@ -462,25 +462,45 @@ <orientation>vertical</orientation> <focusedlayout height="100" width="780"> <control type="label"> + <visible>!String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible> <left>10</left> <height>90</height> <width>830</width> <aligny>center</aligny> - <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$INFO[ListItem.EpisodeName, (,)]</label> + <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName, (,)]$INFO[ListItem.Property(totalcount), (, $LOCALIZE[31036])]</label> + <shadowcolor>text_shadow</shadowcolor> + </control> + <control type="label"> + <visible>String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible> + <left>10</left> + <height>90</height> + <width>830</width> + <aligny>center</aligny> + <label>[COLOR grey]$INFO[ListItem.ChannelName,,[CR]][/COLOR]$INFO[ListItem.EpgEventTitle]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName, (,)]</label> <shadowcolor>text_shadow</shadowcolor> </control> </focusedlayout> <itemlayout height="100" width="780"> <control type="label"> + <visible>!String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible> + <left>10</left> + <height>90</height> + <width>830</width> + <aligny>center</aligny> + <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName, (,)]$INFO[ListItem.Property(totalcount), (, $LOCALIZE[31036])]</label> + <shadowcolor>text_shadow</shadowcolor> + </control> + <control type="label"> + <visible>String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible> <left>10</left> <height>90</height> <width>830</width> <aligny>center</aligny> - <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$INFO[ListItem.EpisodeName, (,)]</label> + <label>[COLOR grey]$INFO[ListItem.ChannelName,,[CR]][/COLOR]$INFO[ListItem.EpgEventTitle]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName, (,)]</label> <shadowcolor>text_shadow</shadowcolor> </control> </itemlayout> - <content sortby="date" sortorder="$PARAM[folder_sortorder]">$INFO[ListItem.FilenameAndPath]</content> + <content sortby="$PARAM[folder_sortby]" sortorder="$PARAM[folder_sortorder]">$INFO[ListItem.FilenameAndPath]</content> </control> </control> </control> diff --git a/addons/skin.estuary/xml/MyPVRProviders.xml b/addons/skin.estuary/xml/MyPVRProviders.xml new file mode 100644 index 0000000000000..eeff33a56fb33 --- /dev/null +++ b/addons/skin.estuary/xml/MyPVRProviders.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<window> + <defaultcontrol always="true">50</defaultcontrol> + <backgroundcolor>background</backgroundcolor> + <views>50</views> + <menucontrol>9000</menucontrol> + <controls> + <include>DefaultBackground</include> + <control type="group"> + <animation effect="fade" start="100" end="0" time="200" tween="sine" condition="$EXP[infodialog_active]">Conditional</animation> + <control type="group"> + <include>OpenClose_Left</include> + <control type="fixedlist" id="50"> + <left>0</left> + <top>list_top_offset</top> + <right>918</right> + <bottom>list_bottom_offset</bottom> + <onleft>9000</onleft> + <onright>73</onright> + <onup>50</onup> + <ondown>50</ondown> + <movement>4</movement> + <focusposition>4</focusposition> + <pagecontrol>73</pagecontrol> + <scrolltime tween="cubic" easing="out">500</scrolltime> + <include content="PVRListItemLayouts"> + <param name="list_id" value="50" /> + <param name="label1" value="$INFO[ListItem.Label]" /> + </include> + </control> + </control> + <control type="group"> + <depth>DepthContentPanel</depth> + <include>OpenClose_Right</include> + <width>870</width> + <right>0</right> + <include content="ContentPanel"> + <param name="left" value="-72" /> + <param name="width" value="970" /> + <param name="top" value="-20" /> + <param name="flipx" value="true" /> + </include> + <control type="scrollbar" id="73"> + <left>-50</left> + <top>list_top_offset</top> + <width>12</width> + <bottom>list_bottom_offset</bottom> + <onleft>50</onleft> + <onright>50</onright> + <orientation>vertical</orientation> + <animation effect="zoom" start="100,100" end="50,100" center="-50,0" time="300" tween="sine" easing="inout" condition="!Control.HasFocus(73)">conditional</animation> + </control> + <include content="PVRInfoPanel"> + <param name="folder_sortby" value="label" /> + <param name="folder_sortorder" value="ascending" /> + </include> + </control> + <include content="TopBar"> + <param name="breadcrumbs_label" value="$VAR[BreadcrumbsPVRProvidersVar]" /> + </include> + <include content="BottomBar"> + <param name="info_visible" value="true" /> + </include> + <control type="group"> + <include>MediaMenuCommon</include> + <include>PVRSideBar</include> + </control> + </control> + <control type="label" id="29"> + <font></font> + <include>HiddenObject</include> + </control> + <control type="label" id="30"> + <font></font> + <include>HiddenObject</include> + </control> + </controls> +</window> diff --git a/addons/skin.estuary/xml/MyPVRRecordings.xml b/addons/skin.estuary/xml/MyPVRRecordings.xml index 020f1ff0934b5..bc45ed139f326 100644 --- a/addons/skin.estuary/xml/MyPVRRecordings.xml +++ b/addons/skin.estuary/xml/MyPVRRecordings.xml @@ -54,6 +54,7 @@ <animation effect="zoom" start="100,100" end="50,100" center="-50,0" time="300" tween="sine" easing="inout" condition="!Control.HasFocus(73)">conditional</animation> </control> <include content="PVRInfoPanel"> + <param name="folder_sortby" value="date" /> <param name="folder_sortorder" value="descending" /> </include> </control> diff --git a/addons/skin.estuary/xml/MyPVRTimers.xml b/addons/skin.estuary/xml/MyPVRTimers.xml index 9f36dd5ff59f0..fdd2929b092e7 100644 --- a/addons/skin.estuary/xml/MyPVRTimers.xml +++ b/addons/skin.estuary/xml/MyPVRTimers.xml @@ -54,6 +54,7 @@ <animation effect="zoom" start="100,100" end="50,100" center="-50,0" time="300" tween="sine" easing="inout" condition="!Control.HasFocus(73)">conditional</animation> </control> <include content="PVRInfoPanel"> + <param name="folder_sortby" value="date" /> <param name="folder_sortorder" value="ascending" /> </include> </control> diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 4b0feff31735d..7350ff69513ae 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -530,6 +530,10 @@ <value condition="Window.IsActive(TVTimerRules)">$LOCALIZE[19020] / $LOCALIZE[19138]$INFO[Control.GetLabel(29), / ]</value> <value>$LOCALIZE[19021] / $LOCALIZE[19138]$INFO[Control.GetLabel(29), / ]</value> </variable> + <variable name="BreadcrumbsPVRProvidersVar"> + <value condition="Window.IsActive(TVProviders)">$LOCALIZE[19020] / $LOCALIZE[19334]$INFO[Control.GetLabel(29), / ]</value> + <value condition="Window.IsActive(RadioProviders)">$LOCALIZE[19021] / $LOCALIZE[19334]$INFO[Control.GetLabel(29), / ]</value> + </variable> <variable name="BreadcrumbsPVRSearchVar"> <value condition="Window.IsActive(TVSearch)">$LOCALIZE[19020] / $LOCALIZE[137]$INFO[Control.GetLabel(29), / ]$INFO[Control.GetLabel(30), ]</value> <value>$LOCALIZE[19021] / $LOCALIZE[137]$INFO[Control.GetLabel(29), / ]$INFO[Control.GetLabel(30), ]</value> diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index f7ab8e37d1310..7de2c22794514 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -812,6 +812,9 @@ void CFileItem::ToSortable(SortItem &sortable, Field field) const if (HasPVRChannelGroupMemberInfoTag()) GetPVRChannelGroupMemberInfoTag()->ToSortable(sortable, field); + if (HasPVRProviderInfoTag()) + GetPVRProviderInfoTag()->ToSortable(sortable, field); + if (HasAddonInfo()) { switch (field) diff --git a/xbmc/guilib/GUIWindowManager.cpp b/xbmc/guilib/GUIWindowManager.cpp index fac8742681cb2..065a68cbb8603 100644 --- a/xbmc/guilib/GUIWindowManager.cpp +++ b/xbmc/guilib/GUIWindowManager.cpp @@ -113,6 +113,7 @@ #include "video/dialogs/GUIDialogVideoSettings.h" /* PVR related include Files */ +#include "dialogs/GUIDialogSlider.h" #include "pvr/dialogs/GUIDialogPVRChannelGuide.h" #include "pvr/dialogs/GUIDialogPVRChannelManager.h" #include "pvr/dialogs/GUIDialogPVRChannelsOSD.h" @@ -127,13 +128,12 @@ #include "pvr/dialogs/GUIDialogPVRTimerSettings.h" #include "pvr/windows/GUIWindowPVRChannels.h" #include "pvr/windows/GUIWindowPVRGuide.h" +#include "pvr/windows/GUIWindowPVRProviders.h" #include "pvr/windows/GUIWindowPVRRecordings.h" #include "pvr/windows/GUIWindowPVRSearch.h" #include "pvr/windows/GUIWindowPVRTimerRules.h" #include "pvr/windows/GUIWindowPVRTimers.h" - #include "video/dialogs/GUIDialogTeletext.h" -#include "dialogs/GUIDialogSlider.h" #ifdef HAS_OPTICAL_DRIVE #include "dialogs/GUIDialogPlayEject.h" #endif @@ -269,12 +269,14 @@ void CGUIWindowManager::CreateWindows() Add(new CGUIWindowPVRTVTimers); Add(new CGUIWindowPVRTVTimerRules); Add(new CGUIWindowPVRTVSearch); + Add(new CGUIWindowPVRTVProviders); Add(new CGUIWindowPVRRadioChannels); Add(new CGUIWindowPVRRadioRecordings); Add(new CGUIWindowPVRRadioGuide); Add(new CGUIWindowPVRRadioTimers); Add(new CGUIWindowPVRRadioTimerRules); Add(new CGUIWindowPVRRadioSearch); + Add(new CGUIWindowPVRRadioProviders); Add(new CGUIDialogPVRRadioRDSInfo); Add(new CGUIDialogPVRGuideInfo); Add(new CGUIDialogPVRRecordingInfo); @@ -393,12 +395,14 @@ bool CGUIWindowManager::DestroyWindows() DestroyWindow(WINDOW_TV_TIMERS); DestroyWindow(WINDOW_TV_TIMER_RULES); DestroyWindow(WINDOW_TV_SEARCH); + DestroyWindow(WINDOW_TV_PROVIDERS); DestroyWindow(WINDOW_RADIO_CHANNELS); DestroyWindow(WINDOW_RADIO_RECORDINGS); DestroyWindow(WINDOW_RADIO_GUIDE); DestroyWindow(WINDOW_RADIO_TIMERS); DestroyWindow(WINDOW_RADIO_TIMER_RULES); DestroyWindow(WINDOW_RADIO_SEARCH); + DestroyWindow(WINDOW_RADIO_PROVIDERS); DestroyWindow(WINDOW_DIALOG_PVR_GUIDE_INFO); DestroyWindow(WINDOW_DIALOG_PVR_RECORDING_INFO); DestroyWindow(WINDOW_DIALOG_PVR_TIMER_SETTING); diff --git a/xbmc/input/WindowTranslator.cpp b/xbmc/input/WindowTranslator.cpp index c8c48374ce624..795c854f0f64c 100644 --- a/xbmc/input/WindowTranslator.cpp +++ b/xbmc/input/WindowTranslator.cpp @@ -36,11 +36,13 @@ const CWindowTranslator::WindowMapByName CWindowTranslator::WindowMappingByName {"tvguide", WINDOW_TV_GUIDE}, {"tvtimers", WINDOW_TV_TIMERS}, {"tvsearch", WINDOW_TV_SEARCH}, + {"tvproviders", WINDOW_TV_PROVIDERS}, {"radiochannels", WINDOW_RADIO_CHANNELS}, {"radiorecordings", WINDOW_RADIO_RECORDINGS}, {"radioguide", WINDOW_RADIO_GUIDE}, {"radiotimers", WINDOW_RADIO_TIMERS}, {"radiosearch", WINDOW_RADIO_SEARCH}, + {"radioproviders", WINDOW_RADIO_PROVIDERS}, {"gamecontrollers", WINDOW_DIALOG_GAME_CONTROLLERS}, {"gameports", WINDOW_DIALOG_GAME_PORTS}, {"games", WINDOW_GAMES}, diff --git a/xbmc/pvr/providers/PVRProvider.cpp b/xbmc/pvr/providers/PVRProvider.cpp index 99671aa889db7..428c890f6ca29 100644 --- a/xbmc/pvr/providers/PVRProvider.cpp +++ b/xbmc/pvr/providers/PVRProvider.cpp @@ -391,3 +391,11 @@ std::string CPVRProvider::GetClientThumbPath() const std::unique_lock<CCriticalSection> lock(m_critSection); return m_thumbPath.GetClientImage(); } + +void CPVRProvider::ToSortable(SortItem& sortable, Field field) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (field == FieldProvider) + sortable[FieldProvider] = StringUtils::Format( + "{} {} {} {}", m_iClientId, m_type == PVR_PROVIDER_TYPE_ADDON ? 0 : 1, m_type, m_strName); +} diff --git a/xbmc/pvr/providers/PVRProvider.h b/xbmc/pvr/providers/PVRProvider.h index 1e8d835061751..22f56b0178fab 100644 --- a/xbmc/pvr/providers/PVRProvider.h +++ b/xbmc/pvr/providers/PVRProvider.h @@ -12,6 +12,7 @@ #include "pvr/PVRCachedImage.h" #include "threads/CriticalSection.h" #include "utils/ISerializable.h" +#include "utils/ISortable.h" #include <memory> #include <string> @@ -29,7 +30,7 @@ enum class ProviderUpdateMode static constexpr int PVR_PROVIDER_ADDON_UID = -1; static constexpr int PVR_PROVIDER_INVALID_DB_ID = -1; -class CPVRProvider final : public ISerializable +class CPVRProvider final : public ISerializable, public ISortable { public: static const std::string IMAGE_OWNER_PATTERN; @@ -46,6 +47,9 @@ class CPVRProvider final : public ISerializable void Serialize(CVariant& value) const override; + // ISortable implementation + void ToSortable(SortItem& sortable, Field field) const override; + /*! * @brief The database id of this provider * diff --git a/xbmc/pvr/windows/CMakeLists.txt b/xbmc/pvr/windows/CMakeLists.txt index 9f5097ea720ad..7381b07edc0e8 100644 --- a/xbmc/pvr/windows/CMakeLists.txt +++ b/xbmc/pvr/windows/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES GUIViewStatePVR.cpp GUIWindowPVRBase.cpp GUIWindowPVRChannels.cpp GUIWindowPVRGuide.cpp + GUIWindowPVRProviders.cpp GUIWindowPVRRecordings.cpp GUIWindowPVRSearch.cpp GUIWindowPVRTimers.cpp @@ -12,6 +13,7 @@ set(HEADERS GUIViewStatePVR.h GUIWindowPVRBase.h GUIWindowPVRChannels.h GUIWindowPVRGuide.h + GUIWindowPVRProviders.h GUIWindowPVRRecordings.h GUIWindowPVRSearch.h GUIWindowPVRTimerRules.h diff --git a/xbmc/pvr/windows/GUIViewStatePVR.cpp b/xbmc/pvr/windows/GUIViewStatePVR.cpp index 874c98fd8952a..55eefd0f68941 100644 --- a/xbmc/pvr/windows/GUIViewStatePVR.cpp +++ b/xbmc/pvr/windows/GUIViewStatePVR.cpp @@ -14,6 +14,7 @@ #include "pvr/PVRManager.h" #include "pvr/addons/PVRClients.h" #include "pvr/epg/EpgSearchPath.h" +#include "pvr/providers/PVRProvidersPath.h" #include "pvr/recordings/PVRRecordingsPath.h" #include "pvr/timers/PVRTimersPath.h" #include "settings/AdvancedSettings.h" @@ -187,3 +188,37 @@ bool CGUIViewStateWindowPVRSearch::HideParentDirItems() return (CGUIViewState::HideParentDirItems() || CPVREpgSearchPath(m_items.GetPath()).IsSearchRoot()); } + +CGUIViewStateWindowPVRProviders::CGUIViewStateWindowPVRProviders(const int windowId, + const CFileItemList& items) + : CGUIViewStatePVR(windowId, items) +{ + AddSortMethod(SortByLabel, 551, // "Name" + LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty + + if (CPVRProvidersPath(m_items.GetPath()).IsProvidersRoot()) + { + AddSortMethod(SortByProvider, 19348, // "Provider" + LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty + + SetSortMethod(SortByProvider, SortOrderAscending); + } + else + { + SetSortMethod(SortByLabel, SortOrderAscending); + } + + LoadViewState(m_items.GetPath(), m_windowId); +} + +void CGUIViewStateWindowPVRProviders::SaveViewState() +{ + SaveViewToDb(m_items.GetPath(), m_windowId, + CViewStateSettings::GetInstance().Get("pvrproviders")); +} + +bool CGUIViewStateWindowPVRProviders::HideParentDirItems() +{ + return (CGUIViewState::HideParentDirItems() || + CPVRProvidersPath(m_items.GetPath()).IsProvidersRoot()); +} diff --git a/xbmc/pvr/windows/GUIViewStatePVR.h b/xbmc/pvr/windows/GUIViewStatePVR.h index ce39dd703bcae..09c51dff80671 100644 --- a/xbmc/pvr/windows/GUIViewStatePVR.h +++ b/xbmc/pvr/windows/GUIViewStatePVR.h @@ -71,6 +71,16 @@ class CGUIViewStateWindowPVRSearch : public CGUIViewStatePVR public: CGUIViewStateWindowPVRSearch(const int windowId, const CFileItemList& items); +protected: + void SaveViewState() override; + bool HideParentDirItems() override; +}; + +class CGUIViewStateWindowPVRProviders : public CGUIViewStatePVR +{ +public: + CGUIViewStateWindowPVRProviders(const int windowId, const CFileItemList& items); + protected: void SaveViewState() override; bool HideParentDirItems() override; diff --git a/xbmc/pvr/windows/GUIWindowPVRProviders.cpp b/xbmc/pvr/windows/GUIWindowPVRProviders.cpp new file mode 100644 index 0000000000000..48dd0a877d327 --- /dev/null +++ b/xbmc/pvr/windows/GUIWindowPVRProviders.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2024 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 "GUIWindowPVRProviders.h" + +#include "FileItemList.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "pvr/PVRManager.h" +#include "pvr/providers/PVRProvider.h" +#include "pvr/providers/PVRProviders.h" +#include "pvr/providers/PVRProvidersPath.h" +#include "utils/URIUtils.h" + +#include <memory> +#include <string> + +using namespace PVR; + +CGUIWindowPVRProvidersBase::CGUIWindowPVRProvidersBase(bool isRadio, + int id, + const std::string& xmlFile) + : CGUIWindowPVRBase(isRadio, id, xmlFile) +{ +} + +CGUIWindowPVRProvidersBase::~CGUIWindowPVRProvidersBase() = default; + +bool CGUIWindowPVRProvidersBase::OnAction(const CAction& action) +{ + if (action.GetID() == ACTION_PARENT_DIR || action.GetID() == ACTION_NAV_BACK) + { + const CPVRProvidersPath path{m_vecItems->GetPath()}; + if (path.IsValid() && !path.IsProvidersRoot()) + { + GoParentFolder(); + return true; + } + } + + return CGUIWindowPVRBase::OnAction(action); +} + +void CGUIWindowPVRProvidersBase::UpdateButtons() +{ + CGUIWindowPVRBase::UpdateButtons(); + + // Update window breadcrumb. + std::string header1; + const CPVRProvidersPath path{m_vecItems->GetPath()}; + if (path.IsProvider()) + { + const std::shared_ptr<const CPVRProvider> provider{ + CServiceBroker::GetPVRManager().Providers()->GetByClient(path.GetClientId(), + path.GetProviderUid())}; + if (provider) + header1 = provider->GetName(); + } + SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1, header1); +} + +bool CGUIWindowPVRProvidersBase::OnMessage(CGUIMessage& message) +{ + bool ret{false}; + switch (message.GetMessage()) + { + case GUI_MSG_CLICKED: + { + if (message.GetSenderId() == m_viewControl.GetCurrentControl()) + { + const int selectedItem{m_viewControl.GetSelectedItem()}; + if (selectedItem >= 0 && selectedItem < m_vecItems->Size()) + { + const std::shared_ptr<const CFileItem> item{m_vecItems->Get(selectedItem)}; + switch (message.GetParam1()) + { + case ACTION_SELECT_ITEM: + case ACTION_MOUSE_LEFT_CLICK: + { + const CPVRProvidersPath path{m_vecItems->GetPath()}; + if (path.IsValid()) + { + if (path.IsProvidersRoot()) + { + if (item->IsParentFolder()) + { + // Handle .. item, which is only visible if list of providers is empty. + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME); + ret = true; + break; + } + } + } + + if (item->m_bIsFolder) + { + // Folders and ".." folders in subfolders are handled by base class. + ret = false; + } + } + } + } + } + break; + } + } + + return ret || CGUIWindowPVRBase::OnMessage(message); +} + +std::string CGUIWindowPVRTVProviders::GetRootPath() const +{ + return CPVRProvidersPath::PATH_TV_PROVIDERS; +} + +std::string CGUIWindowPVRTVProviders::GetDirectoryPath() +{ + return URIUtils::PathHasParent(m_vecItems->GetPath(), CPVRProvidersPath::PATH_TV_PROVIDERS) + ? m_vecItems->GetPath() + : CPVRProvidersPath::PATH_TV_PROVIDERS; +} + +std::string CGUIWindowPVRRadioProviders::GetRootPath() const +{ + return CPVRProvidersPath::PATH_RADIO_PROVIDERS; +} + +std::string CGUIWindowPVRRadioProviders::GetDirectoryPath() +{ + return URIUtils::PathHasParent(m_vecItems->GetPath(), CPVRProvidersPath::PATH_RADIO_PROVIDERS) + ? m_vecItems->GetPath() + : CPVRProvidersPath::PATH_RADIO_PROVIDERS; +} diff --git a/xbmc/pvr/windows/GUIWindowPVRProviders.h b/xbmc/pvr/windows/GUIWindowPVRProviders.h new file mode 100644 index 0000000000000..71cb6f112f57f --- /dev/null +++ b/xbmc/pvr/windows/GUIWindowPVRProviders.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 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 "pvr/windows/GUIWindowPVRBase.h" + +#include <string> + +namespace PVR +{ +class CPVRProvidersPath; + +class CGUIWindowPVRProvidersBase : public CGUIWindowPVRBase +{ +public: + CGUIWindowPVRProvidersBase(bool isRadio, int id, const std::string& xmlFile); + ~CGUIWindowPVRProvidersBase() override; + + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction& action) override; + void UpdateButtons() override; +}; + +class CGUIWindowPVRTVProviders : public CGUIWindowPVRProvidersBase +{ +public: + CGUIWindowPVRTVProviders() + : CGUIWindowPVRProvidersBase(false, WINDOW_TV_PROVIDERS, "MyPVRProviders.xml") + { + } + std::string GetRootPath() const override; + std::string GetDirectoryPath() override; +}; + +class CGUIWindowPVRRadioProviders : public CGUIWindowPVRProvidersBase +{ +public: + CGUIWindowPVRRadioProviders() + : CGUIWindowPVRProvidersBase(true, WINDOW_RADIO_PROVIDERS, "MyPVRProviders.xml") + { + } + std::string GetRootPath() const override; + std::string GetDirectoryPath() override; +}; +} // namespace PVR diff --git a/xbmc/view/GUIViewState.cpp b/xbmc/view/GUIViewState.cpp index 3bd48bd64b3a9..b60f43d64edcc 100644 --- a/xbmc/view/GUIViewState.cpp +++ b/xbmc/view/GUIViewState.cpp @@ -146,6 +146,9 @@ CGUIViewState* CGUIViewState::GetViewState(int windowId, const CFileItemList& it if (windowId == WINDOW_TV_SEARCH) return new CGUIViewStateWindowPVRSearch(windowId, items); + if (windowId == WINDOW_TV_PROVIDERS) + return new CGUIViewStateWindowPVRProviders(windowId, items); + if (windowId == WINDOW_RADIO_CHANNELS) return new CGUIViewStateWindowPVRChannels(windowId, items); @@ -164,6 +167,9 @@ CGUIViewState* CGUIViewState::GetViewState(int windowId, const CFileItemList& it if (windowId == WINDOW_RADIO_SEARCH) return new CGUIViewStateWindowPVRSearch(windowId, items); + if (windowId == WINDOW_RADIO_PROVIDERS) + return new CGUIViewStateWindowPVRProviders(windowId, items); + if (windowId == WINDOW_PICTURES) return new CGUIViewStateWindowPictures(items); From 0991d2c5308863265932948e93d44131fd288c00 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 22 Aug 2024 13:10:55 +0200 Subject: [PATCH 424/651] [PVR] Cleanup: Align member and method names for client provider UID between CPVRChannel and CPVRRecording. --- xbmc/pvr/addons/PVRClient.cpp | 2 +- xbmc/pvr/channels/PVRChannel.h | 4 ++-- xbmc/pvr/filesystem/PVRGUIDirectory.cpp | 2 +- xbmc/pvr/recordings/PVRRecording.cpp | 24 ++++++++++++------------ xbmc/pvr/recordings/PVRRecording.h | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index ccbd861e7db5b..1a4fd862fa2f4 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -177,7 +177,7 @@ class CAddonRecording : public PVR_RECORDING iFlags = recording.Flags(); sizeInBytes = recording.GetSizeInBytes(); strProviderName = m_providerName.c_str(); - iClientProviderUid = recording.ClientProviderUniqueId(); + iClientProviderUid = recording.ClientProviderUid(); strParentalRatingCode = m_parentalRatingCode.c_str(); strParentalRatingIcon = m_parentalRatingIcon.c_str(); strParentalRatingSource = m_parentalRatingSource.c_str(); diff --git a/xbmc/pvr/channels/PVRChannel.h b/xbmc/pvr/channels/PVRChannel.h index 119b47c4d80a8..785b3d56aea8a 100644 --- a/xbmc/pvr/channels/PVRChannel.h +++ b/xbmc/pvr/channels/PVRChannel.h @@ -446,8 +446,8 @@ class CPVRChannel : public ISerializable, public ISortable int ClientOrder() const { return m_iClientOrder; } /*! - * @brief Get the client provider Uid for this channel - * @return m_iClientProviderUid The provider Uid for this channel + * @brief Get the uid of the provider on the client which this channel is from + * @return the client uid of the provider or PVR_PROVIDER_INVALID_UID */ int ClientProviderUid() const { return m_iClientProviderUid; } diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp index be92565f57ee9..cead6e3b7d145 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp @@ -878,7 +878,7 @@ bool CPVRGUIDirectory::GetProvidersDirectory(CFileItemList& results) const if (recording->ClientID() != path.GetClientId()) continue; - if (checkUid && recording->ClientProviderUniqueId() != path.GetProviderUid()) + if (checkUid && recording->ClientProviderUid() != path.GetProviderUid()) continue; results.Add(std::make_shared<CFileItem>(recording)); diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp index 6d99c6ad105c9..7e9c4fae5af6e 100644 --- a/xbmc/pvr/recordings/PVRRecording.cpp +++ b/xbmc/pvr/recordings/PVRRecording.cpp @@ -118,12 +118,12 @@ CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClien m_sizeInBytes = recording.sizeInBytes; if (recording.strProviderName) m_strProviderName = recording.strProviderName; - m_iClientProviderUniqueId = recording.iClientProviderUid; + m_iClientProviderUid = recording.iClientProviderUid; // Workaround for C++ PVR Add-on API wrapper not initializing this value correctly until API 9.0.1 //! @todo Remove with next incompatible API bump. - if (m_iClientProviderUniqueId == 0) - m_iClientProviderUniqueId = PVR_PROVIDER_INVALID_UID; + if (m_iClientProviderUid == 0) + m_iClientProviderUid = PVR_PROVIDER_INVALID_UID; SetGenre(recording.iGenreType, recording.iGenreSubType, recording.strGenreDescription ? recording.strGenreDescription : ""); @@ -193,7 +193,7 @@ bool CPVRRecording::operator==(const CPVRRecording& right) const m_iGenreSubType == right.m_iGenreSubType && m_firstAired == right.m_firstAired && m_iFlags == right.m_iFlags && m_sizeInBytes == right.m_sizeInBytes && m_strProviderName == right.m_strProviderName && - m_iClientProviderUniqueId == right.m_iClientProviderUniqueId && + m_iClientProviderUid == right.m_iClientProviderUid && m_parentalRating == right.m_parentalRating && m_parentalRatingCode == right.m_parentalRatingCode && m_parentalRatingIcon == right.m_parentalRatingIcon && @@ -244,7 +244,7 @@ void CPVRRecording::ToSortable(SortItem& sortable, Field field) const if (field == FieldSize) sortable[FieldSize] = m_sizeInBytes; else if (field == FieldProvider) - sortable[FieldProvider] = StringUtils::Format("{} {}", m_iClientId, m_iClientProviderUniqueId); + sortable[FieldProvider] = StringUtils::Format("{} {}", m_iClientId, m_iClientProviderUid); else CVideoInfoTag::ToSortable(sortable, field); } @@ -274,7 +274,7 @@ void CPVRRecording::Reset() m_sizeInBytes = 0; } m_strProviderName.clear(); - m_iClientProviderUniqueId = PVR_PROVIDER_INVALID_UID; + m_iClientProviderUid = PVR_PROVIDER_INVALID_UID; m_recordingTime.Reset(); @@ -473,7 +473,7 @@ void CPVRRecording::Update(const CPVRRecording& tag, const CPVRClient& client) std::unique_lock<CCriticalSection> lock(m_critSection); m_sizeInBytes = tag.m_sizeInBytes; m_strProviderName = tag.m_strProviderName; - m_iClientProviderUniqueId = tag.m_iClientProviderUniqueId; + m_iClientProviderUid = tag.m_iClientProviderUid; } if (client.GetClientCapabilities().SupportsRecordingsPlayCount()) @@ -697,10 +697,10 @@ int64_t CPVRRecording::GetSizeInBytes() const return m_sizeInBytes; } -int CPVRRecording::ClientProviderUniqueId() const +int CPVRRecording::ClientProviderUid() const { std::unique_lock<CCriticalSection> lock(m_critSection); - return m_iClientProviderUniqueId; + return m_iClientProviderUid; } std::string CPVRRecording::ProviderName() const @@ -718,13 +718,13 @@ std::shared_ptr<CPVRProvider> CPVRRecording::GetDefaultProvider() const bool CPVRRecording::HasClientProvider() const { std::unique_lock<CCriticalSection> lock(m_critSection); - return m_iClientProviderUniqueId != PVR_PROVIDER_INVALID_UID; + return m_iClientProviderUid != PVR_PROVIDER_INVALID_UID; } std::shared_ptr<CPVRProvider> CPVRRecording::GetProvider() const { - auto provider = CServiceBroker::GetPVRManager().Providers()->GetByClient( - m_iClientId, m_iClientProviderUniqueId); + auto provider = + CServiceBroker::GetPVRManager().Providers()->GetByClient(m_iClientId, m_iClientProviderUid); if (!provider) provider = GetDefaultProvider(); diff --git a/xbmc/pvr/recordings/PVRRecording.h b/xbmc/pvr/recordings/PVRRecording.h index 2e70b48c94791..4e70fc6ffeec3 100644 --- a/xbmc/pvr/recordings/PVRRecording.h +++ b/xbmc/pvr/recordings/PVRRecording.h @@ -471,7 +471,7 @@ class CPVRRecording final : public CVideoInfoTag * @brief Get the uid of the provider on the client which this recording is from * @return the client uid of the provider or PVR_PROVIDER_INVALID_UID */ - int ClientProviderUniqueId() const; + int ClientProviderUid() const; /*! * @brief Get the client provider name for this recording @@ -562,7 +562,7 @@ class CPVRRecording final : public CVideoInfoTag int64_t m_sizeInBytes = 0; /*!< the size of the recording in bytes */ bool m_bDirty = false; std::string m_strProviderName; /*!< name of the provider this recording is from */ - int m_iClientProviderUniqueId = + int m_iClientProviderUid = PVR_PROVIDER_INVALID_UID; /*!< provider uid associated with this recording on the client */ unsigned int m_parentalRating{0}; /*!< parental rating */ std::string m_parentalRatingCode; /*!< Parental rating code */ From 1f9423d2b4377a504870fe4f2fe9388e259267cd Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 21 Aug 2024 22:45:42 +0200 Subject: [PATCH 425/651] [PVR] Providers: VFS: Add filter 'by provider' for recordings and channels listings. --- xbmc/pvr/CMakeLists.txt | 2 + xbmc/pvr/PVRPathUtils.cpp | 70 +++++++++++++++++++++++++ xbmc/pvr/PVRPathUtils.h | 40 ++++++++++++++ xbmc/pvr/filesystem/PVRGUIDirectory.cpp | 63 +++++++++++++++++++--- 4 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 xbmc/pvr/PVRPathUtils.cpp create mode 100644 xbmc/pvr/PVRPathUtils.h diff --git a/xbmc/pvr/CMakeLists.txt b/xbmc/pvr/CMakeLists.txt index a0c262603d35d..002b10b7842bc 100644 --- a/xbmc/pvr/CMakeLists.txt +++ b/xbmc/pvr/CMakeLists.txt @@ -9,6 +9,7 @@ set(SOURCES PVRCachedImage.cpp PVREventLogJob.cpp PVRItem.cpp PVRManager.cpp + PVRPathUtils.cpp PVRPlaybackState.cpp PVRStreamProperties.cpp PVRThumbLoader.cpp) @@ -27,6 +28,7 @@ set(HEADERS IPVRComponent.h PVREventLogJob.h PVRItem.h PVRManager.h + PVRPathUtils.h PVRPlaybackState.h PVRSignalStatus.h PVRStreamProperties.h diff --git a/xbmc/pvr/PVRPathUtils.cpp b/xbmc/pvr/PVRPathUtils.cpp new file mode 100644 index 0000000000000..a529f7d274148 --- /dev/null +++ b/xbmc/pvr/PVRPathUtils.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 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 "PVRPathUtils.h" + +#include "ServiceBroker.h" +#include "URL.h" +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h" // PVR_PROVIDER_INVALID_UID +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID +#include "pvr/PVRManager.h" +#include "pvr/providers/PVRProvider.h" +#include "pvr/providers/PVRProviders.h" +#include "utils/StringUtils.h" + +#include <cstdlib> + +namespace PVR::UTILS +{ + +bool HasClientAndProvider(const std::string& path) +{ + const CURL url{path}; + const std::string clientIdStr{url.GetOption("clientid")}; + const std::string providerIdStr{url.GetOption("providerid")}; + return (!clientIdStr.empty() && !providerIdStr.empty() && StringUtils::IsInteger(clientIdStr) && + StringUtils::IsInteger(providerIdStr)); +} + +bool GetClientAndProviderFromPath(const CURL& url, int& clientId, int& providerId) +{ + const std::string clientIdStr{url.GetOption("clientid")}; + const std::string providerIdStr{url.GetOption("providerid")}; + const bool filterByClientAndProvider{!clientIdStr.empty() && !providerIdStr.empty() && + StringUtils::IsInteger(clientIdStr) && + StringUtils::IsInteger(providerIdStr)}; + if (filterByClientAndProvider) + { + clientId = std::atoi(clientIdStr.c_str()); + providerId = std::atoi(providerIdStr.c_str()); + return true; + } + else + { + clientId = PVR_CLIENT_INVALID_UID; + providerId = PVR_PROVIDER_INVALID_UID; + return false; + } +} + +std::string GetProviderNameFromPath(const std::string& path) +{ + const CURL url{path}; + int clientId{PVR_CLIENT_INVALID_UID}; + int providerId{PVR_PROVIDER_INVALID_UID}; + if (GetClientAndProviderFromPath(url, clientId, providerId)) + { + const std::shared_ptr<const CPVRProvider> provider{ + CServiceBroker::GetPVRManager().Providers()->GetByClient(clientId, providerId)}; + if (provider) + return provider->GetName(); + } + return {}; +} + +} // namespace PVR::UTILS diff --git a/xbmc/pvr/PVRPathUtils.h b/xbmc/pvr/PVRPathUtils.h new file mode 100644 index 0000000000000..3092aa5188469 --- /dev/null +++ b/xbmc/pvr/PVRPathUtils.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 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 <string> + +class CURL; + +namespace PVR::UTILS +{ +/*! + * @brief Check whether the given path contains a client id and a provider id. + * @param path The path. + * @return True if both client id and provider id are present, false otherwise. + */ +bool HasClientAndProvider(const std::string& path); + +/*! + * @brief Get client id and provider id from the given URL. + * @param url The URL. + * @param clientId Filled with the client id on success, PVR_CLIENT_INVALID_UID otherwise + * @param providerId Filled with the provider id on success, PVR_PROVIDER_INVALID_UID otherwise + * @return True on success, false otherwise. + */ +bool GetClientAndProviderFromPath(const CURL& url, int& clientId, int& providerId); + +/*! + * @brief Get the name of a provider from the given path. + * @param path The path. + * @return the name on success, an empty string otherwise. + */ +std::string GetProviderNameFromPath(const std::string& path); + +} // namespace PVR::UTILS diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp index cead6e3b7d145..d53e8279a9b8d 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp @@ -16,6 +16,7 @@ #include "input/WindowTranslator.h" #include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID #include "pvr/PVRManager.h" +#include "pvr/PVRPathUtils.h" #include "pvr/addons/PVRClient.h" #include "pvr/addons/PVRClients.h" #include "pvr/channels/PVRChannel.h" @@ -310,9 +311,39 @@ bool IsDirectoryMember(const std::string& strDirectory, return StringUtils::StartsWithNoCase(strUseEntryDirectory, strUseDirectory); } -void GetSubDirectories(const CPVRRecordingsPath& recParentPath, - const std::vector<std::shared_ptr<CPVRRecording>>& recordings, - CFileItemList& results) +template<class T> +class CByClientAndProviderFilter +{ +public: + explicit CByClientAndProviderFilter(const CURL& url) + : m_applyFilter(UTILS::GetClientAndProviderFromPath(url, m_clientId, m_providerId)) + { + } + + bool Filter(const std::shared_ptr<T>& item) const + { + if (m_applyFilter) + { + if (item->ClientID() != m_clientId) + return true; + + if ((m_providerId != PVR_PROVIDER_INVALID_UID) && (item->ClientProviderUid() != m_providerId)) + return true; + } + return false; + } + +private: + int m_clientId{PVR_CLIENT_INVALID_UID}; + int m_providerId{PVR_PROVIDER_INVALID_UID}; + bool m_applyFilter{false}; +}; + +template<typename T> +void GetGetRecordingsSubDirectories(const CPVRRecordingsPath& recParentPath, + const std::vector<std::shared_ptr<CPVRRecording>>& recordings, + const CByClientAndProviderFilter<T>& byClientAndProviderFilter, + CFileItemList& results) { // Only active recordings are fetched to provide sub directories. // Not applicable for deleted view which is supposed to be flattened. @@ -321,6 +352,9 @@ void GetSubDirectories(const CPVRRecordingsPath& recParentPath, for (const auto& recording : recordings) { + if (byClientAndProviderFilter.Filter(recording)) + continue; + if (recording->IsDeleted()) continue; @@ -425,17 +459,24 @@ bool CPVRGUIDirectory::GetRecordingsDirectory(CFileItemList& results) const const CPVRRecordingsPath recPath(m_url.GetWithoutOptions()); if (recPath.IsValid()) { + // Filter by client id/provider id ? + const CByClientAndProviderFilter<CPVRRecording> byClientAndProviderFilter{m_url}; + // Get the directory structure if in non-flatten mode // Deleted view is always flatten. So only for an active view const std::string strDirectory = recPath.GetUnescapedDirectoryPath(); if (!recPath.IsDeleted() && bGrouped) - GetSubDirectories(recPath, recordings, results); + GetGetRecordingsSubDirectories(recPath, recordings, byClientAndProviderFilter, results); // get all files of the current directory or recursively all files starting at the current directory if in flatten mode std::shared_ptr<CFileItem> item; for (const auto& recording : recordings) { // Omit recordings not matching criteria + + if (byClientAndProviderFilter.Filter(recording)) + continue; + if (recording->IsDeleted() != recPath.IsDeleted() || recording->IsRadio() != recPath.IsRadio() || !IsDirectoryMember(strDirectory, recording->Directory(), bGrouped)) @@ -636,6 +677,7 @@ bool CPVRGUIDirectory::GetChannelsDirectory(CFileItemList& results) const } else if (path.IsChannelGroup()) { + const CByClientAndProviderFilter<const CPVRChannel> byClientAndProviderFilter{m_url}; const bool playedOnly{(m_url.HasOption("view") && (m_url.GetOption("view") == "lastplayed"))}; const bool dateAdded{(m_url.HasOption("view") && (m_url.GetOption("view") == "dateadded"))}; const bool showHiddenChannels{path.IsHiddenChannelGroup()}; @@ -643,18 +685,23 @@ bool CPVRGUIDirectory::GetChannelsDirectory(CFileItemList& results) const GetChannelGroupMembers(path)}; for (const auto& groupMember : groupMembers) { - if (showHiddenChannels != groupMember->Channel()->IsHidden()) + const std::shared_ptr<const CPVRChannel> channel{groupMember->Channel()}; + + if (byClientAndProviderFilter.Filter(channel)) + continue; + + if (showHiddenChannels != channel->IsHidden()) continue; - if (playedOnly && !groupMember->Channel()->LastWatched()) + if (playedOnly && !channel->LastWatched()) continue; if (dateAdded) { - if (groupMember->Channel()->LastWatched()) + if (channel->LastWatched()) continue; - const CDateTime dtChannelAdded{groupMember->Channel()->DateTimeAdded()}; + const CDateTime dtChannelAdded{channel->DateTimeAdded()}; if (!dtChannelAdded.IsValid()) continue; From beb3428030b32cf11f1148b88fd3be0638f612cb Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:04:15 +0200 Subject: [PATCH 426/651] [Estuary][PVR] Providers: Implement filter 'by provider' for Recordings and Channels window. Activate filtered channels and recordings window from Providers window. --- addons/skin.estuary/xml/Variables.xml | 12 ++--- xbmc/pvr/windows/GUIWindowPVRBase.cpp | 13 ++++++ xbmc/pvr/windows/GUIWindowPVRBase.h | 4 +- xbmc/pvr/windows/GUIWindowPVRChannels.cpp | 31 +++++++------ xbmc/pvr/windows/GUIWindowPVRChannels.h | 9 ++-- xbmc/pvr/windows/GUIWindowPVRGuide.cpp | 2 +- xbmc/pvr/windows/GUIWindowPVRProviders.cpp | 50 +++++++++++++++++++++ xbmc/pvr/windows/GUIWindowPVRProviders.h | 4 ++ xbmc/pvr/windows/GUIWindowPVRRecordings.cpp | 34 ++++++++------ xbmc/pvr/windows/GUIWindowPVRRecordings.h | 7 ++- 10 files changed, 121 insertions(+), 45 deletions(-) diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 7350ff69513ae..e5fd8b989b250 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -511,18 +511,18 @@ <value>$LOCALIZE[3]</value> </variable> <variable name="BreadcrumbsPVRChannelsVar"> - <value condition="Window.IsActive(TVChannels)">$LOCALIZE[19020] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)]</value> - <value>$LOCALIZE[19021] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)]</value> + <value condition="Window.IsActive(TVChannels)">$LOCALIZE[19020] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)]$INFO[Control.GetLabel(30), - ]</value> + <value>$LOCALIZE[19021] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)]$INFO[Control.GetLabel(30), - ]</value> </variable> <variable name="BreadcrumbsPVRGuideVar"> <value condition="Window.IsActive(TVGuide)">$LOCALIZE[19020] / $LOCALIZE[19069] / $INFO[Control.GetLabel(30)]</value> <value>$LOCALIZE[19021] / $LOCALIZE[19069] / $INFO[Control.GetLabel(30)]</value> </variable> <variable name="BreadcrumbsPVRRecordingsVar"> - <value condition="Window.IsActive(TVRecordings) + String.Contains(Control.GetLabel(7),*)">$LOCALIZE[19020] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ] - $LOCALIZE[19179]</value> - <value condition="Window.IsActive(TVRecordings) + !String.Contains(Control.GetLabel(7),*)">$LOCALIZE[19020] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ]</value> - <value condition="Window.IsActive(RadioRecordings) + String.Contains(Control.GetLabel(7),*)">$LOCALIZE[19021] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ] - $LOCALIZE[19179]</value> - <value>$LOCALIZE[19021] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ]</value> + <value condition="Window.IsActive(TVRecordings) + String.Contains(Control.GetLabel(7),*)">$LOCALIZE[19020] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ]$INFO[Control.GetLabel(29), - ] - $LOCALIZE[19179]</value> + <value condition="Window.IsActive(TVRecordings) + !String.Contains(Control.GetLabel(7),*)">$LOCALIZE[19020] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ]$INFO[Control.GetLabel(29), - ]</value> + <value condition="Window.IsActive(RadioRecordings) + String.Contains(Control.GetLabel(7),*)">$LOCALIZE[19021] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ]$INFO[Control.GetLabel(29), - ] - $LOCALIZE[19179]</value> + <value>$LOCALIZE[19021] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ]$INFO[Control.GetLabel(29), - ]</value> </variable> <variable name="BreadcrumbsPVRTimersVar"> <value condition="Window.IsActive(TVTimers)">$LOCALIZE[19020] / $LOCALIZE[19040]</value> diff --git a/xbmc/pvr/windows/GUIWindowPVRBase.cpp b/xbmc/pvr/windows/GUIWindowPVRBase.cpp index 2af8531134cc6..a3dd76cfec719 100644 --- a/xbmc/pvr/windows/GUIWindowPVRBase.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRBase.cpp @@ -12,6 +12,7 @@ #include "FileItemList.h" #include "GUIUserMessages.h" #include "ServiceBroker.h" +#include "URL.h" #include "addons/AddonManager.h" #include "addons/addoninfo/AddonType.h" #include "dialogs/GUIDialogExtendedProgressBar.h" @@ -561,6 +562,18 @@ void CGUIWindowPVRBase::SetChannelGroup(std::shared_ptr<CPVRChannelGroup> &&grou } } +void CGUIWindowPVRBase::SetChannelGroupPath(const std::string& path) +{ + const CURL url{path}; + const std::string pathWithoutOptions{url.GetWithoutOptions()}; + + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_channelGroupPath != pathWithoutOptions) + { + m_channelGroupPath = pathWithoutOptions; + } +} + bool CGUIWindowPVRBase::Update(const std::string& strDirectory, bool updateFilterPath /*= true*/) { if (m_bUpdating) diff --git a/xbmc/pvr/windows/GUIWindowPVRBase.h b/xbmc/pvr/windows/GUIWindowPVRBase.h index f8c73bfe7e67b..0e3ab3ce472a6 100644 --- a/xbmc/pvr/windows/GUIWindowPVRBase.h +++ b/xbmc/pvr/windows/GUIWindowPVRBase.h @@ -110,10 +110,11 @@ namespace PVR */ void SetChannelGroup(std::shared_ptr<CPVRChannelGroup> &&group, bool bUpdate = true); + void SetChannelGroupPath(const std::string& path); + virtual void UpdateSelectedItemPath(); CCriticalSection m_critSection; - std::string m_channelGroupPath; bool m_bRadio; std::atomic_bool m_bUpdating = {false}; @@ -135,5 +136,6 @@ namespace PVR XbmcThreads::EndTime<> m_refreshTimeout; CGUIDialogProgressBarHandle* m_progressHandle = nullptr; /*!< progress dialog that is displayed while the pvr manager is loading */ + std::string m_channelGroupPath; }; } diff --git a/xbmc/pvr/windows/GUIWindowPVRChannels.cpp b/xbmc/pvr/windows/GUIWindowPVRChannels.cpp index 8ccbdd68faffb..add693fb2d04c 100644 --- a/xbmc/pvr/windows/GUIWindowPVRChannels.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRChannels.cpp @@ -24,6 +24,7 @@ #include "input/actions/Action.h" #include "input/actions/ActionIDs.h" #include "pvr/PVRManager.h" +#include "pvr/PVRPathUtils.h" #include "pvr/channels/PVRChannel.h" #include "pvr/channels/PVRChannelGroup.h" #include "pvr/channels/PVRChannelGroupMember.h" @@ -37,6 +38,7 @@ #include "pvr/guilib/PVRGUIActionsEPG.h" #include "pvr/guilib/PVRGUIActionsPlayback.h" #include "utils/StringUtils.h" +#include "utils/URIUtils.h" #include "utils/Variant.h" #include <mutex> @@ -58,6 +60,15 @@ CGUIWindowPVRChannelsBase::~CGUIWindowPVRChannelsBase() this); } +std::string CGUIWindowPVRChannelsBase::GetDirectoryPath() +{ + const std::string basePath{CPVRChannelsPath(m_bRadio, m_bShowHiddenChannels, + GetChannelGroup()->GroupName(), + GetChannelGroup()->GetClientID())}; + return URIUtils::PathHasParent(m_vecItems->GetPath(), basePath) ? m_vecItems->GetPath() + : basePath; +} + std::string CGUIWindowPVRChannelsBase::GetRootPath() const { //! @todo Would it make sense to change GetRootPath() declaration in CGUIMediaWindow @@ -121,8 +132,12 @@ void CGUIWindowPVRChannelsBase::UpdateButtons() } CGUIWindowPVRBase::UpdateButtons(); + SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1, m_bShowHiddenChannels ? g_localizeStrings.Get(19022) : GetChannelGroup()->GroupName()); + + // If we are filtering by client id / provider id, expose provider's name. + SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2, UTILS::GetProviderNameFromPath(m_vecItems->GetPath())); } bool CGUIWindowPVRChannelsBase::OnAction(const CAction& action) @@ -167,11 +182,11 @@ bool CGUIWindowPVRChannelsBase::OnMessage(CGUIMessage& message) // Replace wildcard with real group name const auto group = CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(path.IsRadio()); - m_channelGroupPath = group->GetPath(); + SetChannelGroupPath(group->GetPath()); } else { - m_channelGroupPath = message.GetStringParam(0); + SetChannelGroupPath(message.GetStringParam(0)); } } break; @@ -411,19 +426,7 @@ CGUIWindowPVRTVChannels::CGUIWindowPVRTVChannels() { } -std::string CGUIWindowPVRTVChannels::GetDirectoryPath() -{ - return CPVRChannelsPath(false, m_bShowHiddenChannels, GetChannelGroup()->GroupName(), - GetChannelGroup()->GetClientID()); -} - CGUIWindowPVRRadioChannels::CGUIWindowPVRRadioChannels() : CGUIWindowPVRChannelsBase(true, WINDOW_RADIO_CHANNELS, "MyPVRChannels.xml") { } - -std::string CGUIWindowPVRRadioChannels::GetDirectoryPath() -{ - return CPVRChannelsPath(true, m_bShowHiddenChannels, GetChannelGroup()->GroupName(), - GetChannelGroup()->GetClientID()); -} diff --git a/xbmc/pvr/windows/GUIWindowPVRChannels.h b/xbmc/pvr/windows/GUIWindowPVRChannels.h index 257b595264f19..d4684ac0682b4 100644 --- a/xbmc/pvr/windows/GUIWindowPVRChannels.h +++ b/xbmc/pvr/windows/GUIWindowPVRChannels.h @@ -34,6 +34,9 @@ class CGUIWindowPVRChannelsBase : public CGUIWindowPVRBase, public CPVRChannelNu void GetChannelNumbers(std::vector<std::string>& channelNumbers) override; void OnInputDone() override; +protected: + std::string GetDirectoryPath() override; + private: bool OnContextButtonManage(const CFileItemPtr& item, CONTEXT_BUTTON button); @@ -49,17 +52,11 @@ class CGUIWindowPVRTVChannels : public CGUIWindowPVRChannelsBase { public: CGUIWindowPVRTVChannels(); - -protected: - std::string GetDirectoryPath() override; }; class CGUIWindowPVRRadioChannels : public CGUIWindowPVRChannelsBase { public: CGUIWindowPVRRadioChannels(); - -protected: - std::string GetDirectoryPath() override; }; } // namespace PVR diff --git a/xbmc/pvr/windows/GUIWindowPVRGuide.cpp b/xbmc/pvr/windows/GUIWindowPVRGuide.cpp index 6ce75fd5f2bc8..fb8c909797efc 100644 --- a/xbmc/pvr/windows/GUIWindowPVRGuide.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRGuide.cpp @@ -402,7 +402,7 @@ bool CGUIWindowPVRGuideBase::OnMessage(CGUIMessage& message) { // if a path to a channel group is given we must init // that group instead of last played/selected group - m_channelGroupPath = message.GetStringParam(0); + SetChannelGroupPath(message.GetStringParam(0)); } break; } diff --git a/xbmc/pvr/windows/GUIWindowPVRProviders.cpp b/xbmc/pvr/windows/GUIWindowPVRProviders.cpp index 48dd0a877d327..5f544fa3dd7cc 100644 --- a/xbmc/pvr/windows/GUIWindowPVRProviders.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRProviders.cpp @@ -15,9 +15,14 @@ #include "input/actions/Action.h" #include "input/actions/ActionIDs.h" #include "pvr/PVRManager.h" +#include "pvr/channels/PVRChannelGroup.h" +#include "pvr/channels/PVRChannelGroupsContainer.h" +#include "pvr/channels/PVRChannelsPath.h" #include "pvr/providers/PVRProvider.h" #include "pvr/providers/PVRProviders.h" #include "pvr/providers/PVRProvidersPath.h" +#include "pvr/recordings/PVRRecordingsPath.h" +#include "utils/StringUtils.h" #include "utils/URIUtils.h" #include <memory> @@ -98,6 +103,22 @@ bool CGUIWindowPVRProvidersBase::OnMessage(CGUIMessage& message) break; } } + else if (path.IsProvider()) + { + const CPVRProvidersPath selectedPath{item->GetPath()}; + if (selectedPath.IsChannels()) + { + ActivateChannelsWindow(selectedPath); + ret = true; + break; + } + else if (selectedPath.IsRecordings()) + { + ActivateRecordingsWindow(selectedPath); + ret = true; + break; + } + } } if (item->m_bIsFolder) @@ -116,6 +137,35 @@ bool CGUIWindowPVRProvidersBase::OnMessage(CGUIMessage& message) return ret || CGUIWindowPVRBase::OnMessage(message); } +void CGUIWindowPVRProvidersBase::ActivateChannelsWindow(const CPVRProvidersPath& selectedPath) +{ + const CPVRManager& pvrMgr{CServiceBroker::GetPVRManager()}; + const std::shared_ptr<CPVRChannelGroup> allChannelsGroup{ + pvrMgr.ChannelGroups()->GetGroupAll(selectedPath.IsRadio())}; + + std::string targetPath{CPVRChannelsPath(selectedPath.IsRadio(), allChannelsGroup->GroupName(), + allChannelsGroup->GetClientID())}; + targetPath = StringUtils::Format("{}?clientid={}&providerid={}", targetPath, + selectedPath.GetClientId(), selectedPath.GetProviderUid()); + + // Activate channels window. + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow( + selectedPath.IsRadio() ? WINDOW_RADIO_CHANNELS : WINDOW_TV_CHANNELS, targetPath); +} + +void CGUIWindowPVRProvidersBase::ActivateRecordingsWindow(const CPVRProvidersPath& selectedPath) +{ + std::string targetPath{CPVRRecordingsPath(selectedPath.IsRadio() + ? CPVRRecordingsPath::PATH_ACTIVE_RADIO_RECORDINGS + : CPVRRecordingsPath::PATH_ACTIVE_TV_RECORDINGS)}; + targetPath = StringUtils::Format("{}?clientid={}&providerid={}", targetPath, + selectedPath.GetClientId(), selectedPath.GetProviderUid()); + + // Activate recordings window. + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow( + selectedPath.IsRadio() ? WINDOW_RADIO_RECORDINGS : WINDOW_TV_RECORDINGS, targetPath); +} + std::string CGUIWindowPVRTVProviders::GetRootPath() const { return CPVRProvidersPath::PATH_TV_PROVIDERS; diff --git a/xbmc/pvr/windows/GUIWindowPVRProviders.h b/xbmc/pvr/windows/GUIWindowPVRProviders.h index 71cb6f112f57f..9ad29a0d84388 100644 --- a/xbmc/pvr/windows/GUIWindowPVRProviders.h +++ b/xbmc/pvr/windows/GUIWindowPVRProviders.h @@ -25,6 +25,10 @@ class CGUIWindowPVRProvidersBase : public CGUIWindowPVRBase bool OnMessage(CGUIMessage& message) override; bool OnAction(const CAction& action) override; void UpdateButtons() override; + +private: + void ActivateChannelsWindow(const CPVRProvidersPath& selectedPath); + void ActivateRecordingsWindow(const CPVRProvidersPath& selectedPath); }; class CGUIWindowPVRTVProviders : public CGUIWindowPVRProvidersBase diff --git a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp index 50d4b3ef523ea..4238e07d56da2 100644 --- a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp @@ -11,6 +11,7 @@ #include "FileItemList.h" #include "GUIInfoManager.h" #include "ServiceBroker.h" +#include "URL.h" #include "guilib/GUIComponent.h" #include "guilib/GUIMessage.h" #include "guilib/GUIRadioButtonControl.h" @@ -19,6 +20,7 @@ #include "input/actions/Action.h" #include "input/actions/ActionIDs.h" #include "pvr/PVRManager.h" +#include "pvr/PVRPathUtils.h" #include "pvr/guilib/PVRGUIActionsPlayback.h" #include "pvr/guilib/PVRGUIActionsRecordings.h" #include "pvr/recordings/PVRRecording.h" @@ -56,6 +58,22 @@ void CGUIWindowPVRRecordingsBase::OnWindowLoaded() CONTROL_SELECT(CONTROL_BTNGROUPITEMS); } +void CGUIWindowPVRRecordingsBase::OnDeinitWindow(int nextWindowID) +{ + if (UTILS::HasClientAndProvider(m_vecItems->GetPath())) + m_vecItems->SetPath(""); // Open default listing next time. + + CGUIWindowPVRBase::OnDeinitWindow(nextWindowID); +} + +std::string CGUIWindowPVRRecordingsBase::GetRootPath() const +{ + const CURL url{m_vecItems->GetPath()}; + std::string rootPath{CPVRRecordingsPath(m_bShowDeletedRecordings, m_bRadio)}; + rootPath += url.GetOptions(); + return rootPath; +} + std::string CGUIWindowPVRRecordingsBase::GetDirectoryPath() { const std::string basePath = CPVRRecordingsPath(m_bShowDeletedRecordings, m_bRadio); @@ -214,9 +232,9 @@ void CGUIWindowPVRRecordingsBase::UpdateButtons() } CGUIWindowPVRBase::UpdateButtons(); - SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1, m_bShowDeletedRecordings - ? g_localizeStrings.Get(19179) - : ""); /* Deleted recordings trash */ + + // If we are filtering by client id / provider id, expose provider's name. + SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1, UTILS::GetProviderNameFromPath(m_vecItems->GetPath())); const CPVRRecordingsPath path(m_vecItems->GetPath()); SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2, @@ -509,13 +527,3 @@ bool CGUIWindowPVRRecordingsBase::GetFilteredItems(const std::string& filter, CF return listchanged; } - -std::string CGUIWindowPVRTVRecordings::GetRootPath() const -{ - return CPVRRecordingsPath(m_bShowDeletedRecordings, false); -} - -std::string CGUIWindowPVRRadioRecordings::GetRootPath() const -{ - return CPVRRecordingsPath(m_bShowDeletedRecordings, true); -} diff --git a/xbmc/pvr/windows/GUIWindowPVRRecordings.h b/xbmc/pvr/windows/GUIWindowPVRRecordings.h index 21dc269772236..4cb069fd81e73 100644 --- a/xbmc/pvr/windows/GUIWindowPVRRecordings.h +++ b/xbmc/pvr/windows/GUIWindowPVRRecordings.h @@ -27,6 +27,7 @@ class CGUIWindowPVRRecordingsBase : public CGUIWindowPVRBase ~CGUIWindowPVRRecordingsBase() override; void OnWindowLoaded() override; + void OnDeinitWindow(int nextWindowID) override; bool OnMessage(CGUIMessage& message) override; bool OnAction(const CAction& action) override; void GetContextButtons(int itemNumber, CContextButtons& buttons) override; @@ -36,15 +37,15 @@ class CGUIWindowPVRRecordingsBase : public CGUIWindowPVRBase void UpdateButtons() override; protected: + std::string GetRootPath() const override; std::string GetDirectoryPath() override; void OnPrepareFileItems(CFileItemList& items) override; bool GetFilteredItems(const std::string& filter, CFileItemList& items) override; - bool m_bShowDeletedRecordings{false}; - private: bool OnContextButtonDeleteAll(CFileItem* item, CONTEXT_BUTTON button); + bool m_bShowDeletedRecordings{false}; CVideoThumbLoader m_thumbLoader; CVideoDatabase m_database; CPVRSettings m_settings; @@ -57,7 +58,6 @@ class CGUIWindowPVRTVRecordings : public CGUIWindowPVRRecordingsBase : CGUIWindowPVRRecordingsBase(false, WINDOW_TV_RECORDINGS, "MyPVRRecordings.xml") { } - std::string GetRootPath() const override; }; class CGUIWindowPVRRadioRecordings : public CGUIWindowPVRRecordingsBase @@ -67,6 +67,5 @@ class CGUIWindowPVRRadioRecordings : public CGUIWindowPVRRecordingsBase : CGUIWindowPVRRecordingsBase(true, WINDOW_RADIO_RECORDINGS, "MyPVRRecordings.xml") { } - std::string GetRootPath() const override; }; } // namespace PVR From f3fe59a2738526a7e7192bad38f78920caf9e5ac Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Sun, 25 Aug 2024 18:39:08 -0400 Subject: [PATCH 427/651] [uwp][build] Allow UWP build to run on Windows 10 Define separately the sdk version required to build (desktop and store) and the OS version required to run (Windows Store only). --- cmake/scripts/windows/ArchSetup.cmake | 8 ++++---- cmake/scripts/windowsstore/ArchSetup.cmake | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/cmake/scripts/windows/ArchSetup.cmake b/cmake/scripts/windows/ArchSetup.cmake index 8be83e6a7880e..89adc5e125452 100644 --- a/cmake/scripts/windows/ArchSetup.cmake +++ b/cmake/scripts/windows/ArchSetup.cmake @@ -1,9 +1,9 @@ -# Minimum SDK version we support -set(VS_MINIMUM_SDK_VERSION 10.0.22621.0) +# Minimum SDK version required to build +set(VS_MINIMUM_BUILD_SDK_VERSION 10.0.22621.0) -if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS VS_MINIMUM_SDK_VERSION) +if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS VS_MINIMUM_BUILD_SDK_VERSION) message(FATAL_ERROR "Detected Windows SDK version is ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}.\n" - "Windows SDK ${VS_MINIMUM_SDK_VERSION} or higher is required.\n" + "Windows SDK ${VS_MINIMUM_BUILD_SDK_VERSION} or higher is required.\n" "INFO: Windows SDKs can be installed from the Visual Studio installer.") endif() diff --git a/cmake/scripts/windowsstore/ArchSetup.cmake b/cmake/scripts/windowsstore/ArchSetup.cmake index a39914cca0bea..2c1221c48b3f9 100644 --- a/cmake/scripts/windowsstore/ArchSetup.cmake +++ b/cmake/scripts/windowsstore/ArchSetup.cmake @@ -1,9 +1,11 @@ -# Minimum SDK version we support -set(VS_MINIMUM_SDK_VERSION 10.0.22621.0) +# Minimum SDK version required to build +set(VS_MINIMUM_BUILD_SDK_VERSION 10.0.22621.0) +# Minimum OS version to run the app +set(VS_MINIMUM_SDK_VERSION 10.0.18362.0) -if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS VS_MINIMUM_SDK_VERSION) +if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS VS_MINIMUM_BUILD_SDK_VERSION) message(FATAL_ERROR "Detected Windows SDK version is ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}.\n" - "Windows SDK ${VS_MINIMUM_SDK_VERSION} or higher is required.\n" + "Windows SDK ${VS_MINIMUM_BUILD_SDK_VERSION} or higher is required.\n" "INFO: Windows SDKs can be installed from the Visual Studio installer.") endif() From eaca754dc37ea9796bd2d303c7d5f43089785e4b Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 24 Aug 2024 14:55:32 +0200 Subject: [PATCH 428/651] [Estuary][guiinfo][PVR] Introduce (ListItem|VideoPlayer|MusicPlayer).MediaProviders guiinfo label, implement it for PVR items, show it in Estuary PVR info panel and PVR info dialog. --- .../resources/strings.po | 4 ++- addons/skin.estuary/xml/DialogPVRInfo.xml | 2 +- addons/skin.estuary/xml/Includes_PVR.xml | 2 +- xbmc/GUIInfoManager.cpp | 33 +++++++++++++++++-- xbmc/guilib/guiinfo/GUIInfoLabels.h | 7 +++- xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 28 ++++++++++++++++ 6 files changed, 69 insertions(+), 7 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index f132270627534..96fa299ac1bfc 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -11723,6 +11723,8 @@ msgid "Switched to channel for auto-closed PVR reminder for channel '{0:s}' at ' msgstr "" #. generic label for pvr providers used in different places +#: addons/skin.estuary/xml/DialogPVRInfo.xml +#: addons/skin.estuary/xml/Includes_PVR.xml #: xbmc/pvr/filesystem/PVRGUIDirectory.cpp #: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp #: xbmc/windows/GUIWindowSystemInfo.cpp @@ -11808,7 +11810,7 @@ msgctxt "#19347" msgid "None of the active PVR clients provide client-specific settings." msgstr "" -#. label for 'by provider' sort method +#. generic label for a pvr provider used in different places #: xbmc/pvr/windows/GUIViewStatePVR.cpp #: xbmc/utils/SortUtils.cpp msgctxt "#19348" diff --git a/addons/skin.estuary/xml/DialogPVRInfo.xml b/addons/skin.estuary/xml/DialogPVRInfo.xml index 04d12c996b839..561d898d966a7 100644 --- a/addons/skin.estuary/xml/DialogPVRInfo.xml +++ b/addons/skin.estuary/xml/DialogPVRInfo.xml @@ -49,7 +49,7 @@ <width>1050</width> <height>425</height> <align>justify</align> - <label>$INFO[ListItem.ChannelName,[B],[/B][CR]]$INFO[ListItem.Date,[COLOR grey]$LOCALIZE[552]:[/COLOR] ,[CR]]$INFO[ListItem.Duration,[COLOR grey]$LOCALIZE[180]:[/COLOR] ,[CR]]$VAR[RecordingSizeLabel]$VAR[PremieredLabel]$INFO[ListItem.Rating,[COLOR grey]$LOCALIZE[563]:[/COLOR] ,[CR]]$VAR[ExpirationDateTimeLabel]$VAR[PVRInstanceName,,[CR]]$INFO[ListItem.Genre,[COLOR grey]$LOCALIZE[515]: [/COLOR]][CR]$INFO[ListItem.ParentalRatingCode,[COLOR grey]$LOCALIZE[31017]: [/COLOR],[CR]]$INFO[ListItem.Writer,[COLOR grey]$LOCALIZE[20417]:[/COLOR] ,[CR]]$INFO[ListItem.Director,[COLOR grey]$LOCALIZE[20339]:[/COLOR] ,[CR]]$INFO[ListItem.Cast,[COLOR grey]$LOCALIZE[206]:[/COLOR] ,[CR]][CR]$INFO[ListItem.Plot]</label> + <label>$INFO[ListItem.ChannelName,[B],[/B][CR]]$INFO[ListItem.Date,[COLOR grey]$LOCALIZE[552]:[/COLOR] ,[CR]]$INFO[ListItem.Duration,[COLOR grey]$LOCALIZE[180]:[/COLOR] ,[CR]]$VAR[RecordingSizeLabel]$VAR[PremieredLabel]$INFO[ListItem.Rating,[COLOR grey]$LOCALIZE[563]:[/COLOR] ,[CR]]$VAR[ExpirationDateTimeLabel]$VAR[PVRInstanceName,,[CR]]$INFO[ListItem.MediaProviders,[COLOR grey]$LOCALIZE[19334]:[/COLOR] ,[CR]]$INFO[ListItem.Genre,[COLOR grey]$LOCALIZE[515]: [/COLOR]][CR]$INFO[ListItem.ParentalRatingCode,[COLOR grey]$LOCALIZE[31017]: [/COLOR],[CR]]$INFO[ListItem.Writer,[COLOR grey]$LOCALIZE[20417]:[/COLOR] ,[CR]]$INFO[ListItem.Director,[COLOR grey]$LOCALIZE[20339]:[/COLOR] ,[CR]]$INFO[ListItem.Cast,[COLOR grey]$LOCALIZE[206]:[/COLOR] ,[CR]][CR]$INFO[ListItem.Plot]</label> <autoscroll time="3000" delay="4000" repeat="5000">Skin.HasSetting(AutoScroll)</autoscroll> </control> <control type="grouplist" id="9000"> diff --git a/addons/skin.estuary/xml/Includes_PVR.xml b/addons/skin.estuary/xml/Includes_PVR.xml index 56d9bbb124905..75840d8b1cabe 100644 --- a/addons/skin.estuary/xml/Includes_PVR.xml +++ b/addons/skin.estuary/xml/Includes_PVR.xml @@ -436,7 +436,7 @@ <top>465</top> <width>830</width> <bottom>list_bottom_offset</bottom> - <label>$VAR[PVRInstanceName,,[CR]]$VAR[FlagLabel,,[CR]]$INFO[ListItem.Genre,[COLOR grey]$LOCALIZE[515]:[/COLOR] ,[CR]]$INFO[ListItem.ParentalRatingCode,[COLOR grey]$LOCALIZE[31017]: [/COLOR],[CR]]$INFO[ListItem.TimerType,[COLOR grey]$LOCALIZE[803]:[/COLOR] ,[CR]]$VAR[RecordingSizeLabel]$VAR[ExpirationDateTimeLabel]$INFO[ListItem.Plot,[CR]]</label> + <label>$VAR[PVRInstanceName,,[CR]]$INFO[ListItem.MediaProviders,[COLOR grey]$LOCALIZE[19334]:[/COLOR] ,[CR]]$VAR[FlagLabel,,[CR]]$INFO[ListItem.Genre,[COLOR grey]$LOCALIZE[515]:[/COLOR] ,[CR]]$INFO[ListItem.ParentalRatingCode,[COLOR grey]$LOCALIZE[31017]: [/COLOR],[CR]]$INFO[ListItem.TimerType,[COLOR grey]$LOCALIZE[803]:[/COLOR] ,[CR]]$VAR[RecordingSizeLabel]$VAR[ExpirationDateTimeLabel]$INFO[ListItem.Plot,[CR]]</label> <autoscroll delay="10000" time="3000" repeat="10000">Skin.HasSetting(AutoScroll)</autoscroll> </control> </control> diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 85d2875428260..b7cd78b54cbaa 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -2883,6 +2883,14 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED }, /// @skinning_v19 **[New Infolabel]** \link MusicPlayer_Station `MusicPlayer.Station`\endlink /// <p> /// } +/// \table_row3{ <b>`MusicPlayer.MediaProviders`</b>, +/// \anchor MusicPlayer_MediaProviders +/// _string_, +/// @return string containing the names of the providers of the currently playing media\, separated by commas if muliple are present. +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link MusicPlayer_MediaProviders `MusicPlayer.MediaProviders`\endlink +/// <p> +/// } /// \table_end /// /// ----------------------------------------------------------------------------- @@ -2931,7 +2939,8 @@ const infomap musicplayer[] = {{ "title", MUSICPLAYER_TITLE }, { "bpm", MUSICPLAYER_BPM }, { "ismultidisc", MUSICPLAYER_ISMULTIDISC }, { "totaldiscs", MUSICPLAYER_TOTALDISCS }, - { "station", MUSICPLAYER_STATIONNAME } + { "station", MUSICPLAYER_STATIONNAME }, + { "mediaproviders", MUSICPLAYER_MEDIAPROVIDERS }, }; // clang-format on @@ -3989,7 +3998,15 @@ const infomap musicplayer[] = {{ "title", MUSICPLAYER_TITLE }, /// @skinning_v22 **[Infolabel Updated]** \link VideoPlayer_EpisodePart `VideoPlayer.EpisodePart`\endlink /// also supports EPG. /// <p> -/// }/// +/// } +/// \table_row3{ <b>`VideoPlayer.MediaProviders`</b>, +/// \anchor VideoPlayer_MediaProviders +/// _string_, +/// @return string containing the names of the providers of the currently playing media\, separated by commas if muliple are present. +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link VideoPlayer_MediaProviders `VideoPlayer.MediaProviders`\endlink +/// <p> +/// } /// \table_end /// /// ----------------------------------------------------------------------------- @@ -4072,7 +4089,8 @@ const infomap videoplayer[] = {{ "title", VIDEOPLAYER_TITLE }, { "art", VIDEOPLAYER_ART}, { "videoversionname", VIDEOPLAYER_VIDEOVERSION_NAME}, { "hasvideoversions", VIDEOPLAYER_HAS_VIDEOVERSIONS}, - { "episodepart", VIDEOPLAYER_EPISODEPART} + { "episodepart", VIDEOPLAYER_EPISODEPART}, + { "mediaproviders", VIDEOPLAYER_MEDIAPROVIDERS }, }; // clang-format on @@ -7082,6 +7100,14 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @skinning_v22 **[New Infolabel]** \link ListItem_EpisodePart `ListItem.EpisodePart`\endlink /// <p> /// } +/// \table_row3{ <b>`ListItem.MediaProviders`</b>, +/// \anchor ListItem_MediaProviders +/// _string_, +/// @return string containing the names of the media providers of the item\, separated by commas if muliple are present. +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link ListItem_MediaProviders `ListItem.MediaProviders`\endlink +/// <p> +/// } /// /// \table_end /// @@ -7311,6 +7337,7 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "pvrinstancename", LISTITEM_PVR_INSTANCE_NAME }, { "pvrgrouporigin", LISTITEM_PVR_GROUP_ORIGIN }, { "episodepart", LISTITEM_EPISODEPART }, + { "mediaproviders", LISTITEM_MEDIAPROVIDERS }, }; // clang-format on diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index be24323d2f128..613aff347f7a4 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -321,12 +321,16 @@ #define RETROPLAYER_STRETCH_MODE 331 #define RETROPLAYER_VIDEO_ROTATION 332 -// More PVR infolabels +// More VideoPlayer infolabels #define VIDEOPLAYER_CHANNEL_LOGO 333 #define VIDEOPLAYER_EPISODEPART 334 #define VIDEOPLAYER_PARENTAL_RATING_CODE 335 #define VIDEOPLAYER_PARENTAL_RATING_ICON 336 #define VIDEOPLAYER_PARENTAL_RATING_SOURCE 337 +#define VIDEOPLAYER_MEDIAPROVIDERS 338 + +// More MusicPlayer infolabels +#define MUSICPLAYER_MEDIAPROVIDERS 339 #define CONTAINER_HAS_PARENT_ITEM 341 #define CONTAINER_CAN_FILTER 342 @@ -996,6 +1000,7 @@ static constexpr unsigned int SYSTEM_LOCALE = 1012; #define LISTITEM_PARENTAL_RATING_ICON (LISTITEM_START + 221) #define LISTITEM_PARENTAL_RATING_SOURCE (LISTITEM_START + 222) #define LISTITEM_EPISODEPART (LISTITEM_START + 223) +#define LISTITEM_MEDIAPROVIDERS (LISTITEM_START + 224) #define LISTITEM_END (LISTITEM_START + 2500) diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index c89ea997831c7..d57aef2fd5a34 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -433,6 +433,7 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, case LISTITEM_PARENTAL_RATING_CODE: case LISTITEM_PARENTAL_RATING_ICON: case LISTITEM_PARENTAL_RATING_SOURCE: + case LISTITEM_MEDIAPROVIDERS: break; // obtain value from channel/epg default: return false; @@ -602,6 +603,20 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, strValue = std::to_string(recording->EpisodePart()); return true; } + return false; + case MUSICPLAYER_MEDIAPROVIDERS: + case VIDEOPLAYER_MEDIAPROVIDERS: + case LISTITEM_MEDIAPROVIDERS: + if (recording->HasClientProvider()) + { + const std::shared_ptr<const CPVRProvider> provider{recording->GetProvider()}; + if (provider) + { + strValue = provider->GetName(); + return true; + } + } + return false; } return false; } @@ -940,6 +955,19 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, return true; } break; + case MUSICPLAYER_MEDIAPROVIDERS: + case VIDEOPLAYER_MEDIAPROVIDERS: + case LISTITEM_MEDIAPROVIDERS: + if (channel->HasClientProvider()) + { + const std::shared_ptr<const CPVRProvider> provider{channel->GetProvider()}; + if (provider) + { + strValue = provider->GetName(); + return true; + } + } + break; } } From f86d61ee883a0ef028e4f5befb0838c9944345fe Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 31 Aug 2024 15:50:09 +0200 Subject: [PATCH 429/651] [PVR] Fix CPVRTimerType::GetPriorityValues(). --- xbmc/pvr/timers/PVRTimerType.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/pvr/timers/PVRTimerType.h b/xbmc/pvr/timers/PVRTimerType.h index c23fbcd373d5b..e2ac4d6c49678 100644 --- a/xbmc/pvr/timers/PVRTimerType.h +++ b/xbmc/pvr/timers/PVRTimerType.h @@ -389,7 +389,7 @@ class CPVRTimerType */ const std::vector<SettingIntValue>& GetPriorityValues() const { - return m_lifetimeValues.GetValues(); + return m_priorityValues.GetValues(); } /*! From 83db21512c215be149a5c65a6beabe92dfc3f3bb Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 31 Aug 2024 18:07:46 +0200 Subject: [PATCH 430/651] [PVR] Timer settings dialog: Some properties should be read-only for in-progress recordings. --- xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp index 69c5de5b60cff..07f9d221eac90 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp @@ -1361,6 +1361,15 @@ bool CGUIDialogPVRTimerSettings::TypeReadOnlyCondition(const std::string& condit return true; } + /* Handle recordings in progress. */ + if (pThis->m_timerInfoTag->State() == PVR_TIMER_STATE_RECORDING) + { + if (cond == SETTING_TMR_TYPE || cond == SETTING_TMR_CHANNEL || cond == SETTING_TMR_BEGIN_PRE || + cond == SETTING_TMR_START_DAY || cond == SETTING_TMR_BEGIN || SETTING_TMR_PRIORITY || + cond == SETTING_TMR_DIR) + return false; + } + // Let the PVR client decide... int idx = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); const auto entry = pThis->m_typeEntries.find(idx); From c3746317e21702a6fc53b13689df69fe78b74d34 Mon Sep 17 00:00:00 2001 From: Tobias Markus <tobbi.bugs@googlemail.com> Date: Mon, 22 Jul 2024 23:14:09 +0200 Subject: [PATCH 431/651] xbmc base: cppcheck performance fixes --- xbmc/Util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/Util.cpp b/xbmc/Util.cpp index 0b507b502bf91..cd761165e3c2c 100644 --- a/xbmc/Util.cpp +++ b/xbmc/Util.cpp @@ -158,7 +158,7 @@ std::string GetHomePath(const std::string& strTarget, std::string strPath) strPath = CUtil::ResolveExecutablePath(); auto last_sep = strPath.find_last_of(PATH_SEPARATOR_CHAR); if (last_sep != std::string::npos) - strPath = strPath.substr(0, last_sep); + strPath.resize(last_sep); g_charsetConverter.utf8ToW(strPath, strPathW); if (IsDirectoryValidRoot(strPathW)) From eb6ed5500f1bbe2940af4509f1a9b31a01b54e5b Mon Sep 17 00:00:00 2001 From: Garrett Brown <themagnificentmrb@gmail.com> Date: Sun, 1 Sep 2024 20:55:07 -0700 Subject: [PATCH 432/651] Games: Offer to install missing dependencies on game launch --- xbmc/games/addons/GameClientProperties.cpp | 147 ++++++++++++++++++++- xbmc/games/addons/GameClientProperties.h | 3 + 2 files changed, 146 insertions(+), 4 deletions(-) diff --git a/xbmc/games/addons/GameClientProperties.cpp b/xbmc/games/addons/GameClientProperties.cpp index ae081afa923ce..9395e443ca027 100644 --- a/xbmc/games/addons/GameClientProperties.cpp +++ b/xbmc/games/addons/GameClientProperties.cpp @@ -12,15 +12,21 @@ #include "FileItemList.h" #include "GameClient.h" #include "ServiceBroker.h" +#include "addons/AddonInstaller.h" #include "addons/AddonManager.h" #include "addons/GameResource.h" #include "addons/IAddon.h" #include "addons/addoninfo/AddonInfo.h" #include "addons/addoninfo/AddonType.h" +#include "dialogs/GUIDialogProgress.h" +#include "dialogs/GUIDialogSelect.h" #include "dialogs/GUIDialogYesNo.h" #include "filesystem/Directory.h" #include "filesystem/SpecialProtocol.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" #include "messaging/helpers/DialogOKHelper.h" #include "utils/StringUtils.h" #include "utils/Variant.h" @@ -201,7 +207,8 @@ unsigned int CGameClientProperties::GetExtensionCount(void) const bool CGameClientProperties::GetProxyAddons(ADDON::VECADDONS& addons) { ADDON::VECADDONS ret; - std::vector<std::string> missingDependencies; // ID or name of missing dependencies + std::vector<std::string> disabledDependencies; // ID or name of disabled dependencies + std::vector<std::string> uninstalledDependencies; // ID or name of uninstalled dependencies for (const auto& dependency : m_parent.GetDependencies()) { @@ -218,14 +225,14 @@ bool CGameClientProperties::GetProxyAddons(ADDON::VECADDONS& addons) if (!CServiceBroker::GetAddonMgr().EnableAddon(dependency.id)) { CLog::Log(LOGERROR, "Failed to enable add-on {}", dependency.id); - missingDependencies.emplace_back(addon->Name()); + disabledDependencies.emplace_back(addon->Name()); addon.reset(); } } else { CLog::Log(LOGERROR, "User chose to not enable add-on {}", dependency.id); - missingDependencies.emplace_back(addon->Name()); + disabledDependencies.emplace_back(addon->Name()); addon.reset(); } } @@ -242,11 +249,35 @@ bool CGameClientProperties::GetProxyAddons(ADDON::VECADDONS& addons) else { CLog::Log(LOGERROR, "Missing mandatory dependency {}", dependency.id); - missingDependencies.emplace_back(dependency.id); + uninstalledDependencies.emplace_back(dependency.id); } } } + // Attempt to install missing dependencies + if (disabledDependencies.empty() && !uninstalledDependencies.empty()) + { + if (InstallDependencies(uninstalledDependencies)) + uninstalledDependencies.clear(); + } + + // IDs of all missing dependencies + std::vector<std::string> missingDependencies; + + // Reserve enough space to avoid multiple allocations + missingDependencies.reserve(disabledDependencies.size() + uninstalledDependencies.size()); + + // Move elements from the first vector + missingDependencies.insert(missingDependencies.end(), + std::make_move_iterator(disabledDependencies.begin()), + std::make_move_iterator(disabledDependencies.end())); + + // Move elements from the second vector + missingDependencies.insert(missingDependencies.end(), + std::make_move_iterator(uninstalledDependencies.begin()), + std::make_move_iterator(uninstalledDependencies.end())); + + // Show an error dialog if any dependencies are still missing if (!missingDependencies.empty()) { std::string strDependencies = StringUtils::Join(missingDependencies, ", "); @@ -289,3 +320,111 @@ bool CGameClientProperties::HasProxyDll(const std::string& strLibPath) const } return false; } + +bool CGameClientProperties::InstallDependencies(const std::vector<std::string>& addons) +{ + // Only proceed if we have a GUI + CGUIComponent* const gui = CServiceBroker::GetGUI(); + if (gui == nullptr) + return false; + + const CGUIWindowManager& windowManager = gui->GetWindowManager(); + + // Get GUI dialogs + auto* selectDialog = windowManager.GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + auto* progressDialog = windowManager.GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); + + // We can only install add-ons if the dialogs are present + if (selectDialog == nullptr || progressDialog == nullptr) + return false; + + // Get installable add-ons + VECADDONS installableAddons; + for (const std::string& dependency : addons) + { + AddonPtr addon; + if (CServiceBroker::GetAddonMgr().FindInstallableById(dependency, addon) && addon) + installableAddons.emplace_back(std::move(addon)); + else + CLog::Log(LOGERROR, "Failed to find installable add-on for {}", dependency); + } + + // Verify all add-ons are installable + if (addons.size() != installableAddons.size()) + return false; + + // Setup select dialog + selectDialog->Reset(); + selectDialog->SetHeading(39020); // "The following additional add-ons will be installed" + selectDialog->SetUseDetails(true); + selectDialog->EnableButton(true, 186); // "OK"" + selectDialog->SetButtonFocus(true); + for (const auto& addon : installableAddons) + { + CFileItem item{addon->Name()}; + item.SetArt("icon", addon->Icon()); + selectDialog->Add(item); + } + + // Show select dialog + selectDialog->Open(); + if (!selectDialog->IsButtonPressed()) + { + CLog::Log(LOGDEBUG, "Dependency installer: User cancelled installation dialog"); + return false; + } + + CLog::Log(LOGDEBUG, "Dependency installer: Installing {} add-ons", installableAddons.size()); + + progressDialog->SetHeading(CVariant{24086}); // "Installing add-on..." + progressDialog->SetLine(0, CVariant{""}); + progressDialog->SetLine(1, CVariant{""}); + progressDialog->SetLine(2, CVariant{""}); + + progressDialog->Open(); + + size_t installedCount = 0; + while (installedCount < installableAddons.size()) + { + const AddonPtr& addon = installableAddons.at(installedCount); + + // Set dialog text + const std::string& progressTemplate = g_localizeStrings.Get(24057); // "Installing {0:s}..." + const std::string progressText = StringUtils::Format(progressTemplate, addon->Name()); + progressDialog->SetLine(0, CVariant{progressText}); + + // Set dialog percentage + const unsigned int percentage = + 100 * (installedCount + 1) / static_cast<unsigned int>(installableAddons.size()); + progressDialog->SetPercentage(percentage); + + if (!ADDON::CAddonInstaller::GetInstance().InstallOrUpdate( + addon->ID(), ADDON::BackgroundJob::CHOICE_NO, ADDON::ModalJob::CHOICE_NO)) + { + CLog::Log(LOGERROR, "Controller installer: Failed to install {}", addon->ID()); + // "Error" + // "Failed to install add-on." + MESSAGING::HELPERS::ShowOKDialogText(257, 35256); + return false; + } + + if (progressDialog->IsCanceled()) + { + CLog::Log(LOGDEBUG, "Controller installer: User cancelled add-on installation"); + return false; + } + + if (windowManager.GetActiveWindowOrDialog() != WINDOW_DIALOG_PROGRESS) + { + CLog::Log(LOGDEBUG, "Controller installer: Progress dialog is hidden, canceling"); + return false; + } + + installedCount++; + } + + CLog::Log(LOGDEBUG, "Controller window: Installed {} controller add-ons", installedCount); + progressDialog->Close(); + + return true; +} diff --git a/xbmc/games/addons/GameClientProperties.h b/xbmc/games/addons/GameClientProperties.h index efb9a8b566496..681c255be0840 100644 --- a/xbmc/games/addons/GameClientProperties.h +++ b/xbmc/games/addons/GameClientProperties.h @@ -78,6 +78,9 @@ class CGameClientProperties void AddProxyDll(const GameClientPtr& gameClient); bool HasProxyDll(const std::string& strLibPath) const; + // Utility functions + static bool InstallDependencies(const std::vector<std::string>& addons); + // Construction parameters const CGameClient& m_parent; AddonProps_Game& m_properties; From 3d25d125f95ec4f28b871812bcc4fb724d508925 Mon Sep 17 00:00:00 2001 From: Garrett Brown <themagnificentmrb@gmail.com> Date: Sun, 1 Sep 2024 21:03:05 -0700 Subject: [PATCH 433/651] Controllers: Focus "OK" button by default in installation dialog --- xbmc/games/controllers/dialogs/ControllerInstaller.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/games/controllers/dialogs/ControllerInstaller.cpp b/xbmc/games/controllers/dialogs/ControllerInstaller.cpp index 18f7634a011b9..4fdc0ebfa44bb 100644 --- a/xbmc/games/controllers/dialogs/ControllerInstaller.cpp +++ b/xbmc/games/controllers/dialogs/ControllerInstaller.cpp @@ -74,6 +74,7 @@ void CControllerInstaller::Process() pSelectDialog->SetHeading(39020); // "The following additional add-ons will be installed" pSelectDialog->SetUseDetails(true); pSelectDialog->EnableButton(true, 186); // "OK"" + pSelectDialog->SetButtonFocus(true); for (const auto& it : items) pSelectDialog->Add(*it); pSelectDialog->Open(); From 4f6a8dbb92b7d71e4c50ab708dfb5265205dfb38 Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Mon, 2 Sep 2024 11:40:35 +0200 Subject: [PATCH 434/651] [GUITextLayout] Removed custom chinese chars line break --- xbmc/guilib/GUITextLayout.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/guilib/GUITextLayout.h b/xbmc/guilib/GUITextLayout.h index 1b01d1db8bf70..a357db0573bf7 100644 --- a/xbmc/guilib/GUITextLayout.h +++ b/xbmc/guilib/GUITextLayout.h @@ -187,7 +187,7 @@ class CGUITextLayout inline bool CanWrapAtLetter(character_t letter) const XBMC_FORCE_INLINE { character_t ch = letter & 0xffff; - return ch == L' ' || (ch >=0x4e00 && ch <= 0x9fff); + return ch == L' '; }; static void AppendToUTF32(const std::string &utf8, character_t colStyle, vecText &utf32); static void AppendToUTF32(const std::wstring &utf16, character_t colStyle, vecText &utf32); From 79215959fe26d9544ee9ba611a8671bb374e09af Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Tue, 3 Sep 2024 09:00:01 +0200 Subject: [PATCH 435/651] [GUITextLayout] Add todo comment for unicode spaces --- xbmc/guilib/GUITextLayout.h | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/guilib/GUITextLayout.h b/xbmc/guilib/GUITextLayout.h index a357db0573bf7..7634d6eb14b09 100644 --- a/xbmc/guilib/GUITextLayout.h +++ b/xbmc/guilib/GUITextLayout.h @@ -187,6 +187,7 @@ class CGUITextLayout inline bool CanWrapAtLetter(character_t letter) const XBMC_FORCE_INLINE { character_t ch = letter & 0xffff; + //! @todo: unicode spaces are not handled, to check also all other GUI parts return ch == L' '; }; static void AppendToUTF32(const std::string &utf8, character_t colStyle, vecText &utf32); From 672c552956614e96846ffc812982599c5994c362 Mon Sep 17 00:00:00 2001 From: ronie <ronie@kodi.tv> Date: Wed, 4 Sep 2024 01:01:31 +0200 Subject: [PATCH 436/651] Fix CDDB lookups --- xbmc/network/cddb.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/xbmc/network/cddb.cpp b/xbmc/network/cddb.cpp index 3d807963c5203..977411b54ea15 100644 --- a/xbmc/network/cddb.cpp +++ b/xbmc/network/cddb.cpp @@ -126,11 +126,10 @@ bool Xcddb::Send( const void *buffer, int bytes ) { std::unique_ptr<char[]> tmp_buffer(new char[bytes + 10]); strcpy(tmp_buffer.get(), (const char*)buffer); - tmp_buffer.get()[bytes] = '.'; - tmp_buffer.get()[bytes + 1] = 0x0d; - tmp_buffer.get()[bytes + 2] = 0x0a; - tmp_buffer.get()[bytes + 3] = 0x00; - int iErr = send((SOCKET)m_cddb_socket, (const char*)tmp_buffer.get(), bytes + 3, 0); + tmp_buffer.get()[bytes] = 0x0d; + tmp_buffer.get()[bytes + 1] = 0x0a; + tmp_buffer.get()[bytes + 2] = 0x00; + int iErr = send((SOCKET)m_cddb_socket, (const char*)tmp_buffer.get(), bytes + 2, 0); if (iErr <= 0) { return false; From 5feadf7473f2384c492142a7c47c9ce98e06df52 Mon Sep 17 00:00:00 2001 From: Ratchanan Srirattanamet <peathot@hotmail.com> Date: Wed, 4 Sep 2024 06:38:08 +0700 Subject: [PATCH 437/651] CPUInfo: make sure m_cpuFeatures is initialized m_cpuFeatures is OR'ed into in implementations, but seemingly none of them initialized this field before they OR into, so do it first in CCPUInfo class. --- xbmc/utils/CPUInfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/utils/CPUInfo.h b/xbmc/utils/CPUInfo.h index a53ec29c6eebb..2d3c044248d45 100644 --- a/xbmc/utils/CPUInfo.h +++ b/xbmc/utils/CPUInfo.h @@ -115,7 +115,7 @@ class CCPUInfo std::size_t m_totalTime{0}; int m_cpuCount; - unsigned int m_cpuFeatures; + unsigned int m_cpuFeatures{0}; std::vector<CoreInfo> m_cores; }; From 98c61c6d1ac04992d4ac54381ecea3990cd191f9 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti <joseluis.marti@gmail.com> Date: Wed, 4 Sep 2024 14:02:37 +0200 Subject: [PATCH 438/651] Bump Android SDK 35 --- cmake/platform/android/android.cmake | 2 +- docs/README.Android.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/platform/android/android.cmake b/cmake/platform/android/android.cmake index 6a7f71e1eff67..9aa4472179e6d 100644 --- a/cmake/platform/android/android.cmake +++ b/cmake/platform/android/android.cmake @@ -4,7 +4,7 @@ set(APP_RENDER_SYSTEM gles) list(APPEND PLATFORM_OPTIONAL_DEPS LibDovi) # Store SDK compile version -set(TARGET_SDK 34) +set(TARGET_SDK 35) # Minimum supported SDK version set(TARGET_MINSDK 24) diff --git a/docs/README.Android.md b/docs/README.Android.md index 72fcf75e7fdd9..d6c7dab5f48a5 100644 --- a/docs/README.Android.md +++ b/docs/README.Android.md @@ -98,8 +98,8 @@ Before Android SDK can be used, you need to accept the licenses and configure it cd $HOME/android-tools/android-sdk-linux/cmdline-tools/bin ./sdkmanager --sdk_root=$(pwd)/../.. --licenses ./sdkmanager --sdk_root=$(pwd)/../.. platform-tools -./sdkmanager --sdk_root=$(pwd)/../.. "platforms;android-34" -./sdkmanager --sdk_root=$(pwd)/../.. "build-tools;33.0.1" +./sdkmanager --sdk_root=$(pwd)/../.. "platforms;android-35" +./sdkmanager --sdk_root=$(pwd)/../.. "build-tools;34.0.0" ./sdkmanager --sdk_root=$(pwd)/../.. "ndk;26.2.11394342" ``` From 735d5eb1d7c268ccac0e2452886ef9d075b69e86 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti <joseluis.marti@gmail.com> Date: Wed, 4 Sep 2024 14:10:50 +0200 Subject: [PATCH 439/651] Bump Android Gradle Plugin 8.6 --- tools/android/packaging/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/android/packaging/build.gradle b/tools/android/packaging/build.gradle index 1c08ce20c87eb..6684442ea35a3 100644 --- a/tools/android/packaging/build.gradle +++ b/tools/android/packaging/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.1.1' + classpath 'com.android.tools.build:gradle:8.6.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From adbe34e9a4ec301be5787b0768aa04ec38101a6a Mon Sep 17 00:00:00 2001 From: Jose Luis Marti <joseluis.marti@gmail.com> Date: Wed, 4 Sep 2024 14:29:19 +0200 Subject: [PATCH 440/651] Specify ndkVersion in the build.gradle file --- tools/android/packaging/xbmc/build.gradle.in | 1 + tools/depends/configure.ac | 3 +++ tools/depends/target/Toolchain.cmake.in | 1 + 3 files changed, 5 insertions(+) diff --git a/tools/android/packaging/xbmc/build.gradle.in b/tools/android/packaging/xbmc/build.gradle.in index c00e6e5b352cf..ab5dbd0205b3c 100644 --- a/tools/android/packaging/xbmc/build.gradle.in +++ b/tools/android/packaging/xbmc/build.gradle.in @@ -4,6 +4,7 @@ android { namespace '@APP_PACKAGE@' compileSdk @TARGET_SDK@ ndkPath "@NDKROOT@" + ndkVersion "@NDKVERSION@" defaultConfig { applicationId "@APP_PACKAGE@" minSdk @TARGET_MINSDK@ diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac index 80bca65150e54..a4af8fdf4cfa5 100644 --- a/tools/depends/configure.ac +++ b/tools/depends/configure.ac @@ -581,6 +581,8 @@ if test "$platform_os" = "android"; then AC_MSG_ERROR(verify ndk path) fi + use_ndk_version=[`cat $use_ndk_path/source.properties | grep "Pkg.Revision" | cut -d'=' -f2 | awk '{print $1}'`] + #not all sort versions support -V - probe it... SORT_PARAMS="" sort -V /dev/null > /dev/null 2>&1 && SORT_PARAMS="-V" @@ -681,6 +683,7 @@ if test "$platform_os" = "android"; then echo -e AC_SUBST(use_sdk_path) AC_SUBST(use_ndk_path) + AC_SUBST(use_ndk_version) AC_SUBST(use_ndk_api) AC_SUBST(build_tools_path) fi diff --git a/tools/depends/target/Toolchain.cmake.in b/tools/depends/target/Toolchain.cmake.in index 21ae20ef3c53a..8484aba830162 100644 --- a/tools/depends/target/Toolchain.cmake.in +++ b/tools/depends/target/Toolchain.cmake.in @@ -97,6 +97,7 @@ endif() # add Android directories and tools if(CORE_SYSTEM_NAME STREQUAL android) set(NDKROOT @use_ndk_path@) + set(NDKVERSION @use_ndk_version@) set(SDKROOT @use_sdk_path@) set(TOOLCHAIN @use_toolchain@) set(HOST @use_host@) From 6130623db9f8ac78bc10dd27390101558d60a2d3 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti <joseluis.marti@gmail.com> Date: Wed, 4 Sep 2024 23:15:42 +0200 Subject: [PATCH 441/651] Bump Gradle 8.7 --- .../gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 43453 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +-- tools/android/packaging/gradlew | 29 ++++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/tools/android/packaging/gradle/wrapper/gradle-wrapper.jar b/tools/android/packaging/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 UfUyy3EX0IC28@JalTrWuAK7&_a{vGU literal 61574 zcmb6AV{~QRwml9f72CFLyJFk6ZKq;e729@pY}>YNR8p1vbMJH7ubt<dd+&SR4|A{8 z_8x1`wdNXQ_CETUQ$ZR86dDK!5)udqNbsux0e$=LPaq(uucNG}iXfe&oEQTLkiy># zZR`2@zJD1Ad^Oa6Hk1{VlN1wGR-u;_dyt)+kddaNpM#U8qn@6eX;fldWZ6BspQIa= zoRXcQk)#ENJ`XiXJuK3q0$`Ap92QXrW00Yv7NOrc-8ljOOOIcj{J&cR{W`aIGXJ-` z`ez%Mf7qBi8JgIb{-35Oe>Zh^GIVe-b^5nULQhxRDZa)^4+98@`hUJe{J%R>|LYHA z4K3~Hjcp8_owGF{d~lZVKJ;kc4<Ckvr<Ks9vIZMiV4(sAp`c=~R>8^OQ<D~h6Ll`5 z=!nm<7O3PbETu8BUFLvY<^?(Oh1mhX>+`_2migWY?JqgW&))70RgSB6KY9+&wm<*8 z_{<;(c;5H|u}3{Y>y_<0Z59a)MIGK7wRMX0Nvo<zIDqMu>>feeJs+U?bt-++E8bu7 zh#_cwz0(4#RaT@xy14c7d<92q-Dd}Dt<*RS+$r0a^=LGCM{ny?rMFjhgxIG4>Hc~r zC$L?-FW0FZ((8@dsowXlQq}ja%DM{z&0kia*w7B*PQ`gLvPGS7M}$T&EPl8mew3In z0U$u}+bk?Vei{E$6dAYI8Tsze6A5wah?d(+fyP_5t4ytRXNktK&*JB!hRl07G62m_ zAt1nj(37{1<Wmo=-(LugpOmJbN(=P?-A(<ow)~a5JAXg(H8s$_GJKckTU5u}IyZ5) zID#0@d#u~b^#$*`cV17ad(U@5r))nh`-qFNz0@+>p~L|m(Bsz3vE*usD`78QTgYIk zQ6BF14KLzsJTCqx&E!h>XP4)bya|{*G7&T$<FT|Z={(v^7m3~#J9tGcS<+P2=Tejr zxJrEie2${T<2-zUB5kI&nQy+&60~G>^hR0(bOWjUs2p0uw7xEjbz1FNSBCDb@^NIA z$qaq^0it^(#pFEmuGVS4&-r4(7HLmtT%_~Xhr-k8yp0`$N|y>#$Ao#zibzGi*UKzi zha<HbskADA3#gr3aAC$ZH)Tcy=inv|<2yKvmO0JJ<dBm{)-|UQda;}0M1XK9dcv-k zj*bd5iC<wJoDj_?uq2C%Kd!4FX}6CyGuV#o(=9kymQGv{rd+Y3G&K_2!6QhU{`3?j z+Q&_-A%{8RHHIy*O?tKNyhXq;8WSlJkQ)qLNTruWw#W#m;v-0D`XF+q#o~A(3P{m2 z;lo>V#@e1{2@1Vn2iq}4J{1-ox;7K(-;Sk{3G2_EtV-D<)^Pk-G<6-vP{W}Yd>GLL zuOVrmN@KlD4f5sVMTs7c{ATcIGrv4@2umVI$<X+cdF9BLa?U7s$+j?-FE`<Yhu{3v zm-POqSUQU8BX8jB@ytrC==zcsZZ?j^W>r!xI8a?GN(R;?32n0NS(g@B8S00-=zzLn z%^Agl9eV(q&8UrK^~&$}{S(6-nEXnI8%|hoQ47P?I0Kd=woZ-pH==;jEg+QOfMSq~ zOu>&DkHsc{?o&M5`jyJBWbfoPBv9Y#70qvoHbZXOj*qRM(CQV=uX5KN+b>SQf-~a8 ziZg}@&XHHXkAUqr)Q{y`jNd7`1F8nm6}n}+_She>KO`VNlnu(&??!(i#$mKOpWpi1 z#WfWxi3L)bNRodhPM~~?!5{TrrBY_+nD?CIUupkwAPGz-P;QYc-DcUoCe`w(7)}|S zRvN)9ru8b)MoullmASwsgKQo1U6nsVAvo8iKnbaWydt<H{8vU9I$LDfl1MfrWq}?F z%%BkY3ha?cs|<yNKDXrvve>o4y?#-|kP^%e6m@L`88KyDrLH`=EDx*6>?r5~7Iv~I zr__%SximG(izLKSnbTlXa-ksH@R6rvBrBavt4)>o<gSrg7I0fxTC|BGz)_?|eniUt zH_>3$dgztLt4W=!3=O(*w7I+pHY2(P<tk;7Lh^p(Rnnvo+CX{Dd?hm$oTH{_-W7{q zu}NF3_#7kf<nrX2>0QbTma+g#dXoD7N#?FaXNQ^I0*;jzvjM}%=+km`YtC%O#Alm| zqgORKSqk!#^~6whtLQASqiJ7*nq?38OJ3$u<vMEw9Jh~44Q4hTDRDqzmm%b3x^UF= zLBCr=VYvlEU9WU>=Tp%Y`x^eYJtOqTzVkJ60b2t>TzdQ{I}!lEBxm}JSy7sy8DpDb zIqdT%PKf&Zy--T^c-;%mbDCxLrMWTVLW}c=DP2>Td74)-mLl|70)8hU??(2)I@Zyo z2i`q5oyA!!(2xV~gahuKl&L(@_3SP012#x(7P!1}6vNFFK5f*A1xF({JwxSFwA|TM z&1z}!*mZKcUA-v4QzLz&5wS$7=5{M@RAlx@RkJaA4nWVqsuuaW(eDh^LNPPkmM~Al zwxCe@*-^4!ky#iNv2NIIU$CS+UW%ziW0q@6HN3{eCYOUe;2P)C*M`Bt{~-mC%T3%# zEaf)lATO1;uF33x>Hr~YD0Ju*Syi!Jz+x3myVvU^-O>C*lFCKS&=Tuz@>&o?68aF& zBv<^ziPywPu#;WSlTkzdZ9`GWe7D8h<1-v0M*R@oYgS5jlPbgHcx)n2*+!+<X4Sz= zF<(*{{g@=lH4jy)*p}rT-<4T4Fw)#ma;8+D6Q=aIpmf+}M2_LL>VcGlYh?;9Ngkg% z=MPD+`pXryN1T|<gNawNdd=;UY*-!iJ%fZlg5ae%11P)$2ZHLReoE^K;AUcD#eraI zW2Jw_o}UbWPh1qz6WDV!b&%%X8swB_9RdQoXQ{)60Sp4j=t<!Wv<??Fx&undkY6=M zp^U1^^|vSZdH*M=3==bK{v76w`IZ3wZG5KCDUVhG4eu7L$pZX=r7Q=0MZ%#Plpse0 zj|!yvsLx4Ko8Xbnye_ZV=o^f7N}iC#5Ggv6sUx^FGbFfYL(|d>%I7c?ZPLb3bqWr7 zU4bfG1y+?!bw)5Iq#8IqWN@G=Ru%Thxf)#=yL>^wZXSCC8we@>$hu=yrU;2=7>h;5 zvj_pYgKg2lKvNggl1ALnsz2IlcvL;q79buN5T3IhXuJvy@^crqWpB-5NOm<MvmU0^ zfWyxG^|~LQH?k{MJ@>{7UVfxmPJ>`?;Tn@qHzF+W!5W{8Z&ZAnDOquw6r4$<s?vdk zEX#m3wy&R243i23YR~}>bv*jM#5<a(8w<||oow_9eO$iZq{wch7;}iKWK!3H&^0ol z8E@^IWzGX#ifkn2ub+3V0d`jStB4u`@NX_8c-KJ7C%`3ms#ozk0o*zfOW~Yip*rlb zUJl)I++yRk2eAniJ|kvBc2mbjXhGd$f81**NxUDJ8^-T2NB3=FMYYlG>lc%3v|c~^ zdqo4LuxzkKhK4Q+JTK8tR_|i6O<y>(x#N2N0Fy5)!_trK&cn9odQu#Vlh1K~7q|rE z61#!ZPZ+G&Y7hqmY;`{XeDbQexC2@oFWY)Nzg@lL3GeEVRxWQlx@0?Zt`PcP0iq@6 zLgc)p&s$;*K_;q0L(mQ8mKqOJSrq$aQYO-Hbssf3P=wC6CvTVHudzJH-Jgm&foBSy zx0=qu$w477lIHk);XhaUR!R-tQOZ;tjLXFH6;%0)8^IAc*MO>Q;J={We(0OHaogG0 zE_C@bXic&m?F7sl<j`<kvQNoY+x(cEx&_vtQsf>FAB~x|n#>a^@u8lu;=!sqE*?vq zu4`(x!Jb4F#&3+jQ|ygldPjyYn#uCjNWR)%M3(L!?3C`miKT;~iv_)dll>Q6b+I&c zrlB04k&>mSYLR7-k{Od+lARt~3}Bv!LWY4>igJl!L5@;V21H6dNHIGr+qV551e@yL z`*SdKGPE^yF?FJ|`#L)RQ?LJ;8+={+|Cl<$*ZF@j^?$H%V;jqVqt#2B0yVr}Nry5R z5D?S9n+qB_yEqvdy9nFc+8WxK$XME$3ftSceLb+L(_id5MMc*hSrC;E1SaZYow%jh zPgo#1PKjE+1QB`Of|aNmX?}3TP;<rB;3uqb;g9~E0tVK(L4w^l=54%qh18};!;@_; z@0&K~<LxbhA3r|86~xxQf8*hHgA=JU{yy^1>y6~0iN}TKi3b+yvGk;)X&i3mTnf9M zuv3qvhErosfZ%Pb-Q>|BEm5(j-RV6Zf^$icM=sC-5^6MnAvcE9xzH@FwnDeG0YU{J zi~Fq?=bi0;Ir=hfOJu8PxC)qjYW~cv^+74Hs#GmU%Cw6?3LUUHh|Yab`spoqh8F@_ zm4bCy<ZGtgkO(7O6ao|0R7F>iXPx-Cp4!JpI~w!ShPfJOXsy>f*|$@P8L8(oeh#~w z-2a4IOeckn6}_TQ+rgl_gLArS3|Ml(i<`*Lqv6rWh$(Z5ycTYD#Z*&-5mpa}a_zHt z6E<A4-^`0kuY7AESY$HXWfy*HzhG7z?2bT&m)RVU4(*2{4<_QOjWG0vQru%^QFPsd z^=_dkmCX;Gh4gd6TZ-Shd}2ZLv1DRnfvrV{x3$X=F?BO5JDuJMl?o|L&G4fW{t*Hy z0r`iA*rc4RDWj<}$<)O77fc<&p@?JHGT@uCM*b2mgaMjlgF6EeRYNp)tC%P77Dog2 z+LV5{Pv`eXPJLX#2@DvL!Dq4XAGvB8L@?vh2HDRz6-s_tJmsh!7muM-H~f4^k#hUM z_)2yTu}%A{Sy{%opBC7kY5TeHwMh4<Gs%yve8vnxbf&GJz<&?9m7wNEjj!bL@+-^Z z`QKep;r|8P|L&C4YTD{Js;D1w%wxTbN61hT`Dk^E!9|18(#?{JQsptBQ6+(^*gP_! zjEH9Ylc0O@uh&}L?^^l=F4gUCm!EfnKU;m}=n;`E$C)*`PA+|)w@-N9-R>`T<bMF) zvUe*oM!%T)<QYId_y7tGfZfq(_wgwN4lCcG=U*lIMn)eEpeFdzhM)=5I&LGr5%6CL zwLk=U^hV6Q){z@$U;Lmq79Pfc?Mt>y-^L9RK-M*mN5AasoBhc|XWZ7=YRQSvG)3$v zgr&U_X`Ny0)IOZtX}e$wNUzTpD%iF7Rgf?nWoG2J@PsS-qK4OD!kJ?UfO+1|F*|Bo z1KU`qDA^;$0*4mUJ#{EPOm7)t#EdX=Yx1R2T&xlzzThfRC7eq@pX&%MO&2AZVO%zw zS;A{HtJiL=rfXDigS=NcWL-s>Rbv|=)7eDoOVnVI>DI_8x>{E>msC$kXsS}z?R6*x zi(yO`$WN)_F1$=18cbA^5|f`pZA+9DG_Zu8uW<yFpiK<#3yv}AsDFoUekjoayKE8M zjPw_zVj~_)I$#aVKR)E7T|zk&YqY39lcF(?h1DDN%IKEH?vITQ12w$+&QXzP%3F|U zHWgUxNnEzm-Gsv<g3L$p@s0<AoI#Vx6M9WnSWO%H`v;nON1{|V41;);$yGxk&Wj9w zi-tKE?|^N&2O0_NpVF%e54Zzraz^?CX*7F6iUS(7I6>?rA9IxUXx^QCAp3Gk1MSdq zBZv;_$W>*-zLL)F>Vn`}ti1k!%6{Q=g!g1J*`KONL#)M{ZC*%QzsNRaL|uJcGB7jD zTbUe%T(_x`UtlM!Ntp&-qu!v|mPZGcJw$mdnanY3Uo>5{oiFOjDr!ZznKz}iWT#x& z?*#;H$`M0VC|a~1u_<(}WD>ogx(EvF6A6S8l0<WvboGL}rf@^)8<C#!$wR!@Z}FvT zwAkT!?^@=SX*2^Z{C2Qi=`+=~IW^XdAX__JGhTi%m-paUE?w89xOAB2ESkg>%9U<( zH||OBbh8Tnzz*#bV8&$d#AZNF$xF9F2{_B`^(zWNC}af(V~J+EZAbeC2%hjKz3V1C zj#%d%Gf(uyQ@0Y6CcP^CWkq`n+YR^W0`_qkDw333O<0FoO9()vP^!tZ{`0zsNQx~E zb&BcBU>GTP2svE2Tmd;~73mj!_*V8uL?ZL<SWxNr<Id?L;Zz%@Kx^PNbTY{!BdtD9 z;P@>bx}{^l9+yvR5fas+w&0EpA?_<ah$HcP9OLfD8v-WTI03v0B6dPQ@R=i6?PANv zFRqVZxp>g?i9@A$j*?LnmctPDQG|zJ`=EF}Vx8aMD^LrtMvpNIR*|RHA`ctK*sbG= zjN7Q)(|dGpC}$+nt~bupuKSyaiU}Ws{?Tha@$q}cJ;tvH>+MuPih+B4d$Zbq9$Y*U z)iA(-dK?Ov@uCDq48Zm%%t5uw1GrnxDm7*ITGCEF!2UjA`BqPRiUR`yNq^zz|A3wU zG(8DAnY-GW+PR2&7@In{Sla(XnMz5Rk^*5u4UvCiDQs@hvZXoiziv{6*i?fihVI|( zPrY8SOcOIh9-AzyJ*wF4hq%ojB&Abrf;4kX@^-p$mmhr}xxn#fVU?ydm<YDTaK(W8 zc9I2?^9*?&_QB^{q$eVJ`lLK0y2^)*j7oo$hNR+4ziNvfmxev7Sd@~%0D=uyzE7ST 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 ScNgau(U?ep-K_E5zy1%ZQTdPn diff --git a/tools/android/packaging/gradle/wrapper/gradle-wrapper.properties b/tools/android/packaging/gradle/wrapper/gradle-wrapper.properties index 0d944d330c630..b82aa23a4f05d 100644 --- a/tools/android/packaging/gradle/wrapper/gradle-wrapper.properties +++ b/tools/android/packaging/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip - diff --git a/tools/android/packaging/gradlew b/tools/android/packaging/gradlew index 65dcd68d65c82..1aa94a4269074 100755 --- a/tools/android/packaging/gradlew +++ b/tools/android/packaging/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ From bf4742620b37f91dcea2923dc139750f71048a2e Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Mon, 19 Aug 2024 10:42:35 -0400 Subject: [PATCH 442/651] [xaudio2] wait for queued audio data to play when draining the sink - matches expectations of AE that the buffered data is played and not flushed - fixes incorrect GetDelay values after Drain() due to difference between MS doc and actual behavior of voice reset. - fixes incorrect m_framesInBuffers value when restarting playback of a drained sink. Confirmed by MS insider see https://stackoverflow.com/questions/65754955/how-to-reset-a-ixaudio2sourcevoices-samplesplayed-counter-after-flushing-sour --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 105 ++++++++++++++---- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h | 34 +++++- xbmc/cores/AudioEngine/Utils/AEUtil.cpp | 22 ++++ xbmc/cores/AudioEngine/Utils/AEUtil.h | 4 + 4 files changed, 140 insertions(+), 25 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 473495c16876d..3e399442928e8 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -239,18 +239,8 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi LARGE_INTEGER timerStop; LARGE_INTEGER timerFreq; #endif - size_t dataLenght = frames * m_format.m_frameSize; - struct buffer_ctx *ctx = new buffer_ctx; - ctx->data = new uint8_t[dataLenght]; - ctx->frames = frames; - ctx->sink = this; - memcpy(ctx->data, data[0] + offset * m_format.m_frameSize, dataLenght); - - XAUDIO2_BUFFER xbuffer = {}; - xbuffer.AudioBytes = dataLenght; - xbuffer.pAudioData = ctx->data; - xbuffer.pContext = ctx; + XAUDIO2_BUFFER xbuffer = BuildXAudio2Buffer(data, frames, offset); if (!m_running) //first time called, pre-fill buffer then start voice { @@ -259,7 +249,7 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi if (FAILED(hr)) { CLog::LogF(LOGERROR, "voice submit buffer failed due to {}", WASAPIErrToStr(hr)); - delete ctx; + delete xbuffer.pContext; return 0; } hr = m_sourceVoice->Start(0, XAUDIO2_COMMIT_NOW); @@ -267,7 +257,7 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi { CLog::LogF(LOGERROR, "voice start failed due to {}", WASAPIErrToStr(hr)); m_isDirty = true; //flag new device or re-init needed - delete ctx; + delete xbuffer.pContext; return INT_MAX; } m_sinkFrames += frames; @@ -292,7 +282,7 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi if (eventAudioCallback != WAIT_OBJECT_0) { CLog::LogF(LOGERROR, "voice buffer timed out"); - delete ctx; + delete xbuffer.pContext; return INT_MAX; } } @@ -319,7 +309,7 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi #ifdef _DEBUG CLog::LogF(LOGERROR, "submiting buffer failed due to {}", WASAPIErrToStr(hr)); #endif - delete ctx; + delete xbuffer.pContext; return INT_MAX; } @@ -843,23 +833,49 @@ void CAESinkXAudio::Drain() if(!m_sourceVoice) return; - AEDelayStatus status; - GetDelay(status); - - KODI::TIME::Sleep(std::chrono::milliseconds(static_cast<int>(status.GetDelay() * 500))); - if (m_running) { try { + // Contrary to MS doc, the voice must play a buffer with end of stream flag for the voice + // SamplesPlayed counter to be reset. + // Per MS doc, Discontinuity() may not take effect until after the entire buffer queue is + // consumed, which wouldn't invoke the callback or reset the voice stats. + // Solution: submit a 1 sample buffer with end of stream flag and wait for StreamEnd callback + // The StreamEnd event is manual reset so that it cannot be missed even if raised before this + // code starts waiting for it + + AddEndOfStreamPacket(); + + constexpr uint32_t waitSafety = 100; // extra ms wait in case of scheduler issue + DWORD waitRc = + WaitForSingleObject(m_voiceCallback.m_StreamEndEvent, + m_framesInBuffers * 1000 / m_format.m_sampleRate + waitSafety); + + if (waitRc != WAIT_OBJECT_0) + { + if (waitRc == WAIT_FAILED) + { + //! @todo use FormatMessage for a human readable error message + CLog::LogF(LOGERROR, + "error WAIT_FAILED while waiting for queued buffers to drain. GetLastError:{}", + GetLastError()); + } + else + { + CLog::LogF(LOGERROR, "error {} while waiting for queued buffers to drain.", waitRc); + } + } + m_sourceVoice->Stop(); - m_sourceVoice->FlushSourceBuffers(); + ResetEvent(m_voiceCallback.m_StreamEndEvent); + m_sinkFrames = 0; m_framesInBuffers = 0; } catch (...) { - CLog::Log(LOGDEBUG, "{}: Invalidated source voice - Releasing", __FUNCTION__); + CLog::Log(LOGERROR, "{}: Invalidated source voice - Releasing", __FUNCTION__); } } m_running = false; @@ -887,3 +903,48 @@ bool CAESinkXAudio::IsUSBDevice() #endif return false; } + +bool CAESinkXAudio::AddEndOfStreamPacket() +{ + constexpr unsigned int frames{1}; + + XAUDIO2_BUFFER xbuffer = BuildXAudio2Buffer(nullptr, frames, 0); + xbuffer.Flags = XAUDIO2_END_OF_STREAM; + + HRESULT hr = m_sourceVoice->SubmitSourceBuffer(&xbuffer); + + if (hr != S_OK) + { + CLog::LogF(LOGERROR, "SubmitSourceBuffer failed due to {:X}", hr); + delete xbuffer.pContext; + return false; + } + + m_sinkFrames += frames; + m_framesInBuffers += frames; + return true; +} + +XAUDIO2_BUFFER CAESinkXAudio::BuildXAudio2Buffer(uint8_t** data, + unsigned int frames, + unsigned int offset) +{ + const unsigned int dataLength{frames * m_format.m_frameSize}; + + struct buffer_ctx* ctx = new buffer_ctx; + ctx->data = new uint8_t[dataLength]; + ctx->frames = frames; + ctx->sink = this; + + if (data) + memcpy(ctx->data, data[0] + offset * m_format.m_frameSize, dataLength); + else + CAEUtil::GenerateSilence(m_format.m_dataFormat, m_format.m_frameSize, ctx->data, frames); + + XAUDIO2_BUFFER xbuffer{}; + xbuffer.AudioBytes = dataLength; + xbuffer.pAudioData = ctx->data; + xbuffer.pContext = ctx; + + return xbuffer; +} diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h index 6af787273272e..86158ca925b29 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h @@ -66,14 +66,27 @@ class CAESinkXAudio : public IAESink mBufferEnd.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE)); if (!mBufferEnd) { - throw std::exception("CreateEvent"); + throw std::exception("CreateEventEx BufferEnd"); } + if (NULL == (m_StreamEndEvent = CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, + EVENT_MODIFY_STATE | SYNCHRONIZE))) + { + throw std::exception("CreateEventEx StreamEnd"); + } + } + virtual ~VoiceCallback() + { + if (m_StreamEndEvent != NULL) + CloseHandle(m_StreamEndEvent); } - virtual ~VoiceCallback() { } STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) override {} STDMETHOD_(void, OnVoiceProcessingPassEnd)() override {} - STDMETHOD_(void, OnStreamEnd)() override {} + STDMETHOD_(void, OnStreamEnd)() override + { + if (m_StreamEndEvent != NULL) + SetEvent(m_StreamEndEvent); + } STDMETHOD_(void, OnBufferStart)(void*) override {} STDMETHOD_(void, OnBufferEnd)(void* context) override { @@ -95,10 +108,25 @@ class CAESinkXAudio : public IAESink } }; std::unique_ptr<void, handle_closer> mBufferEnd; + HANDLE m_StreamEndEvent{0}; }; bool InitializeInternal(std::string deviceId, AEAudioFormat &format); bool IsUSBDevice(); + /*! + * \brief Add a 1 frame long buffer with the end of stream flag to the voice. + * \return true for success, false for failure + */ + bool AddEndOfStreamPacket(); + /*! + * \brief Create a XAUDIO2_BUFFER with a struct buffer_ctx in pContext member, which must + * be deleted either manually or by XAudio2 BufferEnd callbak to avoid memory leaks. + * \param data data of the frames to copy. if null, the new buffer will contain silence. + * \param frames number of frames + * \param offset offset from the start in the data buffer. + * \return the new buffer + */ + XAUDIO2_BUFFER BuildXAudio2Buffer(uint8_t** data, unsigned int frames, unsigned int offset); Microsoft::WRL::ComPtr<IXAudio2> m_xAudio2; IXAudio2MasteringVoice* m_masterVoice; diff --git a/xbmc/cores/AudioEngine/Utils/AEUtil.cpp b/xbmc/cores/AudioEngine/Utils/AEUtil.cpp index 81a5f1995a291..3932760575d95 100644 --- a/xbmc/cores/AudioEngine/Utils/AEUtil.cpp +++ b/xbmc/cores/AudioEngine/Utils/AEUtil.cpp @@ -607,3 +607,25 @@ int CAEUtil::GetAVChannelIndex(enum AEChannel aechannel, uint64_t layout) av_channel_layout_uninit(&ch_layout); return idx; } + +void CAEUtil::GenerateSilence(AEDataFormat format, + unsigned int frameSize, + void* buffer, + unsigned int frames) + +{ + const unsigned int dataLength{frames * frameSize}; + + switch (format) + { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: + memset(buffer, 0x80, dataLength); + break; + + default: + // binary representation of 0 in all other formats regardless of number of bits per sample + // is a concatenation of 0 bytes. + memset(buffer, 0, dataLength); + } +} diff --git a/xbmc/cores/AudioEngine/Utils/AEUtil.h b/xbmc/cores/AudioEngine/Utils/AEUtil.h index 65a846e04402d..e47137ab95af7 100644 --- a/xbmc/cores/AudioEngine/Utils/AEUtil.h +++ b/xbmc/cores/AudioEngine/Utils/AEUtil.h @@ -178,4 +178,8 @@ class CAEUtil static uint64_t GetAVChannelMask(enum AEChannel aechannel); static enum AVChannel GetAVChannel(enum AEChannel aechannel); static int GetAVChannelIndex(enum AEChannel aechannel, uint64_t layout); + static void GenerateSilence(AEDataFormat format, + unsigned int frameSize, + void* buffer, + unsigned int frames); }; From b3b7410f68aa3177924068951c3bf4e9cf731203 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Sun, 25 Aug 2024 12:07:36 -0400 Subject: [PATCH 443/651] [xaudio2] wait for buffer release before destroying the source voice. Stop and FlushSourceBuffers are async and callbacks on the queued buffers don't seem to be called when destroying the voice, with a leak of the buffers' memory. --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 3e399442928e8..ab7f46adce5cc 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -163,12 +163,26 @@ void CAESinkXAudio::Deinitialize() { m_sourceVoice->Stop(); m_sourceVoice->FlushSourceBuffers(); + + // Stop and FlushSourceBuffers are async, wait for queued buffers to be released by XAudio2. + // callbacks don't seem to be called otherwise, with memory leakage. + XAUDIO2_VOICE_STATE state{}; + do + { + if (WAIT_OBJECT_0 != WaitForSingleObject(m_voiceCallback.mBufferEnd.get(), 500)) + { + CLog::LogF(LOGERROR, "timeout waiting for buffer flush - possible buffer memory leak"); + break; + } + m_sourceVoice->GetState(&state, 0); + } while (state.BuffersQueued > 0); + m_sinkFrames = 0; m_framesInBuffers = 0; } catch (...) { - CLog::Log(LOGDEBUG, "{}: Invalidated source voice - Releasing", __FUNCTION__); + CLog::Log(LOGERROR, "{}: Invalidated source voice - Releasing", __FUNCTION__); } } m_running = false; From eb9b34cff5490efac20b36ff24d83d04cd73e31e Mon Sep 17 00:00:00 2001 From: sarbes <sarbes@kodi.tv> Date: Fri, 6 Sep 2024 04:48:20 +0200 Subject: [PATCH 444/651] GL/GLES: implement mapping for various texture formats (#25205) GL: implement mapping for various texture formats --- xbmc/guilib/TextureGL.cpp | 196 +++++++++++++++++---- xbmc/guilib/TextureGL.h | 24 ++- xbmc/guilib/TextureGLES.cpp | 341 +++++++++++++++++++++++++++++++----- xbmc/guilib/TextureGLES.h | 21 +++ 4 files changed, 504 insertions(+), 78 deletions(-) diff --git a/xbmc/guilib/TextureGL.cpp b/xbmc/guilib/TextureGL.cpp index 78dd891b1fcc6..28da1cc000d56 100644 --- a/xbmc/guilib/TextureGL.cpp +++ b/xbmc/guilib/TextureGL.cpp @@ -9,6 +9,7 @@ #include "TextureGL.h" #include "ServiceBroker.h" +#include "guilib/TextureFormats.h" #include "guilib/TextureManager.h" #include "rendering/RenderSystem.h" #include "settings/AdvancedSettings.h" @@ -19,6 +20,118 @@ #include <memory> +namespace +{ +// clang-format off +static const std::map<KD_TEX_FMT, TextureFormat> TextureMapping +{ +#if defined(GL_EXT_texture_sRGB_R8) && (GL_EXT_texture_sRGB_RG8) + {KD_TEX_FMT_SDR_R8, {GL_R8, GL_SR8_EXT, GL_RED}}, + {KD_TEX_FMT_SDR_RG8, {GL_RG8, GL_SRG8_EXT, GL_RG}}, +#else + {KD_TEX_FMT_SDR_R8, {GL_R8, GL_FALSE, GL_RED}}, + {KD_TEX_FMT_SDR_RG8, {GL_RG8, GL_FALSE, GL_RG}}, +#endif + {KD_TEX_FMT_SDR_R5G6B5, {GL_RGB565, GL_FALSE, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}}, + {KD_TEX_FMT_SDR_RGB5_A1, {GL_RGB5_A1, GL_FALSE, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}}, + {KD_TEX_FMT_SDR_RGBA4, {GL_RGBA4, GL_FALSE, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}}, + {KD_TEX_FMT_SDR_RGB8, {GL_RGB8, GL_SRGB8, GL_RGB}}, + {KD_TEX_FMT_SDR_RGBA8, {GL_RGBA8, GL_SRGB8_ALPHA8, GL_RGBA}}, + {KD_TEX_FMT_SDR_BGRA8, {GL_RGBA8, GL_SRGB8_ALPHA8, GL_BGRA}}, + +#if defined(GL_VERSION_3_0) + {KD_TEX_FMT_HDR_R16f, {GL_R16F, GL_FALSE, GL_RED, GL_HALF_FLOAT}}, + {KD_TEX_FMT_HDR_RG16f, {GL_RG16F, GL_FALSE, GL_RG, GL_HALF_FLOAT}}, + {KD_TEX_FMT_HDR_R11F_G11F_B10F, {GL_R11F_G11F_B10F, GL_FALSE, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV}}, + {KD_TEX_FMT_HDR_RGB9_E5, {GL_RGB9_E5, GL_FALSE, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}}, + {KD_TEX_FMT_HDR_RGB10_A2, {GL_RGB10_A2, GL_FALSE, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}}, + {KD_TEX_FMT_HDR_RGBA16f, {GL_RGBA16F, GL_FALSE, GL_RGBA, GL_HALF_FLOAT}}, +#endif + +#if defined(GL_EXT_texture_compression_s3tc) && (GL_EXT_texture_sRGB) + {KD_TEX_FMT_S3TC_RGB8, {GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT}}, + {KD_TEX_FMT_S3TC_RGB8_A1, {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}}, + {KD_TEX_FMT_S3TC_RGB8_A4, {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}}, + {KD_TEX_FMT_S3TC_RGBA8, {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}}, +#elif defined(GL_EXT_texture_compression_s3tc) + {KD_TEX_FMT_S3TC_RGB8, {GL_COMPRESSED_RGB_S3TC_DXT1_EXT}}, + {KD_TEX_FMT_S3TC_RGB8_A1, {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT}}, + {KD_TEX_FMT_S3TC_RGB8_A4, {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT}}, + {KD_TEX_FMT_S3TC_RGBA8, {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT}}, +#elif defined(GL_EXT_texture_compression_dxt1) + {KD_TEX_FMT_S3TC_RGB8, {GL_COMPRESSED_RGB_S3TC_DXT1_EXT}}, + {KD_TEX_FMT_S3TC_RGB8_A1, {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT}}, +#endif + +#if defined(GL_EXT_texture_compression_rgtc) + {KD_TEX_FMT_RGTC_R11, {GL_COMPRESSED_RED_RGTC1_EXT}}, + {KD_TEX_FMT_RGTC_RG11, {GL_COMPRESSED_RED_GREEN_RGTC2_EXT}}, +#endif + +#if defined(GL_ARB_texture_compression_bptc) + {KD_TEX_FMT_BPTC_RGB16F, {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB}}, + {KD_TEX_FMT_BPTC_RGBA8, {GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB}}, +#endif + +#if defined(GL_VERSION_4_3) + {KD_TEX_FMT_ETC1_RGB8, {GL_COMPRESSED_RGB8_ETC2, GL_COMPRESSED_SRGB8_ETC2}}, + + {KD_TEX_FMT_ETC2_R11, {GL_COMPRESSED_R11_EAC}}, + {KD_TEX_FMT_ETC2_RG11, {GL_COMPRESSED_RG11_EAC}}, + {KD_TEX_FMT_ETC2_RGB8, {GL_COMPRESSED_RGB8_ETC2, GL_COMPRESSED_SRGB8_ETC2}}, + {KD_TEX_FMT_ETC2_RGB8_A1, {GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2}}, + {KD_TEX_FMT_ETC2_RGBA8, {GL_COMPRESSED_RGBA8_ETC2_EAC, GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC}}, +#endif + +#if defined(GL_KHR_texture_compression_astc_ldr) || (GL_KHR_texture_compression_astc_hdr) + {KD_TEX_FMT_ASTC_LDR_4x4, {GL_COMPRESSED_RGBA_ASTC_4x4_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR}}, + {KD_TEX_FMT_ASTC_LDR_5x4, {GL_COMPRESSED_RGBA_ASTC_5x4_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR}}, + {KD_TEX_FMT_ASTC_LDR_5x5, {GL_COMPRESSED_RGBA_ASTC_5x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR}}, + {KD_TEX_FMT_ASTC_LDR_6x5, {GL_COMPRESSED_RGBA_ASTC_6x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR}}, + {KD_TEX_FMT_ASTC_LDR_6x6, {GL_COMPRESSED_RGBA_ASTC_6x6_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}}, + {KD_TEX_FMT_ASTC_LDR_8x5, {GL_COMPRESSED_RGBA_ASTC_8x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR}}, + {KD_TEX_FMT_ASTC_LDR_8x6, {GL_COMPRESSED_RGBA_ASTC_8x6_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR}}, + {KD_TEX_FMT_ASTC_LDR_8x8, {GL_COMPRESSED_RGBA_ASTC_8x8_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR}}, + {KD_TEX_FMT_ASTC_LDR_10x5, {GL_COMPRESSED_RGBA_ASTC_10x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR}}, + {KD_TEX_FMT_ASTC_LDR_10x6, {GL_COMPRESSED_RGBA_ASTC_10x6_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR}}, + {KD_TEX_FMT_ASTC_LDR_10x8, {GL_COMPRESSED_RGBA_ASTC_10x8_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR}}, + {KD_TEX_FMT_ASTC_LDR_10x10, {GL_COMPRESSED_RGBA_ASTC_10x10_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}}, + {KD_TEX_FMT_ASTC_LDR_12x10, {GL_COMPRESSED_RGBA_ASTC_12x10_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR}}, + {KD_TEX_FMT_ASTC_LDR_12x12, {GL_COMPRESSED_RGBA_ASTC_12x12_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR}}, + + {KD_TEX_FMT_ASTC_HDR_4x4, {GL_COMPRESSED_RGBA_ASTC_4x4_KHR}}, + {KD_TEX_FMT_ASTC_HDR_5x4, {GL_COMPRESSED_RGBA_ASTC_5x4_KHR}}, + {KD_TEX_FMT_ASTC_HDR_5x5, {GL_COMPRESSED_RGBA_ASTC_5x5_KHR}}, + {KD_TEX_FMT_ASTC_HDR_6x5, {GL_COMPRESSED_RGBA_ASTC_6x5_KHR}}, + {KD_TEX_FMT_ASTC_HDR_6x6, {GL_COMPRESSED_RGBA_ASTC_6x6_KHR}}, + {KD_TEX_FMT_ASTC_HDR_8x5, {GL_COMPRESSED_RGBA_ASTC_8x5_KHR}}, + {KD_TEX_FMT_ASTC_HDR_8x6, {GL_COMPRESSED_RGBA_ASTC_8x6_KHR}}, + {KD_TEX_FMT_ASTC_HDR_8x8, {GL_COMPRESSED_RGBA_ASTC_8x8_KHR}}, + {KD_TEX_FMT_ASTC_HDR_10x5, {GL_COMPRESSED_RGBA_ASTC_10x5_KHR}}, + {KD_TEX_FMT_ASTC_HDR_10x6, {GL_COMPRESSED_RGBA_ASTC_10x6_KHR}}, + {KD_TEX_FMT_ASTC_HDR_10x8, {GL_COMPRESSED_RGBA_ASTC_10x8_KHR}}, + {KD_TEX_FMT_ASTC_HDR_10x10, {GL_COMPRESSED_RGBA_ASTC_10x10_KHR}}, + {KD_TEX_FMT_ASTC_HDR_12x10, {GL_COMPRESSED_RGBA_ASTC_12x10_KHR}}, + {KD_TEX_FMT_ASTC_HDR_12x12, {GL_COMPRESSED_RGBA_ASTC_12x12_KHR}}, +#endif +}; + +static const std::map<KD_TEX_SWIZ, Textureswizzle> SwizzleMap +{ + {KD_TEX_SWIZ_RGBA, {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}}, + {KD_TEX_SWIZ_RGB1, {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}}, + {KD_TEX_SWIZ_RRR1, {GL_RED, GL_RED, GL_RED, GL_ONE}}, + {KD_TEX_SWIZ_111R, {GL_ONE, GL_ONE, GL_ONE, GL_RED}}, + {KD_TEX_SWIZ_RRRG, {GL_RED, GL_RED, GL_RED, GL_GREEN}}, + {KD_TEX_SWIZ_RRRR, {GL_RED, GL_RED, GL_RED, GL_RED}}, + {KD_TEX_SWIZ_GGG1, {GL_GREEN, GL_GREEN, GL_GREEN, GL_ONE}}, + {KD_TEX_SWIZ_111G, {GL_ONE, GL_ONE, GL_ONE, GL_GREEN}}, + {KD_TEX_SWIZ_GGGA, {GL_GREEN, GL_GREEN, GL_GREEN, GL_ALPHA}}, + {KD_TEX_SWIZ_GGGG, {GL_GREEN, GL_GREEN, GL_GREEN, GL_GREEN}}, +}; +// clang-format on +} // namespace + std::unique_ptr<CTexture> CTexture::CreateTexture(unsigned int width, unsigned int height, XB_FMT format) @@ -33,6 +146,8 @@ CGLTexture::CGLTexture(unsigned int width, unsigned int height, XB_FMT format) CServiceBroker::GetRenderSystem()->GetRenderVersion(major, minor); if (major >= 3) m_isOglVersion3orNewer = true; + if (major > 3 || (major == 3 && minor >= 3)) + m_isOglVersion33orNewer = true; } CGLTexture::~CGLTexture() @@ -119,49 +234,30 @@ void CGLTexture::LoadToGPU() m_textureWidth = maxSize; } - GLenum format = GL_BGRA; - GLint numcomponents = GL_RGBA; + SetSwizzle(); + + TextureFormat glFormat = GetFormatGL(m_textureFormat); - switch (m_format) + if (glFormat.format == GL_FALSE) { - case XB_FMT_DXT1: - format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; - break; - case XB_FMT_DXT3: - format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; - break; - case XB_FMT_DXT5: - case XB_FMT_DXT5_YCoCg: - format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; - break; - case XB_FMT_RGB8: - format = GL_RGB; - numcomponents = GL_RGB; - break; - case XB_FMT_A8R8G8B8: - default: - break; + CLog::LogF(LOGDEBUG, "Failed to load texture. Unsupported format {}", m_textureFormat); + m_loadedToGPU = true; + return; } - if ((m_format & XB_FMT_DXT_MASK) == 0) + if ((m_textureFormat & KD_TEX_FMT_SDR) || (m_textureFormat & KD_TEX_FMT_HDR)) { - glTexImage2D(GL_TEXTURE_2D, 0, numcomponents, - m_textureWidth, m_textureHeight, 0, - format, GL_UNSIGNED_BYTE, m_pixels); + glTexImage2D(GL_TEXTURE_2D, 0, glFormat.internalFormat, m_textureWidth, m_textureHeight, 0, + glFormat.format, glFormat.type, m_pixels); } else { - glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, - m_textureWidth, m_textureHeight, 0, - GetPitch() * GetRows(), m_pixels); + glCompressedTexImage2D(GL_TEXTURE_2D, 0, glFormat.internalFormat, m_textureWidth, + m_textureHeight, 0, GetPitch() * GetRows(), m_pixels); } if (IsMipmapped() && m_isOglVersion3orNewer) - { glGenerateMipmap(GL_TEXTURE_2D); - } - - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); VerifyGLState(); @@ -185,3 +281,41 @@ void CGLTexture::BindToUnit(unsigned int unit) glBindTexture(GL_TEXTURE_2D, m_texture); } +void CGLTexture::SetSwizzle() +{ + if (!SwizzleMap.contains(m_textureSwizzle)) + return; + + Textureswizzle swiz = SwizzleMap.at(m_textureSwizzle); + + // GL_TEXTURE_SWIZZLE_RGBA and GL_TEXTURE_SWIZZLE_RGBA_EXT should be the same + // token, but just to be sure... +#if defined(GL_VERSION_3_3) || (GL_ARB_texture_swizzle) + if (m_isOglVersion33orNewer || + CServiceBroker::GetRenderSystem()->IsExtSupported("GL_ARB_texture_swizzle")) + { + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, &swiz.r); + return; + } +#endif +#if defined(GL_EXT_texture_swizzle) + if (CServiceBroker::GetRenderSystem()->IsExtSupported("GL_EXT_texture_swizzle")) + { + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA_EXT, &swiz.r); + return; + } +#endif +} + +TextureFormat CGLTexture::GetFormatGL(KD_TEX_FMT textureFormat) +{ + TextureFormat glFormat; + + const auto it = TextureMapping.find(textureFormat); + if (it != TextureMapping.cend()) + { + glFormat = it->second; + } + + return glFormat; +} diff --git a/xbmc/guilib/TextureGL.h b/xbmc/guilib/TextureGL.h index 0a979065e1be0..e3f68bc421ec2 100644 --- a/xbmc/guilib/TextureGL.h +++ b/xbmc/guilib/TextureGL.h @@ -12,6 +12,22 @@ #include "system_gl.h" +struct TextureFormat +{ + GLenum internalFormat{GL_FALSE}; + GLenum internalFormatSRGB{GL_FALSE}; + GLint format{GL_FALSE}; + GLenum type{GL_UNSIGNED_BYTE}; +}; + +struct Textureswizzle +{ + GLint r{GL_RED}; + GLint g{GL_GREEN}; + GLint b{GL_BLUE}; + GLint a{GL_ALPHA}; +}; + /************************************************************************/ /* CGLTexture */ /************************************************************************/ @@ -28,7 +44,11 @@ class CGLTexture : public CTexture void BindToUnit(unsigned int unit) override; protected: - GLuint m_texture = 0; - bool m_isOglVersion3orNewer = false; + void SetSwizzle(); + TextureFormat GetFormatGL(KD_TEX_FMT textureFormat); + + GLuint m_texture{0}; + bool m_isOglVersion3orNewer{false}; + bool m_isOglVersion33orNewer{false}; }; diff --git a/xbmc/guilib/TextureGLES.cpp b/xbmc/guilib/TextureGLES.cpp index 828d005edb73b..bf0785cc77f5e 100644 --- a/xbmc/guilib/TextureGLES.cpp +++ b/xbmc/guilib/TextureGLES.cpp @@ -9,6 +9,7 @@ #include "TextureGLES.h" #include "ServiceBroker.h" +#include "guilib/TextureFormats.h" #include "guilib/TextureManager.h" #include "rendering/RenderSystem.h" #include "settings/AdvancedSettings.h" @@ -19,6 +20,156 @@ #include <memory> +namespace +{ +// clang-format off +// GLES 2.0 texture formats. +// Any extension used here is in the core 3.0 profile (except BGRA) +// format = (unsized) internalFormat (with core 2.0) +static const std::map<KD_TEX_FMT, TextureFormat> TextureMappingGLES20 +{ +#if defined(GL_EXT_texture_rg) + {KD_TEX_FMT_SDR_R8, {GL_RED_EXT}}, + {KD_TEX_FMT_SDR_RG8, {GL_RG_EXT}}, +#endif + {KD_TEX_FMT_SDR_R5G6B5, {GL_RGB, GL_FALSE, GL_FALSE, GL_UNSIGNED_SHORT_5_6_5}}, + {KD_TEX_FMT_SDR_RGB5_A1, {GL_RGBA, GL_FALSE, GL_FALSE, GL_UNSIGNED_SHORT_5_5_5_1}}, + {KD_TEX_FMT_SDR_RGBA4, {GL_RGBA, GL_FALSE, GL_FALSE, GL_UNSIGNED_SHORT_4_4_4_4}}, +#if defined(GL_EXT_sRGB) + {KD_TEX_FMT_SDR_RGB8, {GL_RGB, GL_SRGB_EXT}}, + {KD_TEX_FMT_SDR_RGBA8, {GL_RGBA, GL_SRGB_ALPHA_EXT}}, +#else + {KD_TEX_FMT_SDR_RGB8, {GL_RGB}}, + {KD_TEX_FMT_SDR_RGBA8, {GL_RGBA}}, +#endif + +#if defined(GL_EXT_texture_format_BGRA8888) || (GL_IMG_texture_format_BGRA8888) + {KD_TEX_FMT_SDR_BGRA8, {GL_BGRA_EXT}}, +#endif + +#if defined(GL_EXT_texture_type_2_10_10_10_REV) + {KD_TEX_FMT_HDR_RGB10_A2, {GL_RGBA, GL_FALSE, GL_FALSE, GL_UNSIGNED_INT_2_10_10_10_REV_EXT}}, +#endif +#if defined(GL_OES_texture_half_float_linear) + {KD_TEX_FMT_HDR_RGBA16f, {GL_RGBA, GL_FALSE, GL_FALSE, GL_HALF_FLOAT_OES}}, +#endif + +#if defined(GL_OES_compressed_ETC1_RGB8_texture) + {KD_TEX_FMT_ETC1_RGB8, {GL_ETC1_RGB8_OES}}, +#endif +}; + +// GLES 3.0 texture formats. +#if defined(GL_ES_VERSION_3_0) +std::map<KD_TEX_FMT, TextureFormat> TextureMappingGLES30 +{ +#if defined(GL_EXT_texture_sRGB_R8) && (GL_EXT_texture_sRGB_RG8) // in gl2ext.h, but spec says >= 3.0 + {KD_TEX_FMT_SDR_R8, {GL_R8, GL_SR8_EXT, GL_RED}}, + {KD_TEX_FMT_SDR_RG8, {GL_RG8, GL_SRG8_EXT, GL_RG}}, +#else + {KD_TEX_FMT_SDR_R8, {GL_R8, GL_FALSE, GL_RED}}, + {KD_TEX_FMT_SDR_RG8, {GL_RG8, GL_FALSE, GL_RG}}, +#endif + {KD_TEX_FMT_SDR_R5G6B5, {GL_RGB565, GL_FALSE, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}}, + {KD_TEX_FMT_SDR_RGB5_A1, {GL_RGB5_A1, GL_FALSE, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}}, + {KD_TEX_FMT_SDR_RGBA4, {GL_RGBA4, GL_FALSE, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}}, + {KD_TEX_FMT_SDR_RGB8, {GL_RGB8, GL_SRGB8, GL_RGB}}, + {KD_TEX_FMT_SDR_RGBA8, {GL_RGBA8, GL_SRGB8_ALPHA8, GL_RGBA}}, + + {KD_TEX_FMT_HDR_R16f, {GL_R16F, GL_FALSE, GL_RED, GL_HALF_FLOAT}}, + {KD_TEX_FMT_HDR_RG16f, {GL_RG16F, GL_FALSE, GL_RG, GL_HALF_FLOAT}}, + {KD_TEX_FMT_HDR_R11F_G11F_B10F, {GL_R11F_G11F_B10F, GL_FALSE, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV}}, + {KD_TEX_FMT_HDR_RGB9_E5, {GL_RGB9_E5, GL_FALSE, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}}, + {KD_TEX_FMT_HDR_RGB10_A2, {GL_RGB10_A2, GL_FALSE, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}}, + {KD_TEX_FMT_HDR_RGBA16f, {GL_RGBA16F, GL_FALSE, GL_RGBA, GL_HALF_FLOAT}}, + + {KD_TEX_FMT_ETC1_RGB8, {GL_COMPRESSED_RGB8_ETC2, GL_COMPRESSED_SRGB8_ETC2}}, + + {KD_TEX_FMT_ETC2_R11, {GL_COMPRESSED_R11_EAC}}, + {KD_TEX_FMT_ETC2_RG11, {GL_COMPRESSED_RG11_EAC}}, + {KD_TEX_FMT_ETC2_RGB8, {GL_COMPRESSED_RGB8_ETC2, GL_COMPRESSED_SRGB8_ETC2}}, + {KD_TEX_FMT_ETC2_RGB8_A1, {GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2}}, + {KD_TEX_FMT_ETC2_RGBA8, {GL_COMPRESSED_RGBA8_ETC2_EAC, GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC}}, +}; +#endif // GL_ES_VERSION_3_0 + +// Common GLES extensions (texture compression) +static const std::map<KD_TEX_FMT, TextureFormat> TextureMappingGLESExtensions +{ +#if defined(GL_EXT_texture_compression_s3tc) && (GL_EXT_texture_compression_s3tc_srgb) + {KD_TEX_FMT_S3TC_RGB8, {GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT}}, + {KD_TEX_FMT_S3TC_RGB8_A1, {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}}, + {KD_TEX_FMT_S3TC_RGB8_A4, {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}}, + {KD_TEX_FMT_S3TC_RGBA8, {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}}, +#elif defined(GL_EXT_texture_compression_s3tc) + {KD_TEX_FMT_S3TC_RGB8, {GL_COMPRESSED_RGB_S3TC_DXT1_EXT}}, + {KD_TEX_FMT_S3TC_RGB8_A1, {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT}}, + {KD_TEX_FMT_S3TC_RGB8_A4, {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT}}, + {KD_TEX_FMT_S3TC_RGBA8, {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT}}, +#elif defined(GL_EXT_texture_compression_dxt1) + {KD_TEX_FMT_S3TC_RGB8, {GL_COMPRESSED_RGB_S3TC_DXT1_EXT}}, + {KD_TEX_FMT_S3TC_RGB8_A1, {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT}}, +#endif + +#if defined(GL_EXT_texture_compression_rgtc) + {KD_TEX_FMT_RGTC_R11, {GL_COMPRESSED_RED_RGTC1_EXT}}, + {KD_TEX_FMT_RGTC_RG11, {GL_COMPRESSED_RED_GREEN_RGTC2_EXT}}, +#endif + +#if defined(GL_EXT_texture_compression_bptc) + {KD_TEX_FMT_BPTC_RGB16F, {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT}}, + {KD_TEX_FMT_BPTC_RGBA8, {GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT}}, +#endif + +#if defined(GL_KHR_texture_compression_astc_ldr) || (GL_KHR_texture_compression_astc_hdr) + {KD_TEX_FMT_ASTC_LDR_4x4, {GL_COMPRESSED_RGBA_ASTC_4x4_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR}}, + {KD_TEX_FMT_ASTC_LDR_5x4, {GL_COMPRESSED_RGBA_ASTC_5x4_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR}}, + {KD_TEX_FMT_ASTC_LDR_5x5, {GL_COMPRESSED_RGBA_ASTC_5x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR}}, + {KD_TEX_FMT_ASTC_LDR_6x5, {GL_COMPRESSED_RGBA_ASTC_6x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR}}, + {KD_TEX_FMT_ASTC_LDR_6x6, {GL_COMPRESSED_RGBA_ASTC_6x6_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}}, + {KD_TEX_FMT_ASTC_LDR_8x5, {GL_COMPRESSED_RGBA_ASTC_8x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR}}, + {KD_TEX_FMT_ASTC_LDR_8x6, {GL_COMPRESSED_RGBA_ASTC_8x6_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR}}, + {KD_TEX_FMT_ASTC_LDR_8x8, {GL_COMPRESSED_RGBA_ASTC_8x8_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR}}, + {KD_TEX_FMT_ASTC_LDR_10x5, {GL_COMPRESSED_RGBA_ASTC_10x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR}}, + {KD_TEX_FMT_ASTC_LDR_10x6, {GL_COMPRESSED_RGBA_ASTC_10x6_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR}}, + {KD_TEX_FMT_ASTC_LDR_10x8, {GL_COMPRESSED_RGBA_ASTC_10x8_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR}}, + {KD_TEX_FMT_ASTC_LDR_10x10, {GL_COMPRESSED_RGBA_ASTC_10x10_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}}, + {KD_TEX_FMT_ASTC_LDR_12x10, {GL_COMPRESSED_RGBA_ASTC_12x10_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR}}, + {KD_TEX_FMT_ASTC_LDR_12x12, {GL_COMPRESSED_RGBA_ASTC_12x12_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR}}, + + {KD_TEX_FMT_ASTC_HDR_4x4, {GL_COMPRESSED_RGBA_ASTC_4x4_KHR}}, + {KD_TEX_FMT_ASTC_HDR_5x4, {GL_COMPRESSED_RGBA_ASTC_5x4_KHR}}, + {KD_TEX_FMT_ASTC_HDR_5x5, {GL_COMPRESSED_RGBA_ASTC_5x5_KHR}}, + {KD_TEX_FMT_ASTC_HDR_6x5, {GL_COMPRESSED_RGBA_ASTC_6x5_KHR}}, + {KD_TEX_FMT_ASTC_HDR_6x6, {GL_COMPRESSED_RGBA_ASTC_6x6_KHR}}, + {KD_TEX_FMT_ASTC_HDR_8x5, {GL_COMPRESSED_RGBA_ASTC_8x5_KHR}}, + {KD_TEX_FMT_ASTC_HDR_8x6, {GL_COMPRESSED_RGBA_ASTC_8x6_KHR}}, + {KD_TEX_FMT_ASTC_HDR_8x8, {GL_COMPRESSED_RGBA_ASTC_8x8_KHR}}, + {KD_TEX_FMT_ASTC_HDR_10x5, {GL_COMPRESSED_RGBA_ASTC_10x5_KHR}}, + {KD_TEX_FMT_ASTC_HDR_10x6, {GL_COMPRESSED_RGBA_ASTC_10x6_KHR}}, + {KD_TEX_FMT_ASTC_HDR_10x8, {GL_COMPRESSED_RGBA_ASTC_10x8_KHR}}, + {KD_TEX_FMT_ASTC_HDR_10x10, {GL_COMPRESSED_RGBA_ASTC_10x10_KHR}}, + {KD_TEX_FMT_ASTC_HDR_12x10, {GL_COMPRESSED_RGBA_ASTC_12x10_KHR}}, + {KD_TEX_FMT_ASTC_HDR_12x12, {GL_COMPRESSED_RGBA_ASTC_12x12_KHR}}, +#endif +}; + +static const std::map<KD_TEX_SWIZ, TextureSwizzle> SwizzleMapGLES +{ + {KD_TEX_SWIZ_RGBA, {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}}, + {KD_TEX_SWIZ_RGB1, {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}}, + {KD_TEX_SWIZ_RRR1, {GL_RED, GL_RED, GL_RED, GL_ONE}}, + {KD_TEX_SWIZ_111R, {GL_ONE, GL_ONE, GL_ONE, GL_RED}}, + {KD_TEX_SWIZ_RRRG, {GL_RED, GL_RED, GL_RED, GL_GREEN}}, + {KD_TEX_SWIZ_RRRR, {GL_RED, GL_RED, GL_RED, GL_RED}}, + {KD_TEX_SWIZ_GGG1, {GL_GREEN, GL_GREEN, GL_GREEN, GL_ONE}}, + {KD_TEX_SWIZ_111G, {GL_ONE, GL_ONE, GL_ONE, GL_GREEN}}, + {KD_TEX_SWIZ_GGGA, {GL_GREEN, GL_GREEN, GL_GREEN, GL_ALPHA}}, + {KD_TEX_SWIZ_GGGG, {GL_GREEN, GL_GREEN, GL_GREEN, GL_GREEN}}, +}; +// clang-format on +} // namespace + std::unique_ptr<CTexture> CTexture::CreateTexture(unsigned int width, unsigned int height, XB_FMT format) @@ -125,52 +276,42 @@ void CGLESTexture::LoadToGPU() } } - // All incoming textures are BGRA, which GLES does not necessarily support. - // Some (most?) hardware supports BGRA textures via an extension. - // If not, we convert to RGBA first to avoid having to swizzle in shaders. - // Explicitly define GL_BGRA_EXT here in the case that it's not defined by - // system headers, and trust the extension list instead. -#ifndef GL_BGRA_EXT -#define GL_BGRA_EXT 0x80E1 -#endif - - GLint internalformat; - GLenum pixelformat; - - switch (m_format) - { - default: - case XB_FMT_RGBA8: - internalformat = pixelformat = GL_RGBA; - break; - case XB_FMT_RGB8: - internalformat = pixelformat = GL_RGB; - break; - case XB_FMT_A8R8G8B8: - if (CServiceBroker::GetRenderSystem()->IsExtSupported("GL_EXT_texture_format_BGRA8888") || - CServiceBroker::GetRenderSystem()->IsExtSupported("GL_IMG_texture_format_BGRA8888")) - { - internalformat = pixelformat = GL_BGRA_EXT; - } - else if (CServiceBroker::GetRenderSystem()->IsExtSupported( - "GL_APPLE_texture_format_BGRA8888")) - { - // Apple's implementation does not conform to spec. Instead, they require - // differing format/internalformat, more like GL. - internalformat = GL_RGBA; - pixelformat = GL_BGRA_EXT; - } - else - { - SwapBlueRed(m_pixels, m_textureHeight, GetPitch()); - internalformat = pixelformat = GL_RGBA; - } - break; - } - - glTexImage2D(GL_TEXTURE_2D, 0, internalformat, m_textureWidth, m_textureHeight, 0, pixelformat, - GL_UNSIGNED_BYTE, m_pixels); + TextureFormat glesFormat; + if (m_isGLESVersion30orNewer) + { + KD_TEX_FMT textureFormat = m_textureFormat; + bool swapRB = false; + // Support for BGRA is hit and miss, swizzle instead + if (textureFormat == KD_TEX_FMT_SDR_BGRA8) + { + textureFormat = KD_TEX_FMT_SDR_RGBA8; + swapRB = true; + } + SetSwizzle(swapRB); + glesFormat = GetFormatGLES30(textureFormat); + } + else + { + glesFormat = GetFormatGLES20(m_textureFormat); + } + + if (glesFormat.internalFormat == GL_FALSE) + { + CLog::LogF(LOGDEBUG, "Failed to load texture. Unsupported format {}", m_textureFormat); + m_loadedToGPU = true; + return; + } + if ((m_textureFormat & KD_TEX_FMT_SDR) || (m_textureFormat & KD_TEX_FMT_HDR)) + { + glTexImage2D(GL_TEXTURE_2D, 0, glesFormat.internalFormat, m_textureWidth, m_textureHeight, 0, + glesFormat.format, glesFormat.type, m_pixels); + } + else + { + glCompressedTexImage2D(GL_TEXTURE_2D, 0, glesFormat.internalFormat, m_textureWidth, + m_textureHeight, 0, GetPitch() * GetRows(), m_pixels); + } if (IsMipmapped()) { glGenerateMipmap(GL_TEXTURE_2D); @@ -196,3 +337,113 @@ void CGLESTexture::BindToUnit(unsigned int unit) glActiveTexture(GL_TEXTURE0 + unit); glBindTexture(GL_TEXTURE_2D, m_texture); } + +void CGLESTexture::SetSwizzle(bool swapRB) +{ +#if defined(GL_ES_VERSION_3_0) + TextureSwizzle swiz; + + const auto it = SwizzleMapGLES.find(m_textureSwizzle); + if (it != SwizzleMapGLES.cend()) + swiz = it->second; + + if (swapRB) + { + SwapBlueRedSwizzle(swiz.r); + SwapBlueRedSwizzle(swiz.g); + SwapBlueRedSwizzle(swiz.b); + SwapBlueRedSwizzle(swiz.a); + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, swiz.r); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, swiz.g); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, swiz.b); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, swiz.a); +#endif +} + +void CGLESTexture::SwapBlueRedSwizzle(GLint& component) +{ + if (component == GL_RED) + component = GL_BLUE; + else if (component == GL_BLUE) + component = GL_RED; +} + +TextureFormat CGLESTexture::GetFormatGLES20(KD_TEX_FMT textureFormat) +{ + TextureFormat glFormat; + + // GLES 2.0 does not support swizzling. But for some Kodi formats+swizzles, + // we can map GLES formats (Luminance, Luminance-Alpha, BGRA). Other swizzles + // would have to be supported in the shader, or converted before upload. + + if (m_textureFormat == KD_TEX_FMT_SDR_R8 && m_textureSwizzle == KD_TEX_SWIZ_RRR1) + { + glFormat.format = glFormat.internalFormat = GL_LUMINANCE; + } + else if (m_textureFormat == KD_TEX_FMT_SDR_RG8 && m_textureSwizzle == KD_TEX_SWIZ_RRRG) + { + glFormat.format = glFormat.internalFormat = GL_LUMINANCE_ALPHA; + } + else if (m_textureFormat == KD_TEX_FMT_SDR_BGRA8 && m_textureSwizzle == KD_TEX_SWIZ_RGBA && + !CServiceBroker::GetRenderSystem()->IsExtSupported("GL_EXT_texture_format_BGRA8888") && + !CServiceBroker::GetRenderSystem()->IsExtSupported("GL_IMG_texture_format_BGRA8888")) + { +#if defined(GL_APPLE_texture_format_BGRA8888) + if (CServiceBroker::GetRenderSystem()->IsExtSupported("GL_APPLE_texture_format_BGRA8888")) + { + glFormat.internalFormat = GL_RGBA; + glFormat.format = GL_BGRA_EXT; + } + else +#endif + { + SwapBlueRed(m_pixels, m_textureHeight, GetPitch()); + glFormat.format = glFormat.internalFormat = GL_RGBA; + } + } + else if (textureFormat & KD_TEX_FMT_SDR || textureFormat & KD_TEX_FMT_HDR || + textureFormat & KD_TEX_FMT_ETC1) + { + const auto it = TextureMappingGLES20.find(textureFormat); + if (it != TextureMappingGLES20.cend()) + glFormat = it->second; + glFormat.format = glFormat.internalFormat; + } + else + { + const auto it = TextureMappingGLESExtensions.find(textureFormat); + if (it != TextureMappingGLESExtensions.cend()) + glFormat = it->second; + } + + return glFormat; +} + +TextureFormat CGLESTexture::GetFormatGLES30(KD_TEX_FMT textureFormat) +{ + TextureFormat glFormat; + + if (textureFormat & KD_TEX_FMT_SDR || textureFormat & KD_TEX_FMT_HDR) + { +#if defined(GL_ES_VERSION_3_0) + const auto it = TextureMappingGLES30.find(KD_TEX_FMT_SDR_RGBA8); + if (it != TextureMappingGLES30.cend()) + glFormat = it->second; +#else + const auto it = TextureMappingGLES20.find(textureFormat); + if (it != TextureMappingGLES20.cend()) + glFormat = it->second; + glFormat.format = glFormat.internalFormat; +#endif + } + else + { + const auto it = TextureMappingGLESExtensions.find(textureFormat); + if (it != TextureMappingGLESExtensions.cend()) + glFormat = it->second; + } + + return glFormat; +} diff --git a/xbmc/guilib/TextureGLES.h b/xbmc/guilib/TextureGLES.h index 4a9cb0502f882..92089506c50e0 100644 --- a/xbmc/guilib/TextureGLES.h +++ b/xbmc/guilib/TextureGLES.h @@ -12,6 +12,22 @@ #include "system_gl.h" +struct TextureFormat +{ + GLenum internalFormat{GL_FALSE}; + GLenum internalFormatSRGB{GL_FALSE}; + GLint format{GL_FALSE}; + GLenum type{GL_UNSIGNED_BYTE}; +}; + +struct TextureSwizzle +{ + GLint r{GL_RED}; + GLint g{GL_GREEN}; + GLint b{GL_BLUE}; + GLint a{GL_ALPHA}; +}; + class CGLESTexture : public CTexture { public: @@ -24,6 +40,11 @@ class CGLESTexture : public CTexture void BindToUnit(unsigned int unit) override; protected: + void SetSwizzle(bool swapRB); + void SwapBlueRedSwizzle(GLint& component); + TextureFormat GetFormatGLES20(KD_TEX_FMT textureFormat); + TextureFormat GetFormatGLES30(KD_TEX_FMT textureFormat); + GLuint m_texture = 0; bool m_isGLESVersion30orNewer{false}; }; From fec024078dac3fb9b91a370100adc77f55d7fd5f Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Fri, 6 Sep 2024 13:28:33 -0400 Subject: [PATCH 445/651] [AE] use correct format enum values for silence generation --- xbmc/cores/AudioEngine/Utils/AEUtil.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/AudioEngine/Utils/AEUtil.cpp b/xbmc/cores/AudioEngine/Utils/AEUtil.cpp index 3932760575d95..45928621cf284 100644 --- a/xbmc/cores/AudioEngine/Utils/AEUtil.cpp +++ b/xbmc/cores/AudioEngine/Utils/AEUtil.cpp @@ -618,8 +618,8 @@ void CAEUtil::GenerateSilence(AEDataFormat format, switch (format) { - case AV_SAMPLE_FMT_U8: - case AV_SAMPLE_FMT_U8P: + case AE_FMT_U8: + case AE_FMT_U8P: memset(buffer, 0x80, dataLength); break; From 054e10bfd0633e1cffae22cfaad409af9621eee9 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 25 Aug 2024 23:43:08 +0200 Subject: [PATCH 446/651] [addons][PVR] PVR add-on API: Introduce data types for add-on supplied settings. Extend struct PVR_TIMER and struct PVR_TIMER_TYPE to support add-on supplied settings. --- .../include/kodi/addon-instance/pvr/General.h | 915 +++++++++++++++++- .../include/kodi/addon-instance/pvr/Timers.h | 128 ++- .../c-api/addon-instance/pvr/pvr_defines.h | 11 +- .../c-api/addon-instance/pvr/pvr_general.h | 120 +++ .../c-api/addon-instance/pvr/pvr_timers.h | 19 +- 5 files changed, 1179 insertions(+), 14 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h index c2860829a4c35..cfd072791e243 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h @@ -23,8 +23,8 @@ namespace addon //============================================================================== /// @defgroup cpp_kodi_addon_pvr_Defs_PVRTypeIntValue class PVRTypeIntValue /// @ingroup cpp_kodi_addon_pvr_Defs_General -/// @brief **PVR add-on type value**\n -/// Representation of a <b>`<int, std::string>`</b> event related value. +/// @brief **PVR add-on int type value**\n +/// Representation of a <b>`<int, std::string>`</b> value. /// /// ---------------------------------------------------------------------------- /// @@ -92,7 +92,7 @@ class PVRTypeIntValue : public DynamicCStructHdl<PVRTypeIntValue, PVR_ATTRIBUTE_ for (unsigned int i = 0; i < source.size(); ++i) { values[i].iValue = source[i].GetCStructure()->iValue; - AllocResources(source[i].GetCStructure(), &values[i]); + AllocResources(source[i].GetCStructure(), &values[i]); // handles strDescription } return values; } @@ -104,7 +104,7 @@ class PVRTypeIntValue : public DynamicCStructHdl<PVRTypeIntValue, PVR_ATTRIBUTE_ for (unsigned int i = 0; i < size; ++i) { values[i].iValue = source[i].iValue; - AllocResources(&source[i], &values[i]); + AllocResources(&source[i], &values[i]); // handles strDescription } return values; } @@ -147,6 +147,913 @@ class PVRTypeIntValue : public DynamicCStructHdl<PVRTypeIntValue, PVR_ATTRIBUTE_ ///@} //------------------------------------------------------------------------------ +//============================================================================== +/// @defgroup cpp_kodi_addon_pvr_Defs_PVRTypeStringValue class PVRTypeStringValue +/// @ingroup cpp_kodi_addon_pvr_Defs_General +/// @brief **PVR add-on string type value**\n +/// Representation of a <b>`<std::string, std::string>`</b> value. +/// +/// ---------------------------------------------------------------------------- +/// +/// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeStringValue_Help +/// +///@{ +class PVRTypeStringValue : public DynamicCStructHdl<PVRTypeStringValue, PVR_ATTRIBUTE_STRING_VALUE> +{ + friend class CInstancePVRClient; + +public: + /*! \cond PRIVATE */ + PVRTypeStringValue(const PVRTypeStringValue& data) : DynamicCStructHdl(data) {} + /*! \endcond */ + + /// @defgroup cpp_kodi_addon_pvr_Defs_PVRTypeStringValue_Help Value Help + /// @ingroup cpp_kodi_addon_pvr_Defs_PVRTypeStringValue + /// + /// <b>The following table contains values that can be set with @ref cpp_kodi_addon_pvr_Defs_PVRTypeStringValue :</b> + /// | Name | Type | Set call | Get call + /// |------|------|----------|---------- + /// | **Value** | `std::string` | @ref PVRTypeStringValue::SetValue "SetValue" | @ref PVRTypeStringValue::GetValue "GetValue" + /// | **Description** | `std::string` | @ref PVRTypeStringValue::SetDescription "SetDescription" | @ref PVRTypeStringValue::GetDescription "GetDescription" + /// + /// @remark Further can there be used his class constructor to set values. + + /// @addtogroup cpp_kodi_addon_pvr_Defs_PVRTypeStringValue + ///@{ + + /// @brief Default class constructor. + PVRTypeStringValue() = default; + + /// @brief Class constructor with integrated value set. + /// + /// @param[in] value Type identification value + /// @param[in] description Type description text + PVRTypeStringValue(const std::string& value, const std::string& description) + { + SetValue(value); + SetDescription(description); + } + + /// @brief To set with the identification value. + void SetValue(const std::string& value) + { + ReallocAndCopyString(&m_cStructure->strValue, value.c_str()); + } + + /// @brief To get with the identification value. + std::string GetValue() const { return m_cStructure->strValue ? m_cStructure->strValue : ""; } + + /// @brief To set with the description text of the value. + void SetDescription(const std::string& description) + { + ReallocAndCopyString(&m_cStructure->strDescription, description.c_str()); + } + + /// @brief To get with the description text of the value. + std::string GetDescription() const + { + return m_cStructure->strDescription ? m_cStructure->strDescription : ""; + } + ///@} + + static PVR_ATTRIBUTE_STRING_VALUE* AllocAndCopyData(const std::vector<PVRTypeStringValue>& source) + { + PVR_ATTRIBUTE_STRING_VALUE* values = new PVR_ATTRIBUTE_STRING_VALUE[source.size()]{}; + for (unsigned int i = 0; i < source.size(); ++i) + AllocResources(source[i].GetCStructure(), &values[i]); // handles strValue, strDescription + return values; + } + + static PVR_ATTRIBUTE_STRING_VALUE* AllocAndCopyData(const PVR_ATTRIBUTE_STRING_VALUE* source, + unsigned int size) + { + PVR_ATTRIBUTE_STRING_VALUE* values = new PVR_ATTRIBUTE_STRING_VALUE[size]{}; + for (unsigned int i = 0; i < size; ++i) + AllocResources(&source[i], &values[i]); // handles strValue, strDescription + return values; + } + + static void AllocResources(const PVR_ATTRIBUTE_STRING_VALUE* source, + PVR_ATTRIBUTE_STRING_VALUE* target) + { + target->strValue = AllocAndCopyString(source->strValue); + target->strDescription = AllocAndCopyString(source->strDescription); + } + + static void FreeResources(PVR_ATTRIBUTE_STRING_VALUE* target) + { + FreeString(target->strValue); + target->strValue = nullptr; + FreeString(target->strDescription); + target->strDescription = nullptr; + } + + static void FreeResources(PVR_ATTRIBUTE_STRING_VALUE* values, unsigned int size) + { + for (unsigned int i = 0; i < size; ++i) + FreeResources(&values[i]); + delete[] values; + } + + static void ReallocAndCopyData(PVR_ATTRIBUTE_STRING_VALUE** source, + unsigned int* size, + const std::vector<PVRTypeStringValue>& values) + { + FreeResources(*source, *size); + *source = nullptr; + *size = values.size(); + if (*size) + *source = AllocAndCopyData(values); + } + +private: + PVRTypeStringValue(const PVR_ATTRIBUTE_STRING_VALUE* data) : DynamicCStructHdl(data) {} + PVRTypeStringValue(PVR_ATTRIBUTE_STRING_VALUE* data) : DynamicCStructHdl(data) {} +}; +///@} +//------------------------------------------------------------------------------ + +//============================================================================== +/// @defgroup cpp_kodi_addon_pvr_Defs_PVRIntSettingDefinition class PVRIntSettingDefinition +/// @ingroup cpp_kodi_addon_pvr_Defs_General +/// @brief **PVR add-on integer setting definition**\n +/// Representation of an integer setting definition. +/// +/// ---------------------------------------------------------------------------- +/// +/// @copydetails cpp_kodi_addon_pvr_Defs_PVRIntSettingDefinition_Help +/// +///@{ +class PVRIntSettingDefinition + : public DynamicCStructHdl<PVRIntSettingDefinition, PVR_INT_SETTING_DEFINITION> +{ + friend class CInstancePVRClient; + +public: + /*! \cond PRIVATE */ + PVRIntSettingDefinition() { m_cStructure->iStep = 1; } + PVRIntSettingDefinition(const PVRIntSettingDefinition& def) : DynamicCStructHdl(def) {} + /*! \endcond */ + + /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVRIntSettingDefinition_Help Value Help + /// @ingroup cpp_kodi_addon_pvr_Defs_General_PVRIntSettingDefinition + /// ---------------------------------------------------------------------------- + /// + /// <b>The following table contains values that can be set with @ref cpp_kodi_addon_pvr_Defs_General_PVRIntSettingDefinition :</b> + /// | Name | Type | Set call | Get call | Usage + /// |------|------|----------|----------|----------- + /// | **Values** | @ref cpp_kodi_addon_pvr_Defs_PVRTypeIntValue "PVRTypeIntValue" | @ref PVRIntSettingDefinition::SetValues "SetValues" | @ref PVRIntSettingDefinition:GetValues "GetValues" | *optional* + /// | **Default value** | `int`| @ref PVRIntSettingDefinition::SetDefaultValue "SetDefaultValue" | @ref PVRIntSettingDefinition::GetDefaultValue "GetDefaultValue" | *optional* + /// | **Min value** | `int`| @ref PVRIntSettingDefinition::SetMinValue "SetMinValue" | @ref PVRIntSettingDefinition::GetMinValue "GetMinValue" | *optional* + /// | **Step** | `int`| @ref PVRIntSettingDefinition::SetStep "SetStep" | @ref PVRIntSettingDefinition::GetStep "GetStep" | *optional* + /// | **Max value** | `int`| @ref PVRIntSettingDefinition::SetMaxValue "SetMaxValue" | @ref PVRIntSettingDefinition::GetMaxValue "GetMaxValue" | *optional* + /// + + /// @addtogroup cpp_kodi_addon_pvr_Defs_PVRIntSettingDefinition + ///@{ + + /// @brief Class constructor with integrated values. + /// + /// @param[in] settingValues possible setting values + /// @param[in] defaultValue default setting value + /// @param[in] minValue minimim setting value + /// @param[in] step amount to change values from min to max + /// @param[in] maxValue maximum setting value + PVRIntSettingDefinition(const std::vector<PVRTypeIntValue>& settingValues, + int defaultValue, + int minValue, + int step, + int maxValue) + { + SetValues(settingValues); + SetDefaultValue(defaultValue); + SetMinValue(minValue); + SetStep(step); + SetMaxValue(maxValue); + } + + /// @brief **optional**\n + /// value definitions. + /// + /// Array containing the possible settings values. If left blank, any int value is accepted. + /// + /// @param[in] values List of possible values + /// @param[in] defaultValue [opt] The default value in list, can also be + /// set by @ref SetDefaultValue() + /// + /// -------------------------------------------------------------------------- + /// + /// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeIntValue_Help + void SetValues(const std::vector<PVRTypeIntValue>& values, int defaultValue = -1) + { + PVRTypeIntValue::ReallocAndCopyData(&m_cStructure->values, &m_cStructure->iValuesSize, values); + if (defaultValue != -1) + m_cStructure->iDefaultValue = defaultValue; + } + + /// @brief To get with @ref SetValues changed values. + std::vector<PVRTypeIntValue> GetValues() const + { + std::vector<PVRTypeIntValue> ret; + for (unsigned int i = 0; i < m_cStructure->iValuesSize; ++i) + ret.emplace_back(m_cStructure->values[i].iValue, m_cStructure->values[i].strDescription); + return ret; + } + + /// @brief **optional**\n + /// The default value for this setting. + void SetDefaultValue(int defaultValue) { m_cStructure->iDefaultValue = defaultValue; } + + /// @brief To get with @ref SetDefaultValue changed values. + int GetDefaultValue() const { return m_cStructure->iDefaultValue; } + + /// @brief **optional**\n + /// The minimum value for this setting. + void SetMinValue(int minValue) { m_cStructure->iMinValue = minValue; } + + /// @brief To get with @ref SetMinValue changed values. + int GetMinValue() const { return m_cStructure->iMinValue; } + + /// @brief **optional**\n + /// The amount for increasing the values for this setting from min to max. + void SetStep(int step) { m_cStructure->iStep = step; } + + /// @brief To get with @ref SetStep changed values. + int GetStep() const { return m_cStructure->iStep; } + + /// @brief **optional**\n + /// The maximum value for this setting. + void SetMaxValue(int maxValue) { m_cStructure->iMaxValue = maxValue; } + + /// @brief To get with @ref SetMaxValue changed values. + int GetMaxValue() const { return m_cStructure->iMaxValue; } + ///@} + + static PVR_INT_SETTING_DEFINITION* AllocAndCopyData(const PVRIntSettingDefinition& source) + { + PVR_INT_SETTING_DEFINITION* def = new PVR_INT_SETTING_DEFINITION{}; + AllocResources(source.GetCStructure(), def); // handles values, iValuesSize + def->iDefaultValue = source.GetCStructure()->iDefaultValue; + def->iMinValue = source.GetCStructure()->iMinValue; + def->iStep = source.GetCStructure()->iStep; + def->iMaxValue = source.GetCStructure()->iMaxValue; + return def; + } + + static PVR_INT_SETTING_DEFINITION* AllocAndCopyData(PVR_INT_SETTING_DEFINITION* source) + { + PVR_INT_SETTING_DEFINITION* def = new PVR_INT_SETTING_DEFINITION{}; + AllocResources(source, def); // handles values, iValuesSize + def->iDefaultValue = source->iDefaultValue; + def->iMinValue = source->iMinValue; + def->iStep = source->iStep; + def->iMaxValue = source->iMaxValue; + return def; + } + + static void AllocResources(const PVR_INT_SETTING_DEFINITION* source, + PVR_INT_SETTING_DEFINITION* target) + { + target->values = PVRTypeIntValue::AllocAndCopyData(source->values, source->iValuesSize); + target->iValuesSize = source->iValuesSize; + } + + static void FreeResources(PVR_INT_SETTING_DEFINITION* target) + { + PVRTypeIntValue::FreeResources(target->values, target->iValuesSize); + target->values = nullptr; + target->iValuesSize = 0; + } + + static void ReallocAndCopyData(PVR_INT_SETTING_DEFINITION** source, + const PVRIntSettingDefinition& def) + { + if (*source) + FreeResources(*source); + *source = AllocAndCopyData(def); + } + +private: + PVRIntSettingDefinition(const PVR_INT_SETTING_DEFINITION* def) : DynamicCStructHdl(def) {} + PVRIntSettingDefinition(PVR_INT_SETTING_DEFINITION* def) : DynamicCStructHdl(def) {} +}; +///@} +//------------------------------------------------------------------------------ + +//============================================================================== +/// @defgroup cpp_kodi_addon_pvr_Defs_PVRStringSettingDefinition class PVRStringSettingDefinition +/// @ingroup cpp_kodi_addon_pvr_Defs_General +/// @brief **PVR add-on string setting definition**\n +/// Representation of a string setting definition. +/// +/// ---------------------------------------------------------------------------- +/// +/// @copydetails cpp_kodi_addon_pvr_Defs_PVRStringSettingDefinition_Help +/// +///@{ +class PVRStringSettingDefinition + : public DynamicCStructHdl<PVRStringSettingDefinition, PVR_STRING_SETTING_DEFINITION> +{ + friend class CInstancePVRClient; + +public: + /*! \cond PRIVATE */ + PVRStringSettingDefinition() { m_cStructure->bAllowEmptyValue = true; } + PVRStringSettingDefinition(const PVRStringSettingDefinition& def) : DynamicCStructHdl(def) {} + /*! \endcond */ + + /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVRStringSettingDefinition_Help Value Help + /// @ingroup cpp_kodi_addon_pvr_Defs_General_PVRStringSettingDefinition + /// ---------------------------------------------------------------------------- + /// + /// <b>The following table contains values that can be set with @ref cpp_kodi_addon_pvr_Defs_General_PVRStringSettingDefinition :</b> + /// | Name | Type | Set call | Get call | Usage + /// |------|------|----------|----------|----------- + /// | **Values** | @ref cpp_kodi_addon_pvr_Defs_PVRTypeStringValue "PVRTypeStringValue" | @ref PVRStringSettingDefinition::SetValues "SetValues" | @ref PVRStringSettingDefinition:GetValues "GetValues" | *optional* + /// | **Default value** | `std::string`| @ref PVRStringSettingDefinition::SetDefaultValue "SetDefaultValue" | @ref PVRStringSettingDefinition::GetDefaultValue "GetDefaultValue" | *optional* + /// | **Allow empty value** | `bool`| @ref PVRStringSettingDefinition::SetAllowEmptyValue "SetAllowEmptyValue" | @ref PVRStringSettingDefinition::GetetAllowEmptyValue "GetetAllowEmptyValue" | *optional* + /// + + /// @addtogroup cpp_kodi_addon_pvr_Defs_PVRStringSettingDefinition + ///@{ + + /// @brief Class constructor with integrated values. + /// + /// @param[in] settingValues possible setting values + /// @param[in] defaultValue default setting value + /// @param[in] allowEmptyValues allow empty values flag + PVRStringSettingDefinition(const std::vector<PVRTypeStringValue>& settingValues, + const std::string& defaultValue, + bool allowEmptyValue) + { + SetValues(settingValues); + SetDefaultValue(defaultValue); + SetAllowEmptyValue(allowEmptyValue); + } + + /// @brief **optional**\n + /// value definitions. + /// + /// Array containing the possible settings values. If left blank, any string value is accepted. + /// + /// @param[in] values List of possible values + /// @param[in] defaultValue [opt] The default value in list, can also be + /// set by @ref SetDefaultValue() + /// + /// -------------------------------------------------------------------------- + /// + /// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeStringValue_Help + void SetValues(const std::vector<PVRTypeStringValue>& values, + const std::string& defaultValue = "") + { + PVRTypeStringValue::ReallocAndCopyData(&m_cStructure->values, &m_cStructure->iValuesSize, + values); + ReallocAndCopyString(&m_cStructure->strDefaultValue, defaultValue.c_str()); + } + + /// @brief To get with @ref SetValues changed values. + std::vector<PVRTypeStringValue> GetValues() const + { + std::vector<PVRTypeStringValue> ret; + for (unsigned int i = 0; i < m_cStructure->iValuesSize; ++i) + ret.emplace_back(m_cStructure->values[i].strValue, m_cStructure->values[i].strDescription); + return ret; + } + + /// @brief **optional**\n + /// The default value for this setting. + void SetDefaultValue(const std::string& defaultValue) + { + ReallocAndCopyString(&m_cStructure->strDefaultValue, defaultValue.c_str()); + } + + /// @brief To get with @ref SetDefaultValue changed values. + std::string GetDefaultValue() const + { + return m_cStructure->strDefaultValue ? m_cStructure->strDefaultValue : ""; + } + + /// @brief **optional**\n + /// The allow empty values flag for this setting. + void SetAllowEmptyValue(bool allowEmptyValue) + { + m_cStructure->bAllowEmptyValue = allowEmptyValue; + } + + /// @brief To get with @ref SetAllowEmptyValue changed values. + bool GetAllowEmptyValue() const { return m_cStructure->bAllowEmptyValue; } + ///@} + + static PVR_STRING_SETTING_DEFINITION* AllocAndCopyData(const PVRStringSettingDefinition& source) + { + PVR_STRING_SETTING_DEFINITION* def = new PVR_STRING_SETTING_DEFINITION{}; + AllocResources(source.GetCStructure(), def); // handles strDefaultValue, values, iValuesSize + def->bAllowEmptyValue = source.GetCStructure()->bAllowEmptyValue; + return def; + } + + static PVR_STRING_SETTING_DEFINITION* AllocAndCopyData(PVR_STRING_SETTING_DEFINITION* source) + { + PVR_STRING_SETTING_DEFINITION* def = new PVR_STRING_SETTING_DEFINITION{}; + AllocResources(source, def); // handles strDefaultValue, values, iValuesSize + def->bAllowEmptyValue = source->bAllowEmptyValue; + return def; + } + + static void AllocResources(const PVR_STRING_SETTING_DEFINITION* source, + PVR_STRING_SETTING_DEFINITION* target) + { + target->strDefaultValue = AllocAndCopyString(source->strDefaultValue); + target->values = PVRTypeStringValue::AllocAndCopyData(source->values, source->iValuesSize); + target->iValuesSize = source->iValuesSize; + } + + static void FreeResources(PVR_STRING_SETTING_DEFINITION* target) + { + PVRTypeStringValue::FreeResources(target->values, target->iValuesSize); + target->values = nullptr; + target->iValuesSize = 0; + + FreeString(target->strDefaultValue); + target->strDefaultValue = nullptr; + } + + static void ReallocAndCopyData(PVR_STRING_SETTING_DEFINITION** source, + const PVRStringSettingDefinition& def) + { + if (*source) + FreeResources(*source); + *source = AllocAndCopyData(def); + } + +private: + PVRStringSettingDefinition(const PVR_STRING_SETTING_DEFINITION* def) : DynamicCStructHdl(def) {} + PVRStringSettingDefinition(PVR_STRING_SETTING_DEFINITION* def) : DynamicCStructHdl(def) {} +}; +///@} +//------------------------------------------------------------------------------ + +//============================================================================== +/// @defgroup cpp_kodi_addon_pvr_Defs_PVRSettingDefinition class PVRSettingDefinition +/// @ingroup cpp_kodi_addon_pvr_Defs_General +/// @brief **PVR add-on setting definition**\n +/// Representation of a setting definition. +/// +/// ---------------------------------------------------------------------------- +/// +/// @copydetails cpp_kodi_addon_pvr_Defs_PVRSettingDefinition_Help +/// +///@{ +class PVRSettingDefinition : public DynamicCStructHdl<PVRSettingDefinition, PVR_SETTING_DEFINITION> +{ + friend class CInstancePVRClient; + +public: + /*! \cond PRIVATE */ + PVRSettingDefinition() = default; + PVRSettingDefinition(const PVRSettingDefinition& type) : DynamicCStructHdl(type) {} + /*! \endcond */ + + /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVRSettingDefinition_Help Value Help + /// @ingroup cpp_kodi_addon_pvr_Defs_General_PVRSettingDefinition + /// ---------------------------------------------------------------------------- + /// + /// <b>The following table contains values that can be set with @ref cpp_kodi_addon_pvr_Defs_General_PVRSettingDefinition :</b> + /// | Name | Type | Set call | Get call | Usage + /// |------|------|----------|----------|----------- + /// | **Identifier** | `unsigned int` | @ref PVRSettingDefinition::SetId "SetId" | @ref PVRSettingDefinition::GetId "GetId" | *required to set* + /// | **Name** | `std::string` | @ref PVRSettingDefinition::SetName "SetName" | @ref PVRSettingDefinition::GetName "GetName" | *required to set* + /// | **Type** | @ref PVR_SETTING_TYPE | @ref PVRSettingDefinition::SetType "SetType" | @ref PVRSettingDefinition:GetType "GetType" | *required to set* + /// | | | | | | + /// | **Read-only conditions** | `uint64_t`| @ref PVRSettingDefinition::SetReadonlyConditions "SetReadonlyConditions" | @ref PVRSettingDefinition::GetReadonlyConditions "GetReadonlyConditions" | *optional* + /// | | | | | | + /// | **Int Definition** | @ref cpp_kodi_addon_pvr_Defs_PVRIntSettingDefinition "PVRIntSettingDefinition" | @ref PVRSettingDefinition::SetIntDefinition "SetIntDefinition" | @ref PVRSettingDefinition:GetIntDefinition "GetIntDefinition" | *optional* + /// | **String Definition** | @ref cpp_kodi_addon_pvr_Defs_PVRStringSettingDefinition "PVRStringSettingDefinition" | @ref PVRSettingDefinition::SetStringValues "SetStringDefinition" | @ref PVRSettingDefinition:GetStringDefinition "GetStringDefinition" | *optional* + /// + + /// @addtogroup cpp_kodi_addon_pvr_Defs_PVRSettingDefinition + ///@{ + + /// @brief Class constructor with integrated values. + /// + /// @param[in] settingDefId Setting definition identification value + /// @param[in] settingDefName Setting definition name + /// @param[in] readonlyConditions readonly conditions value + /// @param[in] settingDef int setting definition + PVRSettingDefinition(unsigned int settingDefId, + const std::string& settingDefName, + uint64_t readonlyConditions, + const PVRIntSettingDefinition& settingDef) + { + SetId(settingDefId); + SetName(settingDefName); + SetType(PVR_SETTING_TYPE::INTEGER); + SetReadonlyConditions(readonlyConditions); + SetIntDefinition(settingDef); + } + + /// @brief Class constructor with integrated values. + /// + /// @param[in] settingDefId Setting definition identification value + /// @param[in] settingDefName Setting definition name + /// @param[in] readonlyConditions readonly conditions value + /// @param[in] settingDef string setting definition + PVRSettingDefinition(unsigned int settingDefId, + const std::string& settingDefName, + uint64_t readonlyConditions, + const PVRStringSettingDefinition& settingDef) + { + SetId(settingDefId); + SetName(settingDefName); + SetType(PVR_SETTING_TYPE::STRING); + SetReadonlyConditions(readonlyConditions); + SetStringDefinition(settingDef); + } + + /// @brief Class constructor with integrated values. + /// + /// @param[in] settingDefId Setting definition identification value + /// @param[in] settingDefName Setting definition name + /// @param[in] eType Setting type + /// @param[in] readonlyConditions readonly conditions value + /// @param[in] intSettingDef int setting definition + /// @param[in] stringSettingDef string setting definition + PVRSettingDefinition(unsigned int settingDefId, + const std::string& settingDefName, + PVR_SETTING_TYPE eType, + uint64_t readonlyConditions, + const PVRIntSettingDefinition& intSettingDef, + const PVRStringSettingDefinition& stringSettingDef) + { + SetId(settingDefId); + SetName(settingDefName); + SetType(eType); + SetReadonlyConditions(readonlyConditions); + SetIntDefinition(intSettingDef); + SetStringDefinition(stringSettingDef); + } + + /// @brief **required**\n + /// This setting definition's identifier. + void SetId(unsigned int defId) { m_cStructure->iId = defId; } + + /// @brief To get with @ref SetId changed values. + unsigned int GetId() const { return m_cStructure->iId; } + + /// @brief **required**\n + /// A short localized string with the name of the setting. + void SetName(const std::string& name) + { + ReallocAndCopyString(&m_cStructure->strName, name.c_str()); + } + + /// @brief To get with @ref SetName changed values. + std::string GetName() const { return m_cStructure->strName ? m_cStructure->strName : ""; } + + /// @brief **required**\n + /// This setting definition's identifier. + void SetType(PVR_SETTING_TYPE eType) { m_cStructure->eType = eType; } + + /// @brief To get with @ref SetType changed values. + PVR_SETTING_TYPE GetType() const { return m_cStructure->eType; } + + /// @brief **optional**\n + /// The read-only conditions value for this setting. + /// @ref cpp_kodi_addon_pvr_Defs_General_PVR_SETTING_READONLY_CONDITION "PVR_SETTING_READONLY_CONDITION_*" enum values + void SetReadonlyConditions(uint64_t conditions) + { + m_cStructure->iReadonlyConditions = conditions; + } + + /// @brief To get with @ref SetReadonlyConditions changed values. + uint64_t GetReadonlyConditions() const { return m_cStructure->iReadonlyConditions; } + + //---------------------------------------------------------------------------- + + /// @brief **optional**\n + /// @param[in] def integer setting definition + /// + /// -------------------------------------------------------------------------- + /// + /// @copydetails cpp_kodi_addon_pvr_Defs_PVRIntSettingDefinition_Help + void SetIntDefinition(const PVRIntSettingDefinition& def) + { + PVRIntSettingDefinition::ReallocAndCopyData(&m_cStructure->intSettingDefinition, def); + } + + /// @brief To get with @ref SetIntDefinition changed values. + PVRIntSettingDefinition GetIntDefinition() const + { + PVRIntSettingDefinition ret; + if (m_cStructure->intSettingDefinition) + { + std::vector<PVRTypeIntValue> settingValues; + settingValues.reserve(m_cStructure->intSettingDefinition->iValuesSize); + for (unsigned int i = 0; i < m_cStructure->intSettingDefinition->iValuesSize; ++i) + { + settingValues.emplace_back(m_cStructure->intSettingDefinition->values[i].iValue, + m_cStructure->intSettingDefinition->values[i].strDescription); + } + ret.SetValues(std::move(settingValues)); + ret.SetDefaultValue(m_cStructure->intSettingDefinition->iDefaultValue); + ret.SetMinValue(m_cStructure->intSettingDefinition->iMinValue); + ret.SetStep(m_cStructure->intSettingDefinition->iStep); + ret.SetMaxValue(m_cStructure->intSettingDefinition->iMaxValue); + } + return ret; + } + + //---------------------------------------------------------------------------- + + /// @brief **optional**\n + /// @param[in] def string setting definition + /// + /// -------------------------------------------------------------------------- + /// + /// @copydetails cpp_kodi_addon_pvr_Defs_PVRStringSettingDefinition_Help + void SetStringDefinition(const PVRStringSettingDefinition& def) + { + PVRStringSettingDefinition::ReallocAndCopyData(&m_cStructure->stringSettingDefinition, def); + } + + /// @brief To get with @ref SetStringDefinition changed values. + PVRStringSettingDefinition GetStringDefinition() const + { + PVRStringSettingDefinition ret; + if (m_cStructure->stringSettingDefinition) + { + std::vector<PVRTypeStringValue> settingValues; + settingValues.reserve(m_cStructure->stringSettingDefinition->iValuesSize); + for (unsigned int i = 0; i < m_cStructure->stringSettingDefinition->iValuesSize; ++i) + { + settingValues.emplace_back(m_cStructure->stringSettingDefinition->values[i].strValue, + m_cStructure->stringSettingDefinition->values[i].strDescription); + } + ret.SetValues(std::move(settingValues)); + ret.SetDefaultValue(m_cStructure->stringSettingDefinition->strDefaultValue); + } + return ret; + } + ///@} + + static PVR_SETTING_DEFINITION** AllocAndCopyData(const std::vector<PVRSettingDefinition>& source) + { + PVR_SETTING_DEFINITION** defs = new PVR_SETTING_DEFINITION* [source.size()] {}; + for (unsigned int i = 0; i < source.size(); ++i) + { + defs[i] = new PVR_SETTING_DEFINITION{}; + defs[i]->iId = source[i].GetCStructure()->iId; + defs[i]->eType = source[i].GetCStructure()->eType; + defs[i]->iReadonlyConditions = source[i].GetCStructure()->iReadonlyConditions; + AllocResources(source[i].GetCStructure(), + defs[i]); // handles strName, intSettingDefinition, stringSettingDefinition + } + return defs; + } + + static PVR_SETTING_DEFINITION** AllocAndCopyData(PVR_SETTING_DEFINITION** source, + unsigned int size) + { + PVR_SETTING_DEFINITION** defs = new PVR_SETTING_DEFINITION* [size] {}; + for (unsigned int i = 0; i < size; ++i) + { + defs[i] = new PVR_SETTING_DEFINITION{}; + defs[i]->iId = source[i]->iId; + defs[i]->eType = source[i]->eType; + defs[i]->iReadonlyConditions = source[i]->iReadonlyConditions; + AllocResources(source[i], + defs[i]); // handles strName, intSettingDefinition, stringSettingDefinition + } + return defs; + } + + static void AllocResources(const PVR_SETTING_DEFINITION* source, PVR_SETTING_DEFINITION* target) + { + target->strName = AllocAndCopyString(source->strName); + if (source->intSettingDefinition) + target->intSettingDefinition = + PVRIntSettingDefinition::AllocAndCopyData(source->intSettingDefinition); + if (source->stringSettingDefinition) + target->stringSettingDefinition = + PVRStringSettingDefinition::AllocAndCopyData(source->stringSettingDefinition); + } + + static void FreeResources(PVR_SETTING_DEFINITION* target) + { + FreeString(target->strName); + target->strName = nullptr; + + if (target->intSettingDefinition) + { + PVRIntSettingDefinition::FreeResources(target->intSettingDefinition); + target->intSettingDefinition = nullptr; + } + + if (target->stringSettingDefinition) + { + PVRStringSettingDefinition::FreeResources(target->stringSettingDefinition); + target->stringSettingDefinition = nullptr; + } + } + + static void FreeResources(PVR_SETTING_DEFINITION** defs, unsigned int size) + { + for (unsigned int i = 0; i < size; ++i) + { + FreeResources(defs[i]); + delete defs[i]; + } + delete[] defs; + } + + static void ReallocAndCopyData(PVR_SETTING_DEFINITION*** source, + unsigned int* size, + const std::vector<PVRSettingDefinition>& defs) + { + FreeResources(*source, *size); + *source = nullptr; + *size = defs.size(); + if (*size) + *source = AllocAndCopyData(defs); + } + +private: + PVRSettingDefinition(const PVR_SETTING_DEFINITION* def) : DynamicCStructHdl(def) {} + PVRSettingDefinition(PVR_SETTING_DEFINITION* def) : DynamicCStructHdl(def) {} +}; +///@} +//------------------------------------------------------------------------------ + +//============================================================================== +/// @defgroup cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair class PVRSettingKeyValuePair +/// @ingroup cpp_kodi_addon_pvr_Defs_General +/// @brief **Key-value pair of two ints**\n +/// To hold a pair of two ints. +/// +/// ---------------------------------------------------------------------------- +/// +/// @copydetails cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair_Help +/// +///@{ +class PVRSettingKeyValuePair + : public DynamicCStructHdl<PVRSettingKeyValuePair, PVR_SETTING_KEY_VALUE_PAIR> +{ + friend class CInstancePVRClient; + +public: + /*! \cond PRIVATE */ + PVRSettingKeyValuePair(const PVRSettingKeyValuePair& pair) : DynamicCStructHdl(pair) {} + /*! \endcond */ + + /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair_Help Value Help + /// @ingroup cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair + /// + /// <b>The following table contains values that can be set with @ref cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair :</b> + /// | Name | Type | Set call | Get call + /// |------|------|----------|---------- + /// | **Key** | `unsigned int` | @ref PVRSettingKeyValuePair::SetKey "SetKey" | @ref PVRSettingKeyValuePair::GetKey "GetKey" + /// | **Type** | @ref PVR_SETTING_TYPE | @ref PVRSettingKeyValuePair::SetType "SetType" | @ref PVRSettingKeyValuePair:GetType "GetType" | *required to set* + /// | **Int Value** | `int` | @ref PVRSettingKeyValuePair::SetIntValue "SetIntValue" | @ref PVRSettingKeyValuePair::GetIntValue "GetIntValue" + /// | **String Value** | `std::string` | @ref PVRSettingKeyValuePair::SetStringValue "SetStringValue" | @ref PVRSettingKeyValuePair::GetStringValue "GetStringValue" + + /// @addtogroup cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair + ///@{ + + /// @brief Default class constructor. + PVRSettingKeyValuePair() = default; + + /// @brief Class constructor with integrated value set. + /// + /// @param[in] key The key + /// @param[in] value The value + PVRSettingKeyValuePair(unsigned int key, int value) + { + SetKey(key); + SetType(PVR_SETTING_TYPE::INTEGER); + SetIntValue(value); + } + + /// @brief Class constructor with integrated value set. + /// + /// @param[in] key The key + /// @param[in] value The value + PVRSettingKeyValuePair(unsigned int key, const std::string& value) + { + SetKey(key); + SetType(PVR_SETTING_TYPE::STRING); + SetStringValue(value); + } + + /// @brief Class constructor with integrated value set. + /// + /// @param[in] key The key + /// @param[in] eType The type + /// @param[in] intValue The int value + /// @param[in] stringValue The string value + PVRSettingKeyValuePair(unsigned int key, + PVR_SETTING_TYPE eType, + int intValue, + const std::string& stringValue) + { + SetKey(key); + SetType(eType); + SetIntValue(intValue); + SetStringValue(stringValue); + } + + /// @brief To set with the key. + void SetKey(unsigned int key) { m_cStructure->iKey = key; } + + /// @brief To get with the key. + unsigned GetKey() const { return m_cStructure->iKey; } + + /// @brief **required**\n + /// This key value pair's type. + void SetType(PVR_SETTING_TYPE eType) { m_cStructure->eType = eType; } + + /// @brief To get with @ref SetType changed values. + PVR_SETTING_TYPE GetType() const { return m_cStructure->eType; } + + /// @brief To set with the value. + void SetIntValue(int value) { m_cStructure->iValue = value; } + + /// @brief To get with the value. + int GetIntValue() const { return m_cStructure->iValue; } + + /// @brief To set with the value. + void SetStringValue(const std::string& value) + { + ReallocAndCopyString(&m_cStructure->strValue, value.c_str()); + } + + /// @brief To get with the value. + std::string GetStringValue() const + { + return m_cStructure->strValue ? m_cStructure->strValue : ""; + } + ///@} + + static PVR_SETTING_KEY_VALUE_PAIR* AllocAndCopyData( + const std::vector<PVRSettingKeyValuePair>& values) + { + PVR_SETTING_KEY_VALUE_PAIR* pairs = new PVR_SETTING_KEY_VALUE_PAIR[values.size()]{}; + for (unsigned int i = 0; i < values.size(); ++i) + { + pairs[i].iKey = values[i].GetCStructure()->iKey; + pairs[i].eType = values[i].GetCStructure()->eType; + pairs[i].iValue = values[i].GetCStructure()->iValue; + AllocResources(values[i].GetCStructure(), &pairs[i]); // handles strValue + } + return pairs; + } + + static PVR_SETTING_KEY_VALUE_PAIR* AllocAndCopyData(const PVR_SETTING_KEY_VALUE_PAIR* source, + unsigned int size) + { + PVR_SETTING_KEY_VALUE_PAIR* pairs = new PVR_SETTING_KEY_VALUE_PAIR[size]{}; + for (unsigned int i = 0; i < size; ++i) + { + pairs[i].iKey = source[i].iKey; + pairs[i].eType = source[i].eType; + pairs[i].iValue = source[i].iValue; + AllocResources(&source[i], &pairs[i]); // handles strValue + } + return pairs; + } + + static void AllocResources(const PVR_SETTING_KEY_VALUE_PAIR* source, + PVR_SETTING_KEY_VALUE_PAIR* target) + { + target->strValue = AllocAndCopyString(source->strValue); + } + + static void FreeResources(PVR_SETTING_KEY_VALUE_PAIR* target) { FreeString(target->strValue); } + + static void FreeResources(PVR_SETTING_KEY_VALUE_PAIR* pairs, unsigned int size) + { + for (unsigned int i = 0; i < size; ++i) + FreeResources(&pairs[i]); + delete[] pairs; + } + + static void ReallocAndCopyData(PVR_SETTING_KEY_VALUE_PAIR** source, + unsigned int* size, + const std::vector<PVRSettingKeyValuePair>& values) + { + FreeResources(*source, *size); + *source = nullptr; + *size = values.size(); + if (*size) + *source = AllocAndCopyData(values); + } + +private: + PVRSettingKeyValuePair(const PVR_SETTING_KEY_VALUE_PAIR* pair) : DynamicCStructHdl(pair) {} + PVRSettingKeyValuePair(PVR_SETTING_KEY_VALUE_PAIR* pair) : DynamicCStructHdl(pair) {} +}; +///@} +//------------------------------------------------------------------------------ + //============================================================================== /// @defgroup cpp_kodi_addon_pvr_Defs_PVRCapabilities class PVRCapabilities /// @ingroup cpp_kodi_addon_pvr_Defs_General diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h index 4435baf930456..9df97ee685a79 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h @@ -88,6 +88,7 @@ class PVRTimer : public DynamicCStructHdl<PVRTimer, PVR_TIMER> /// | **Genre type** | `int` | @ref PVRTimer::SetGenreType "SetGenreType" | @ref PVRTimer::GetGenreType "GetGenreType" | *optional* /// | **Genre sub type** | `int` | @ref PVRTimer::SetGenreSubType "SetGenreSubType" | @ref PVRTimer::GetGenreSubType "GetGenreSubType" | *optional* /// | **Series link** | `std::string` | @ref PVRTimer::SetSeriesLink "SetSeriesLink" | @ref PVRTimer::GetSeriesLink "GetSeriesLink" | *optional* + /// | **Custom properties** | @ref cpp_kodi_addon_pvr_Defs_PVRSettingKeyValuePair "PVRSettingKeyValuePair" | @ref PVRTimer::SetCustomProperties "SetCustomProperties" | @ref PVRTimer::GetCustomProperties "GetCustomProperties" | *optional* /// @addtogroup cpp_kodi_addon_pvr_Defs_Timer_PVRTimer ///@{ @@ -461,6 +462,39 @@ class PVRTimer : public DynamicCStructHdl<PVRTimer, PVR_TIMER> { return m_cStructure->strSeriesLink ? m_cStructure->strSeriesLink : ""; } + + //---------------------------------------------------------------------------- + + /// @brief **optional**\n + /// Array containing the custom properties. + /// + /// @param[in] properties List of properties. + /// + /// -------------------------------------------------------------------------- + /// + /// @copydetails cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair_Help + void SetCustomProperties(const std::vector<PVRSettingKeyValuePair>& properties) + { + PVRSettingKeyValuePair::ReallocAndCopyData(&m_cStructure->customProps, + &m_cStructure->iCustomPropsSize, properties); + } + + /// @brief To get with @ref SetCustomProperties changed values + std::vector<PVRSettingKeyValuePair> GetCustomProperties() const + { + std::vector<PVRSettingKeyValuePair> ret; + if (m_cStructure->iCustomPropsSize) + { + ret.reserve(m_cStructure->iCustomPropsSize); + for (unsigned int i = 0; i < m_cStructure->iCustomPropsSize; ++i) + { + ret.emplace_back(m_cStructure->customProps[i].iKey, m_cStructure->customProps[i].eType, + m_cStructure->customProps[i].iValue, + m_cStructure->customProps[i].strValue); + } + } + return ret; + } ///@} static void AllocResources(const PVR_TIMER* source, PVR_TIMER* target) @@ -470,6 +504,10 @@ class PVRTimer : public DynamicCStructHdl<PVRTimer, PVR_TIMER> target->strDirectory = AllocAndCopyString(source->strDirectory); target->strSummary = AllocAndCopyString(source->strSummary); target->strSeriesLink = AllocAndCopyString(source->strSeriesLink); + + target->customProps = + PVRSettingKeyValuePair::AllocAndCopyData(source->customProps, source->iCustomPropsSize); + target->iCustomPropsSize = source->iCustomPropsSize; } static void FreeResources(PVR_TIMER* target) @@ -479,6 +517,10 @@ class PVRTimer : public DynamicCStructHdl<PVRTimer, PVR_TIMER> FreeString(target->strDirectory); FreeString(target->strSummary); FreeString(target->strSeriesLink); + + PVRSettingKeyValuePair::FreeResources(target->customProps, target->iCustomPropsSize); + target->customProps = nullptr; + target->iCustomPropsSize = 0; } private: @@ -583,6 +625,8 @@ class PVRTimerType : public DynamicCStructHdl<PVRTimerType, PVR_TIMER_TYPE> /// | | | | | | /// | **Max recordings selection** | @ref cpp_kodi_addon_pvr_Defs_PVRTypeIntValue "PVRTypeIntValue" | @ref PVRTimerType::SetMaxRecordings "SetMaxRecordings" | @ref PVRTimerType::GetMaxRecordings "GetMaxRecordings" | *optional* /// | **Max recordings default** | `int`| @ref PVRTimerType::SetMaxRecordingsDefault "SetMaxRecordingsDefault" | @ref PVRTimerType::GetMaxRecordingsDefault "GetMaxRecordingsDefault" | *optional* + /// | | | | | | + /// | **Custom setting definitions**| @ref cpp_kodi_addon_pvr_Defs_PVRSettingDefinition "PVRSettingDefinition" | @ref PVRTimerType::SetCustomSettingDefinitions "SetCustomSettingDefinitions" | @ref PVRTimerType::GetCustomSettingDefinitions "GetCustomSettingDefinitions" | *optional* /// /// @addtogroup cpp_kodi_addon_pvr_Defs_Timer_PVRTimerType @@ -822,7 +866,7 @@ class PVRTimerType : public DynamicCStructHdl<PVRTimerType, PVR_TIMER_TYPE> /// @brief **optional**\n /// Array containing the possible values of @ref PVRTimer::SetMaxRecordings(). /// - /// @param[in] maxRecordings List of lifetimes values + /// @param[in] maxRecordings List of max recordings values /// @param[in] maxRecordingsDefault [opt] The default value in list, can also be /// set by @ref SetMaxRecordingsDefault() /// @@ -859,6 +903,78 @@ class PVRTimerType : public DynamicCStructHdl<PVRTimerType, PVR_TIMER_TYPE> /// @brief To get with @ref SetMaxRecordingsDefault changed values int GetMaxRecordingsDefault() const { return m_cStructure->iMaxRecordingsDefault; } + + //---------------------------------------------------------------------------- + + /// @brief **optional**\n + /// Array containing the possible custom integer setting definitions. + /// + /// @param[in] defs List of integer setting definitions. + /// + /// -------------------------------------------------------------------------- + /// + /// @copydetails cpp_kodi_addon_pvr_Defs_General_PVRIntSettingDefinition_Help + void SetCustomSettingDefinitions(const std::vector<PVRSettingDefinition>& defs) + { + PVRSettingDefinition::ReallocAndCopyData(&m_cStructure->customSettingDefs, + &m_cStructure->iCustomSettingDefsSize, defs); + } + + /// @brief To get with @ref SetCustomSettingDefinitions changed values + std::vector<PVRSettingDefinition> GetCustomSettingDefinitions() const + { + std::vector<PVRSettingDefinition> ret; + if (m_cStructure->iCustomSettingDefsSize) + { + ret.reserve(m_cStructure->iCustomSettingDefsSize); + for (unsigned int i = 0; i < m_cStructure->iCustomSettingDefsSize; ++i) + { + const PVR_SETTING_DEFINITION* def{m_cStructure->customSettingDefs[i]}; + + PVRIntSettingDefinition intDef; + if (def->intSettingDefinition) + { + const PVR_INT_SETTING_DEFINITION* intSettingDef{def->intSettingDefinition}; + + std::vector<PVRTypeIntValue> intValues; + if (intSettingDef->iValuesSize) + { + intValues.reserve(intSettingDef->iValuesSize); + for (unsigned int j = 0; j < intSettingDef->iValuesSize; ++j) + { + intValues.emplace_back(intSettingDef->values[j].iValue, + intSettingDef->values[j].strDescription); + } + } + intDef = {intValues, intSettingDef->iDefaultValue, intSettingDef->iMinValue, + intSettingDef->iStep, intSettingDef->iMaxValue}; + } + + PVRStringSettingDefinition stringDef; + if (def->stringSettingDefinition) + { + const PVR_STRING_SETTING_DEFINITION* stringSettingDef{def->stringSettingDefinition}; + + std::vector<PVRTypeStringValue> stringValues; + if (stringSettingDef->iValuesSize) + { + stringValues.reserve(stringSettingDef->iValuesSize); + for (unsigned int j = 0; j < stringSettingDef->iValuesSize; ++j) + { + stringValues.emplace_back(stringSettingDef->values[j].strValue, + stringSettingDef->values[j].strDescription); + } + } + stringDef = {stringValues, stringSettingDef->strDefaultValue, + stringSettingDef->bAllowEmptyValue}; + } + + ret.emplace_back(def->iId, def->strName, def->eType, def->iReadonlyConditions, intDef, + stringDef); + } + } + return ret; + } ///@} static void AllocResources(const PVR_TIMER_TYPE* source, PVR_TIMER_TYPE* target) @@ -894,6 +1010,12 @@ class PVRTimerType : public DynamicCStructHdl<PVRTimerType, PVR_TIMER_TYPE> target->maxRecordings = PVRTypeIntValue::AllocAndCopyData(source->maxRecordings, source->iMaxRecordingsSize); } + + if (target->iCustomSettingDefsSize) + { + target->customSettingDefs = PVRSettingDefinition::AllocAndCopyData( + source->customSettingDefs, source->iCustomSettingDefsSize); + } } static void FreeResources(PVR_TIMER_TYPE* target) @@ -921,6 +1043,10 @@ class PVRTimerType : public DynamicCStructHdl<PVRTimerType, PVR_TIMER_TYPE> PVRTypeIntValue::FreeResources(target->maxRecordings, target->iMaxRecordingsSize); target->maxRecordings = nullptr; target->iMaxRecordingsSize = 0; + + PVRSettingDefinition::FreeResources(target->customSettingDefs, target->iCustomSettingDefsSize); + target->customSettingDefs = nullptr; + target->iCustomSettingDefsSize = 0; } private: diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h index 3c64e9dd7ff5c..3ece119195524 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h @@ -20,7 +20,7 @@ extern "C" #endif /* __cplusplus */ /*! - * @brief "C" Representation of a general attribute integer value. + * @brief "C" Representation of an integer value, including a description. */ typedef struct PVR_ATTRIBUTE_INT_VALUE { @@ -28,6 +28,15 @@ extern "C" const char* strDescription; } PVR_ATTRIBUTE_INT_VALUE; + /*! + * @brief "C" Representation of a string value, including a description + */ + typedef struct PVR_ATTRIBUTE_STRING_VALUE + { + const char* strValue; + const char* strDescription; + } PVR_ATTRIBUTE_STRING_VALUE; + /*! * @brief "C" Representation of a named value. */ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h index 420a95c1b8b31..4541f7ffe8704 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h @@ -13,6 +13,7 @@ #include "pvr_defines.h" #include <stdbool.h> +#include <stdint.h> //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ // "C" Definitions group 1 - General PVR @@ -133,6 +134,56 @@ extern "C" ///@} //---------------------------------------------------------------------------- + //============================================================================ + /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVR_SETTING_READONLY_CONDITION enum PVR_SETTING_READONLY_CONDITION + /// @ingroup cpp_kodi_addon_pvr_Defs_General + /// @brief **Read-only conditions for settings**\n + /// To define read-only conditions for settings. + /// + ///@{ + typedef enum PVR_SETTING_READONLY_CONDITION + { + /// @brief __0000 0000 0000 0000 0000 0000 0000 0000__ :\n Empty value. + PVR_SETTING_READONLY_CONDITION_NONE = 0, + + /// @brief __0000 0000 0000 0000 0000 0000 0000 0001__ :\n Readonly, if associated timer is + /// disabled (PVR_TIMER_STATE_DISABLED. Applicable to timer settings only). + PVR_SETTING_READONLY_CONDITION_TIMER_DISABLED = (1 << 0), + + /// @brief __0000 0000 0000 0000 0000 0000 0000 0010__ :\n Readonly, if associated timer is + /// scheduled (PVR_TIMER_STATE_SCHEDULED. Applicable to timer settings only). + PVR_SETTING_READONLY_CONDITION_TIMER_SCHEDULED = (1 << 1), + + /// @brief __0000 0000 0000 0000 0000 0000 0000 0100__ :\n Readonly, if associated timer is + /// currently recording (PVR_TIMER_STATE_RECORDING. Applicable to timer settings only). + PVR_SETTING_READONLY_CONDITION_TIMER_RECORDING = (1 << 2), + + /// @brief __0000 0000 0000 0000 0000 0000 0000 1000__ :\n Readonly, if associated timer is + /// currently recording (PVR_TIMER_STATE_COMPLETED. Applicable to timer settings only). + PVR_SETTING_READONLY_CONDITION_TIMER_COMPLETED = (1 << 3), + + } PVR_SETTING_READONLY_CONDITION; + ///@} + //---------------------------------------------------------------------------- + + //============================================================================ + /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVR_SETTING_TYPE enum PVR_SETTING_TYPE + /// @ingroup cpp_kodi_addon_pvr_Defs_General + /// @brief **PVR setting type**\n + /// + ///@{ + typedef enum PVR_SETTING_TYPE + { + /// @brief __0__ : Integer + INTEGER = 0, + + /// @brief __1__ : String + STRING = 1, + + } PVR_SETTING_TYPE; + ///@} + //---------------------------------------------------------------------------- + //============================================================================ /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVR_STREAM_PROPERTY definition PVR_STREAM_PROPERTY /// @ingroup cpp_kodi_addon_pvr_Defs_General_Inputstream @@ -324,6 +375,75 @@ extern "C" struct PVR_ATTRIBUTE_INT_VALUE* recordingsLifetimeValues; } PVR_ADDON_CAPABILITIES; + /*! + * @brief "C" Representation of an integer setting definition. + * + * Structure used to interface in "C" between Kodi and Addon. + * + * See @ref cpp_kodi_addon_pvr_Defs_PVRIntSettingDefinition "kodi::addon::PVRIntSettingDefinition" + * for description of values. + */ + typedef struct PVR_INT_SETTING_DEFINITION + { + unsigned int iValuesSize; + struct PVR_ATTRIBUTE_INT_VALUE* values; + int iDefaultValue; + int iMinValue; + int iStep; + int iMaxValue; + } PVR_INT_SETTING_DEFINITION; + + /*! + * @brief "C" Representation of a string setting definition. + * + * Structure used to interface in "C" between Kodi and Addon. + * + * See @ref cpp_kodi_addon_pvr_Defs_PVRStringSettingDefinition "kodi::addon::PVRStringSettingDefinition" + * for description of values. + */ + typedef struct PVR_STRING_SETTING_DEFINITION + { + unsigned int iValuesSize; + struct PVR_ATTRIBUTE_STRING_VALUE* values; + const char* strDefaultValue; + bool bAllowEmptyValue; + } PVR_STRING_SETTING_DEFINITION; + + /*! + * @brief "C" Representation of a setting definition. + * + * Structure used to interface in "C" between Kodi and Addon. + * + * See @ref cpp_kodi_addon_pvr_Defs_PVRSettingDefinition "kodi::addon::PVRSettingDefinition" + * for description of values. + */ + typedef struct PVR_SETTING_DEFINITION + { + unsigned int iId; + const char* strName; + enum PVR_SETTING_TYPE eType; + uint64_t iReadonlyConditions; + struct PVR_INT_SETTING_DEFINITION* intSettingDefinition; + struct PVR_STRING_SETTING_DEFINITION* stringSettingDefinition; + } PVR_SETTING_DEFINITION; + + /*! + * @brief "C" Representation of a key-value pair, either {int,int} or {int,string}, depending on + * the type set. + * + * Structure used to interface in "C" between Kodi and Addon. + * + * See @ref cpp_kodi_addon_pvr_Defs_PVRSettingKeyValuePair "kodi::addon::PVRSettingKeyValuePair" for description + * of values. + */ + typedef struct PVR_SETTING_KEY_VALUE_PAIR + { + unsigned int iKey; + enum PVR_SETTING_TYPE eType; + int iValue; + const char* strValue; + } PVR_SETTING_KEY_VALUE_PAIR; + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h index 8af03939ff4d4..668a18529ca9c 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h @@ -298,7 +298,7 @@ extern "C" /// @brief __1__ : The timer is scheduled for recording. PVR_TIMER_STATE_SCHEDULED = 1, - /// @brief __2__ : The timer is currently recordings. + /// @brief __2__ : The timer is currently recording. PVR_TIMER_STATE_RECORDING = 2, /// @brief __3__ : The recording completed successfully. @@ -310,19 +310,17 @@ extern "C" /// @brief __5__ : The timer was scheduled, but was canceled. PVR_TIMER_STATE_CANCELLED = 5, - /// @brief __6__ : The scheduled timer conflicts with another one, but will be - /// recorded. + /// @brief __6__ : The scheduled timer conflicts with another one, but will be recorded. PVR_TIMER_STATE_CONFLICT_OK = 6, - /// @brief __7__ : The scheduled timer conflicts with another one and won't be - /// recorded. + /// @brief __7__ : The scheduled timer conflicts with another one and won't be recorded. PVR_TIMER_STATE_CONFLICT_NOK = 7, /// @brief __8__ : The timer is scheduled, but can't be recorded for some reason. PVR_TIMER_STATE_ERROR = 8, - /// @brief __9__ : The timer was disabled by the user, can be enabled via setting - /// the state to @ref PVR_TIMER_STATE_SCHEDULED. + /// @brief __9__ : The timer was disabled by the user, can be enabled via setting the state to + /// @ref PVR_TIMER_STATE_SCHEDULED. PVR_TIMER_STATE_DISABLED = 9, } PVR_TIMER_STATE; ///@} @@ -365,10 +363,12 @@ extern "C" int iGenreType; int iGenreSubType; const char* strSeriesLink; + unsigned int iCustomPropsSize; + struct PVR_SETTING_KEY_VALUE_PAIR* customProps; } PVR_TIMER; /*! - * @brief "C" PVR add-on timer event type. + * @brief "C" PVR add-on timer type. * * Structure used to interface in "C" between Kodi and Addon. * @@ -400,6 +400,9 @@ extern "C" unsigned int iMaxRecordingsSize; struct PVR_ATTRIBUTE_INT_VALUE* maxRecordings; int iMaxRecordingsDefault; + + unsigned int iCustomSettingDefsSize; + struct PVR_SETTING_DEFINITION** customSettingDefs; } PVR_TIMER_TYPE; #ifdef __cplusplus From 3612e0b0c753aaca5b27896669d42ad7267c8a6a Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:24:41 +0200 Subject: [PATCH 447/651] [addons] PVR Add-on API version bump to 9.1.0 --- xbmc/addons/kodi-dev-kit/include/kodi/versions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h index ad597e914f8ac..c4c45d6f95e99 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h @@ -130,7 +130,7 @@ #define ADDON_INSTANCE_VERSION_PERIPHERAL_DEPENDS "addon-instance/Peripheral.h" \ "addon-instance/PeripheralUtils.h" -#define ADDON_INSTANCE_VERSION_PVR "9.0.1" +#define ADDON_INSTANCE_VERSION_PVR "9.1.0" #define ADDON_INSTANCE_VERSION_PVR_MIN "9.0.0" #define ADDON_INSTANCE_VERSION_PVR_XML_ID "kodi.binary.instance.pvr" #define ADDON_INSTANCE_VERSION_PVR_DEPENDS "c-api/addon-instance/pvr.h" \ From 2bfab15895042df8dcfb5316d9b2c4dc42021558 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 26 Aug 2024 15:05:21 +0200 Subject: [PATCH 448/651] [PVR] Implement support for client add-on supplied custom int and string properties, add possibility to manage this properties in the timer settings dialog. --- xbmc/pvr/addons/PVRClient.cpp | 28 +- .../pvr/dialogs/GUIDialogPVRTimerSettings.cpp | 121 +++++++++ xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h | 34 ++- xbmc/pvr/settings/CMakeLists.txt | 19 +- xbmc/pvr/settings/IPVRSettingsContainer.h | 47 ++++ xbmc/pvr/settings/PVRCustomTimerSettings.cpp | 256 ++++++++++++++++++ xbmc/pvr/settings/PVRCustomTimerSettings.h | 77 ++++++ xbmc/pvr/settings/PVRIntSettingDefinition.cpp | 33 +++ xbmc/pvr/settings/PVRIntSettingDefinition.h | 42 +++ xbmc/pvr/settings/PVRIntSettingValues.cpp | 8 + xbmc/pvr/settings/PVRIntSettingValues.h | 8 +- .../settings/PVRStringSettingDefinition.cpp | 30 ++ .../pvr/settings/PVRStringSettingDefinition.h | 39 +++ xbmc/pvr/settings/PVRStringSettingValues.cpp | 51 ++++ xbmc/pvr/settings/PVRStringSettingValues.h | 41 +++ .../settings/PVRTimerSettingDefinition.cpp | 103 +++++++ xbmc/pvr/settings/PVRTimerSettingDefinition.h | 68 +++++ xbmc/pvr/timers/PVRTimerInfoTag.cpp | 24 +- xbmc/pvr/timers/PVRTimerInfoTag.h | 37 ++- xbmc/pvr/timers/PVRTimerType.cpp | 29 +- xbmc/pvr/timers/PVRTimerType.h | 22 +- 21 files changed, 1095 insertions(+), 22 deletions(-) create mode 100644 xbmc/pvr/settings/IPVRSettingsContainer.h create mode 100644 xbmc/pvr/settings/PVRCustomTimerSettings.cpp create mode 100644 xbmc/pvr/settings/PVRCustomTimerSettings.h create mode 100644 xbmc/pvr/settings/PVRIntSettingDefinition.cpp create mode 100644 xbmc/pvr/settings/PVRIntSettingDefinition.h create mode 100644 xbmc/pvr/settings/PVRStringSettingDefinition.cpp create mode 100644 xbmc/pvr/settings/PVRStringSettingDefinition.h create mode 100644 xbmc/pvr/settings/PVRStringSettingValues.cpp create mode 100644 xbmc/pvr/settings/PVRStringSettingValues.h create mode 100644 xbmc/pvr/settings/PVRTimerSettingDefinition.cpp create mode 100644 xbmc/pvr/settings/PVRTimerSettingDefinition.h diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 1a4fd862fa2f4..162ad13e9a2e9 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -10,6 +10,7 @@ #include "ServiceBroker.h" #include "addons/AddonManager.h" +#include "addons/AddonVersion.h" #include "addons/binary-addons/AddonDll.h" #include "cores/EdlEdit.h" #include "cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.h" @@ -254,6 +255,24 @@ class CAddonTimer : public PVR_TIMER iGenreType = epgTag ? epgTag->GenreType() : 0; iGenreSubType = epgTag ? epgTag->GenreSubType() : 0; strSeriesLink = m_seriesLink.c_str(); + + const auto& props{timer.GetCustomProperties()}; + iCustomPropsSize = static_cast<unsigned int>(props.size()); + if (iCustomPropsSize) + { + m_customProps = std::make_unique<PVR_SETTING_KEY_VALUE_PAIR[]>(iCustomPropsSize); + int idx{0}; + for (const auto& entry : props) + { + PVR_SETTING_KEY_VALUE_PAIR& prop{m_customProps[idx]}; + prop.iKey = entry.first; + prop.eType = entry.second.type; + prop.iValue = entry.second.value.asInteger32(); + prop.strValue = entry.second.value.asString().c_str(); + ++idx; + } + customProps = m_customProps.get(); + } } virtual ~CAddonTimer() = default; @@ -263,6 +282,7 @@ class CAddonTimer : public PVR_TIMER const std::string m_directory; const std::string m_summary; const std::string m_seriesLink; + std::unique_ptr<PVR_SETTING_KEY_VALUE_PAIR[]> m_customProps; }; class CAddonEpgTag : public EPG_TAG @@ -1398,8 +1418,8 @@ PVR_ERROR CPVRClient::UpdateTimerTypes() CLog::LogF(LOGERROR, "Invalid timer type supplied by add-on {}.", GetID()); continue; } - timerTypes.emplace_back( - std::make_shared<CPVRTimerType>(*(types_array[i]), m_iClientId)); + timerTypes.emplace_back(std::make_shared<CPVRTimerType>( + *(types_array[i]), m_iClientId, Addon()->GetTypeVersionDll(ADDON_INSTANCE_PVR))); } } @@ -2259,7 +2279,9 @@ void CPVRClient::cb_transfer_timer_entry(void* kodiInstance, // transfer this entry to the timers container const std::shared_ptr<CPVRTimerInfoTag> transferTimer = - std::make_shared<CPVRTimerInfoTag>(*timer, channel, client->GetID()); + std::make_shared<CPVRTimerInfoTag>( + *timer, channel, client->GetID(), + client->Addon()->GetTypeVersionDll(ADDON_INSTANCE_PVR)); CPVRTimersContainer* timers = static_cast<CPVRTimersContainer*>(handle->dataAddress); timers->UpdateFromClient(transferTimer); diff --git a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp index 07f9d221eac90..cd6a7e9c0a3ef 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp @@ -22,6 +22,7 @@ #include "pvr/channels/PVRChannelGroupMember.h" #include "pvr/channels/PVRChannelGroupsContainer.h" #include "pvr/epg/EpgInfoTag.h" +#include "pvr/settings/PVRCustomTimerSettings.h" #include "pvr/timers/PVRTimerInfoTag.h" #include "pvr/timers/PVRTimerType.h" #include "settings/SettingUtils.h" @@ -148,6 +149,9 @@ void CGUIDialogPVRTimerSettings::SetTimer(const std::shared_ptr<CPVRTimerInfoTag InitializeChannelsList(); InitializeTypesList(); + m_customTimerSettings = std::make_unique<CPVRCustomTimerSettings>( + *m_timerType, m_timerInfoTag->m_customProps, m_typeEntries); + // Channel m_channel = ChannelDescriptor(); @@ -400,6 +404,55 @@ void CGUIDialogPVRTimerSettings::InitializeSettings() RecordingGroupFiller, 811); AddTypeDependentVisibilityCondition(setting, SETTING_TMR_REC_GROUP); AddTypeDependentEnableCondition(setting, SETTING_TMR_REC_GROUP); + + // Add-on supplied custom settings + m_customTimerSettings->AddSettings(*this, group); +} + +void CGUIDialogPVRTimerSettings::AddMultiIntSetting(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingName, + int settingValue) +{ + const std::shared_ptr<CSetting> setting{AddList(group, settingName, 16028, SettingLevel::Basic, + settingValue, CustomIntSettingDefinitionsFiller, + 16028)}; + AddTypeDependentVisibilityCondition(setting, settingName); + AddTypeDependentEnableCondition(setting, settingName); +} + +void CGUIDialogPVRTimerSettings::AddSingleIntSetting(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingName, + int settingValue, + int minValue, + int step, + int maxValue) +{ + const std::shared_ptr<CSetting> setting{AddEdit(group, settingName, 16028, SettingLevel::Basic, + settingValue, minValue, step, maxValue)}; + AddTypeDependentVisibilityCondition(setting, settingName); + AddTypeDependentEnableCondition(setting, settingName); +} + +void CGUIDialogPVRTimerSettings::AddMultiStringSetting(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingName, + const std::string& settingValue) +{ + const std::shared_ptr<CSetting> setting{AddList(group, settingName, 16028, SettingLevel::Basic, + settingValue, + CustomStringSettingDefinitionsFiller, 16028)}; + AddTypeDependentVisibilityCondition(setting, settingName); + AddTypeDependentEnableCondition(setting, settingName); +} + +void CGUIDialogPVRTimerSettings::AddSingleStringSetting(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingName, + const std::string& settingValue, + bool allowEmptyValue) +{ + const std::shared_ptr<CSetting> setting{ + AddEdit(group, settingName, 16028, SettingLevel::Basic, settingValue, allowEmptyValue)}; + AddTypeDependentVisibilityCondition(setting, settingName); + AddTypeDependentEnableCondition(setting, settingName); } int CGUIDialogPVRTimerSettings::GetWeekdaysFromSetting(const SettingConstPtr& setting) @@ -445,6 +498,7 @@ void CGUIDialogPVRTimerSettings::OnSettingChanged(const std::shared_ptr<const CS if (it != m_typeEntries.end()) { m_timerType = it->second; + m_customTimerSettings->SetTimerType(*m_timerType); // reset certain settings to the defaults of the new timer type @@ -559,6 +613,14 @@ void CGUIDialogPVRTimerSettings::OnSettingChanged(const std::shared_ptr<const CS { m_iRecordingGroup = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); } + else if (m_customTimerSettings->IsCustomIntSetting(settingId)) + { + m_customTimerSettings->UpdateIntProperty(setting); + } + else if (m_customTimerSettings->IsCustomStringSetting(settingId)) + { + m_customTimerSettings->UpdateStringProperty(setting); + } } void CGUIDialogPVRTimerSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting) @@ -629,6 +691,16 @@ bool CGUIDialogPVRTimerSettings::Validate() return true; } +std::string CGUIDialogPVRTimerSettings::GetSettingsLabel(const std::shared_ptr<ISetting>& setting) +{ + // Special handling for add-on supplied custom settings. + const std::string label{m_customTimerSettings->GetSettingsLabel(setting->GetId())}; + if (!label.empty()) + return label; + + return CGUIDialogSettingsManualBase::GetSettingsLabel(setting); +} + bool CGUIDialogPVRTimerSettings::Save() { if (!Validate()) @@ -740,6 +812,9 @@ bool CGUIDialogPVRTimerSettings::Save() // Recording group m_timerInfoTag->m_iRecordingGroup = m_iRecordingGroup; + // Custom properties + m_timerInfoTag->m_customProps = m_customTimerSettings->GetProperties(); + // Set the timer's title to the channel name if it's empty or 'New Timer' if (m_strTitle.empty() || m_strTitle == g_localizeStrings.Get(19056)) { @@ -1303,6 +1378,44 @@ void CGUIDialogPVRTimerSettings::MarginTimeFiller(const SettingConstPtr& setting CLog::LogF(LOGERROR, "No dialog"); } +void CGUIDialogPVRTimerSettings::CustomIntSettingDefinitionsFiller( + const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data); + if (pThis) + { + list.clear(); + + const std::string settingId{setting->GetId()}; + if (pThis->m_customTimerSettings->IsCustomIntSetting(settingId)) + pThis->m_customTimerSettings->IntSettingDefinitionsFiller(settingId, list, current); + } + else + CLog::LogF(LOGERROR, "No dialog"); +} + +void CGUIDialogPVRTimerSettings::CustomStringSettingDefinitionsFiller( + const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data); + if (pThis) + { + list.clear(); + + const std::string settingId{setting->GetId()}; + if (pThis->m_customTimerSettings->IsCustomStringSetting(settingId)) + pThis->m_customTimerSettings->StringSettingDefinitionsFiller(settingId, list, current); + } + else + CLog::LogF(LOGERROR, "No dialog"); +} + std::string CGUIDialogPVRTimerSettings::WeekdaysValueFormatter(const SettingConstPtr& setting) { return CPVRTimerInfoTag::GetWeekdaysString(GetWeekdaysFromSetting(setting), true, true); @@ -1370,6 +1483,12 @@ bool CGUIDialogPVRTimerSettings::TypeReadOnlyCondition(const std::string& condit return false; } + if (pThis->m_customTimerSettings->IsCustomSetting(cond)) + { + return !pThis->m_customTimerSettings->IsSettingReadonlyForTimerState( + cond, pThis->m_timerInfoTag->State()); + } + // Let the PVR client decide... int idx = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); const auto entry = pThis->m_typeEntries.find(idx); @@ -1456,6 +1575,8 @@ bool CGUIDialogPVRTimerSettings::TypeSupportsCondition(const std::string& condit return entry->second->SupportsRecordingFolders(); else if (cond == SETTING_TMR_REC_GROUP) return entry->second->SupportsRecordingGroup(); + else if (pThis->m_customTimerSettings->IsCustomSetting(cond)) + return pThis->m_customTimerSettings->IsSettingSupportedForTimerType(cond, *entry->second); else CLog::LogF(LOGERROR, "Unknown condition"); } diff --git a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h index 06c5b6bce95dd..d0a3f100eade8 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h +++ b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h @@ -11,6 +11,7 @@ #include "XBDateTime.h" #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" // PVR_CHANNEL_INVALID_UID #include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID +#include "pvr/settings/IPVRSettingsContainer.h" #include "settings/SettingConditions.h" #include "settings/dialogs/GUIDialogSettingsManualBase.h" #include "settings/lib/SettingDependency.h" @@ -23,13 +24,15 @@ class CSetting; struct IntegerSettingOption; +struct StringSettingOption; namespace PVR { +class CPVRCustomTimerSettings; class CPVRTimerInfoTag; class CPVRTimerType; -class CGUIDialogPVRTimerSettings : public CGUIDialogSettingsManualBase +class CGUIDialogPVRTimerSettings : public CGUIDialogSettingsManualBase, public IPVRSettingsContainer { public: CGUIDialogPVRTimerSettings(); @@ -39,6 +42,24 @@ class CGUIDialogPVRTimerSettings : public CGUIDialogSettingsManualBase void SetTimer(const std::shared_ptr<CPVRTimerInfoTag>& timer); + // implementation of IPVRSettingsContainer + void AddMultiIntSetting(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingName, + int settingValue) override; + void AddSingleIntSetting(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingName, + int settingValue, + int minValue, + int step, + int maxValue) override; + void AddMultiStringSetting(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingName, + const std::string& settingValue) override; + void AddSingleStringSetting(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingName, + const std::string& settingValue, + bool allowEmptyValue) override; + protected: // implementation of ISettingCallback void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; @@ -46,6 +67,7 @@ class CGUIDialogPVRTimerSettings : public CGUIDialogSettingsManualBase // specialization of CGUIDialogSettingsBase bool AllowResettingSettings() const override { return false; } + std::string GetSettingsLabel(const std::shared_ptr<ISetting>& setting) override; bool Save() override; void SetupView() override; @@ -104,6 +126,14 @@ class CGUIDialogPVRTimerSettings : public CGUIDialogSettingsManualBase std::vector<IntegerSettingOption>& list, int& current, void* data); + static void CustomIntSettingDefinitionsFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + static void CustomStringSettingDefinitionsFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); static std::string WeekdaysValueFormatter(const std::shared_ptr<const CSetting>& setting); @@ -165,6 +195,8 @@ class CGUIDialogPVRTimerSettings : public CGUIDialogSettingsManualBase typedef std::map<int, ChannelDescriptor> ChannelEntriesMap; + std::unique_ptr<CPVRCustomTimerSettings> m_customTimerSettings; + std::shared_ptr<CPVRTimerInfoTag> m_timerInfoTag; TypeEntriesMap m_typeEntries; ChannelEntriesMap m_channelEntries; diff --git a/xbmc/pvr/settings/CMakeLists.txt b/xbmc/pvr/settings/CMakeLists.txt index 6697b11633516..d171dc8b613a7 100644 --- a/xbmc/pvr/settings/CMakeLists.txt +++ b/xbmc/pvr/settings/CMakeLists.txt @@ -1,7 +1,18 @@ -set(SOURCES PVRIntSettingValues.cpp - PVRSettings.cpp) +set(SOURCES PVRCustomTimerSettings.cpp + PVRIntSettingDefinition.cpp + PVRIntSettingValues.cpp + PVRSettings.cpp + PVRStringSettingDefinition.cpp + PVRStringSettingValues.cpp + PVRTimerSettingDefinition.cpp) -set(HEADERS PVRIntSettingValues.h - PVRSettings.h) +set(HEADERS IPVRSettingsContainer.h + PVRCustomTimerSettings.h + PVRIntSettingDefinition.h + PVRIntSettingValues.h + PVRSettings.h + PVRStringSettingDefinition.h + PVRStringSettingValues.h + PVRTimerSettingDefinition.h) core_add_library(pvr_settings) diff --git a/xbmc/pvr/settings/IPVRSettingsContainer.h b/xbmc/pvr/settings/IPVRSettingsContainer.h new file mode 100644 index 0000000000000..d5f76ce8f08af --- /dev/null +++ b/xbmc/pvr/settings/IPVRSettingsContainer.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 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 <memory> + +class CSettingGroup; + +namespace PVR +{ +class IPVRSettingsContainer +{ +public: + virtual ~IPVRSettingsContainer() = default; + + virtual void AddMultiIntSetting(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingName, + int settingValue) + { + } + virtual void AddSingleIntSetting(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingName, + int settingValue, + int minValue, + int step, + int maxValue) + { + } + virtual void AddMultiStringSetting(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingName, + const std::string& settingValue) + { + } + virtual void AddSingleStringSetting(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingName, + const std::string& settingValue, + bool allowEmptyValue) + { + } +}; +} // namespace PVR diff --git a/xbmc/pvr/settings/PVRCustomTimerSettings.cpp b/xbmc/pvr/settings/PVRCustomTimerSettings.cpp new file mode 100644 index 0000000000000..c62266896b588 --- /dev/null +++ b/xbmc/pvr/settings/PVRCustomTimerSettings.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2024 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 "PVRCustomTimerSettings.h" + +#include "pvr/settings/IPVRSettingsContainer.h" +#include "pvr/settings/PVRTimerSettingDefinition.h" +#include "pvr/timers/PVRTimerType.h" +#include "settings/lib/Setting.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <map> +#include <memory> +#include <string> +#include <vector> + +namespace PVR +{ +static constexpr const char* SETTING_TMR_CUSTOM_INT{"customsetting.int"}; +static constexpr const char* SETTING_TMR_CUSTOM_STRING{"customsetting.string"}; + +CPVRCustomTimerSettings::CPVRCustomTimerSettings( + const CPVRTimerType& timerType, + const CPVRTimerInfoTag::CustomPropsMap& customProps, + const std::map<int, std::shared_ptr<CPVRTimerType>>& typeEntries) + : m_customProps(customProps) +{ + unsigned int idx{0}; + for (const auto& type : typeEntries) + { + const std::vector<std::shared_ptr<const CPVRTimerSettingDefinition>>& settingDefs{ + type.second->GetCustomSettingDefinitions()}; + for (const auto& settingDef : settingDefs) + { + std::string settingIdPrefix; + if (settingDef->IsIntDefinition()) + { + settingIdPrefix = SETTING_TMR_CUSTOM_INT; + } + else if (settingDef->IsStringDefinition()) + { + settingIdPrefix = SETTING_TMR_CUSTOM_STRING; + } + else + { + CLog::LogF(LOGERROR, "Unknown custom setting definition ignored!"); + continue; + } + + const std::string settingId{StringUtils::Format("{}-{}", settingIdPrefix, idx)}; + m_customSettingDefs.insert({settingId, settingDef}); + ++idx; + } + } + + SetTimerType(timerType); +} + +void CPVRCustomTimerSettings::SetTimerType(const CPVRTimerType& timerType) +{ + CPVRTimerInfoTag::CustomPropsMap newCustomProps; + for (const auto& entry : m_customSettingDefs) + { + // Complete custom props for given type. + const CPVRTimerSettingDefinition& def{*entry.second}; + if (def.GetTimerTypeId() == timerType.GetTypeId() && + def.GetClientId() == timerType.GetClientId()) + { + const auto it{m_customProps.find(def.GetId())}; + if (it == m_customProps.cend()) + newCustomProps.insert({def.GetId(), {def.GetType(), def.GetDefaultValue()}}); + else + newCustomProps.insert({def.GetId(), {(*it).second.type, (*it).second.value}}); + } + } + m_customProps = newCustomProps; +} + +void CPVRCustomTimerSettings::AddSettings(IPVRSettingsContainer& settingsContainer, + const std::shared_ptr<CSettingGroup>& group) +{ + for (const auto& settingDef : m_customSettingDefs) + { + const CPVRTimerSettingDefinition& def{*settingDef.second}; + const auto it{m_customProps.find(def.GetId())}; + if (it == m_customProps.cend()) + continue; + + const std::string settingName{settingDef.first}; + if (IsCustomIntSetting(settingName)) + { + const int intValue{(*it).second.value.asInteger32()}; + const CPVRIntSettingDefinition& intDef{def.GetIntDefinition()}; + if (intDef.GetValues().empty()) + settingsContainer.AddSingleIntSetting(group, settingName, intValue, intDef.GetMinValue(), + intDef.GetStepValue(), intDef.GetMaxValue()); + else + settingsContainer.AddMultiIntSetting(group, settingName, intValue); + } + else if (IsCustomStringSetting(settingName)) + { + const std::string stringValue{(*it).second.value.asString()}; + const CPVRStringSettingDefinition& stringDef{def.GetStringDefinition()}; + if (stringDef.GetValues().empty()) + settingsContainer.AddSingleStringSetting(group, settingName, stringValue, + stringDef.IsAllowEmptyValue()); + else + settingsContainer.AddMultiStringSetting(group, settingName, stringValue); + } + } +} + +bool CPVRCustomTimerSettings::IsCustomSetting(const std::string& settingId) const +{ + return IsCustomIntSetting(settingId) || IsCustomStringSetting(settingId); +} + +bool CPVRCustomTimerSettings::IsCustomIntSetting(const std::string& settingId) const +{ + return settingId.starts_with(SETTING_TMR_CUSTOM_INT); +} + +bool CPVRCustomTimerSettings::IsCustomStringSetting(const std::string& settingId) const +{ + return settingId.starts_with(SETTING_TMR_CUSTOM_STRING); +} + +bool CPVRCustomTimerSettings::UpdateIntProperty(const std::shared_ptr<const CSetting>& setting) +{ + const auto def{GetSettingDefintion(setting->GetId())}; + if (!def) + { + CLog::LogF(LOGERROR, "Custom int setting definition not found"); + return false; + } + + CVariant& prop{m_customProps[def->GetId()].value}; + prop = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); + return true; +} + +bool CPVRCustomTimerSettings::UpdateStringProperty(const std::shared_ptr<const CSetting>& setting) +{ + const auto def{GetSettingDefintion(setting->GetId())}; + if (!def) + { + CLog::LogF(LOGERROR, "Custom string setting definition not found"); + return false; + } + + CVariant& prop{m_customProps[def->GetId()].value}; + prop = std::static_pointer_cast<const CSettingString>(setting)->GetValue(); + return true; +} + +std::string CPVRCustomTimerSettings::GetSettingsLabel(const std::string& settingId) const +{ + const auto def{GetSettingDefintion(settingId)}; + if (!def) + return {}; + + return def->GetName(); +} + +bool CPVRCustomTimerSettings::IntSettingDefinitionsFiller(const std::string& settingId, + std::vector<IntegerSettingOption>& list, + int& current) +{ + const auto def{GetSettingDefintion(settingId)}; + if (!def) + { + CLog::LogF(LOGERROR, "Custom setting definition not found"); + return false; + } + + const std::vector<SettingIntValue>& values{def->GetIntDefinition().GetValues()}; + std::transform(values.cbegin(), values.cend(), std::back_inserter(list), + [](const auto& value) { return IntegerSettingOption(value.first, value.second); }); + + const auto it2{m_customProps.find(def->GetId())}; + if (it2 != m_customProps.cend()) + current = (*it2).second.value.asInteger32(); + else + current = def->GetIntDefinition().GetDefaultValue(); + + return true; +} + +bool CPVRCustomTimerSettings::StringSettingDefinitionsFiller(const std::string& settingId, + std::vector<StringSettingOption>& list, + std::string& current) +{ + const auto def{GetSettingDefintion(settingId)}; + if (!def) + { + CLog::LogF(LOGERROR, "Custom setting definition not found"); + return false; + } + + const std::vector<SettingStringValue>& values{def->GetStringDefinition().GetValues()}; + std::transform(values.cbegin(), values.cend(), std::back_inserter(list), + [](const auto& value) { return StringSettingOption(value.first, value.second); }); + + const auto it2{m_customProps.find(def->GetId())}; + if (it2 != m_customProps.cend()) + current = (*it2).second.value.asString(); + else + current = def->GetStringDefinition().GetDefaultValue(); + + return true; +} + +bool CPVRCustomTimerSettings::IsSettingReadonlyForTimerState(const std::string& settingId, + PVR_TIMER_STATE timerState) const +{ + const auto def{GetSettingDefintion(settingId)}; + if (!def) + { + CLog::LogF(LOGERROR, "Custom setting definition not found"); + return false; + } + + return def->IsReadonlyForTimerState(timerState); +} + +bool CPVRCustomTimerSettings::IsSettingSupportedForTimerType(const std::string& settingId, + const CPVRTimerType& timerType) const +{ + const auto def{GetSettingDefintion(settingId)}; + if (!def) + { + CLog::LogF(LOGERROR, "Custom setting definition not found"); + return false; + } + + return timerType.GetClientId() == def->GetClientId() && + timerType.GetTypeId() == def->GetTimerTypeId(); +} + +std::shared_ptr<const CPVRTimerSettingDefinition> CPVRCustomTimerSettings::GetSettingDefintion( + const std::string& settingId) const +{ + const auto it{m_customSettingDefs.find(settingId)}; + if (it == m_customSettingDefs.cend()) + return {}; + + return (*it).second; +} +} // namespace PVR diff --git a/xbmc/pvr/settings/PVRCustomTimerSettings.h b/xbmc/pvr/settings/PVRCustomTimerSettings.h new file mode 100644 index 0000000000000..864269c2125b7 --- /dev/null +++ b/xbmc/pvr/settings/PVRCustomTimerSettings.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 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 "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h" // PVR_TIMER_STATE +#include "pvr/timers/PVRTimerInfoTag.h" + +#include <map> +#include <memory> +#include <string> +#include <vector> + +class CSetting; +class CSettingGroup; +struct IntegerSettingOption; +struct StringSettingOption; + +namespace PVR +{ +class CPVRTimerSettingDefinition; +class CPVRTimerType; +class IPVRSettingsContainer; + +class CPVRCustomTimerSettings +{ +public: + CPVRCustomTimerSettings(const CPVRTimerType& timerType, + const CPVRTimerInfoTag::CustomPropsMap& customProps, + const std::map<int, std::shared_ptr<CPVRTimerType>>& typeEntries); + virtual ~CPVRCustomTimerSettings() = default; + + void SetTimerType(const CPVRTimerType& timerType); + + void AddSettings(IPVRSettingsContainer& settingsContainer, + const std::shared_ptr<CSettingGroup>& group); + + bool IsCustomSetting(const std::string& settingId) const; + bool IsCustomIntSetting(const std::string& settingId) const; + bool IsCustomStringSetting(const std::string& settingId) const; + + const CPVRTimerInfoTag::CustomPropsMap& GetProperties() const { return m_customProps; } + + bool UpdateIntProperty(const std::shared_ptr<const CSetting>& setting); + bool UpdateStringProperty(const std::shared_ptr<const CSetting>& setting); + + std::string GetSettingsLabel(const std::string& settingId) const; + + bool IntSettingDefinitionsFiller(const std::string& settingId, + std::vector<IntegerSettingOption>& list, + int& current); + bool StringSettingDefinitionsFiller(const std::string& settingId, + std::vector<StringSettingOption>& list, + std::string& current); + + bool IsSettingReadonlyForTimerState(const std::string& settingId, + PVR_TIMER_STATE timerState) const; + bool IsSettingSupportedForTimerType(const std::string& setting, + const CPVRTimerType& timerType) const; + +private: + std::shared_ptr<const CPVRTimerSettingDefinition> GetSettingDefintion( + const std::string& settingId) const; + + using CustomSettingDefinitionsMap = + std::map<std::string, + std::shared_ptr<const CPVRTimerSettingDefinition>>; // setting id, setting def + + CustomSettingDefinitionsMap m_customSettingDefs; + CPVRTimerInfoTag::CustomPropsMap m_customProps; +}; +} // namespace PVR diff --git a/xbmc/pvr/settings/PVRIntSettingDefinition.cpp b/xbmc/pvr/settings/PVRIntSettingDefinition.cpp new file mode 100644 index 0000000000000..d36f8321fa704 --- /dev/null +++ b/xbmc/pvr/settings/PVRIntSettingDefinition.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 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 "PVRIntSettingDefinition.h" + +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h" + +namespace PVR +{ +CPVRIntSettingDefinition::CPVRIntSettingDefinition(const PVR_INT_SETTING_DEFINITION& def) + : m_values{def.values, def.iValuesSize, def.iDefaultValue}, + m_minValue{def.iMinValue}, + m_step{def.iStep}, + m_maxValue{def.iMaxValue} +{ +} + +bool CPVRIntSettingDefinition::operator==(const CPVRIntSettingDefinition& right) const +{ + return (m_values == right.m_values && m_minValue == right.m_minValue && m_step == right.m_step && + m_maxValue == right.m_maxValue); +} + +bool CPVRIntSettingDefinition::operator!=(const CPVRIntSettingDefinition& right) const +{ + return !(*this == right); +} +} // namespace PVR diff --git a/xbmc/pvr/settings/PVRIntSettingDefinition.h b/xbmc/pvr/settings/PVRIntSettingDefinition.h new file mode 100644 index 0000000000000..5f37bb452537b --- /dev/null +++ b/xbmc/pvr/settings/PVRIntSettingDefinition.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 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 "pvr/settings/PVRIntSettingValues.h" + +#include <vector> + +struct PVR_INT_SETTING_DEFINITION; + +namespace PVR +{ +class CPVRIntSettingDefinition +{ +public: + CPVRIntSettingDefinition() = default; + explicit CPVRIntSettingDefinition(const PVR_INT_SETTING_DEFINITION& def); + + virtual ~CPVRIntSettingDefinition() = default; + + bool operator==(const CPVRIntSettingDefinition& right) const; + bool operator!=(const CPVRIntSettingDefinition& right) const; + + const std::vector<SettingIntValue>& GetValues() const { return m_values.GetValues(); } + int GetDefaultValue() const { return m_values.GetDefaultValue(); } + int GetMinValue() const { return m_minValue; } + int GetStepValue() const { return m_step; } + int GetMaxValue() const { return m_maxValue; } + +private: + CPVRIntSettingValues m_values; + int m_minValue{0}; + int m_step{0}; + int m_maxValue{0}; +}; +} // namespace PVR diff --git a/xbmc/pvr/settings/PVRIntSettingValues.cpp b/xbmc/pvr/settings/PVRIntSettingValues.cpp index 01ef45b9e42c0..680671badb94f 100644 --- a/xbmc/pvr/settings/PVRIntSettingValues.cpp +++ b/xbmc/pvr/settings/PVRIntSettingValues.cpp @@ -44,6 +44,10 @@ CPVRIntSettingValues::CPVRIntSettingValues(struct PVR_ATTRIBUTE_INT_VALUE* value } } +CPVRIntSettingValues::CPVRIntSettingValues(int defaultValue) : m_defaultValue(defaultValue) +{ +} + CPVRIntSettingValues::CPVRIntSettingValues(struct PVR_ATTRIBUTE_INT_VALUE* values, unsigned int valuesSize, unsigned int defaultValue, @@ -59,4 +63,8 @@ CPVRIntSettingValues::CPVRIntSettingValues(const std::vector<SettingIntValue>& v { } +bool CPVRIntSettingValues::operator==(const CPVRIntSettingValues& right) const +{ + return (m_defaultValue == right.m_defaultValue && m_values == right.m_values); +} } // namespace PVR diff --git a/xbmc/pvr/settings/PVRIntSettingValues.h b/xbmc/pvr/settings/PVRIntSettingValues.h index 6299f819601b6..209104a554205 100644 --- a/xbmc/pvr/settings/PVRIntSettingValues.h +++ b/xbmc/pvr/settings/PVRIntSettingValues.h @@ -21,7 +21,8 @@ using SettingIntValue = std::pair<std::string, int>; class CPVRIntSettingValues { public: - CPVRIntSettingValues(int defaultValue) : m_defaultValue(defaultValue) {} + CPVRIntSettingValues() = default; + explicit CPVRIntSettingValues(int defaultValue); CPVRIntSettingValues(struct PVR_ATTRIBUTE_INT_VALUE* values, unsigned int valuesSize, int defaultValue, @@ -34,10 +35,7 @@ class CPVRIntSettingValues virtual ~CPVRIntSettingValues() = default; - bool operator==(const CPVRIntSettingValues& right) const - { - return (m_defaultValue == right.m_defaultValue && m_values == right.m_values); - } + bool operator==(const CPVRIntSettingValues& right) const; const std::vector<SettingIntValue>& GetValues() const { return m_values; } int GetDefaultValue() const { return m_defaultValue; } diff --git a/xbmc/pvr/settings/PVRStringSettingDefinition.cpp b/xbmc/pvr/settings/PVRStringSettingDefinition.cpp new file mode 100644 index 0000000000000..e7f897d175bdb --- /dev/null +++ b/xbmc/pvr/settings/PVRStringSettingDefinition.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 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 "PVRStringSettingDefinition.h" + +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h" + +namespace PVR +{ +CPVRStringSettingDefinition::CPVRStringSettingDefinition(const PVR_STRING_SETTING_DEFINITION& def) + : m_values{def.values, def.iValuesSize, def.strDefaultValue}, + m_allowEmptyValue{def.bAllowEmptyValue} +{ +} + +bool CPVRStringSettingDefinition::operator==(const CPVRStringSettingDefinition& right) const +{ + return (m_values == right.m_values && m_allowEmptyValue == right.m_allowEmptyValue); +} + +bool CPVRStringSettingDefinition::operator!=(const CPVRStringSettingDefinition& right) const +{ + return !(*this == right); +} +} // namespace PVR diff --git a/xbmc/pvr/settings/PVRStringSettingDefinition.h b/xbmc/pvr/settings/PVRStringSettingDefinition.h new file mode 100644 index 0000000000000..da42f46149c03 --- /dev/null +++ b/xbmc/pvr/settings/PVRStringSettingDefinition.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 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 "pvr/settings/PVRStringSettingValues.h" + +#include <vector> + +struct PVR_STRING_SETTING_DEFINITION; + +namespace PVR +{ +class CPVRStringSettingDefinition +{ +public: + CPVRStringSettingDefinition() = default; + explicit CPVRStringSettingDefinition(const PVR_STRING_SETTING_DEFINITION& def); + + virtual ~CPVRStringSettingDefinition() = default; + + bool operator==(const CPVRStringSettingDefinition& right) const; + bool operator!=(const CPVRStringSettingDefinition& right) const; + + const std::vector<SettingStringValue>& GetValues() const { return m_values.GetValues(); } + const std::string& GetDefaultValue() const { return m_values.GetDefaultValue(); } + + bool IsAllowEmptyValue() const { return m_allowEmptyValue; } + +private: + CPVRStringSettingValues m_values; + bool m_allowEmptyValue{true}; +}; +} // namespace PVR diff --git a/xbmc/pvr/settings/PVRStringSettingValues.cpp b/xbmc/pvr/settings/PVRStringSettingValues.cpp new file mode 100644 index 0000000000000..ef5c1818270e6 --- /dev/null +++ b/xbmc/pvr/settings/PVRStringSettingValues.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 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 "PVRStringSettingValues.h" + +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h" +#include "guilib/LocalizeStrings.h" +#include "utils/StringUtils.h" + +#include <string> + +namespace PVR +{ +CPVRStringSettingValues::CPVRStringSettingValues(struct PVR_ATTRIBUTE_STRING_VALUE* values, + unsigned int valuesSize, + const std::string& defaultValue, + int defaultDescriptionResourceId /* = 0 */) + : m_defaultValue(defaultValue) +{ + if (values && valuesSize > 0) + { + m_values.reserve(valuesSize); + for (unsigned int i = 0; i < valuesSize; ++i) + { + const std::string value{values[i].strValue ? values[i].strValue : ""}; + const char* desc{values[i].strDescription}; + std::string strDescr{desc ? desc : ""}; + if (strDescr.empty()) + { + // No description given by addon. Create one from value. + if (defaultDescriptionResourceId > 0) + strDescr = StringUtils::Format( + "{} {}", g_localizeStrings.Get(defaultDescriptionResourceId), value); + else + strDescr = value; + } + m_values.emplace_back(strDescr, value); + } + } +} + +bool CPVRStringSettingValues::operator==(const CPVRStringSettingValues& right) const +{ + return (m_defaultValue == right.m_defaultValue && m_values == right.m_values); +} +} // namespace PVR diff --git a/xbmc/pvr/settings/PVRStringSettingValues.h b/xbmc/pvr/settings/PVRStringSettingValues.h new file mode 100644 index 0000000000000..591b5018db929 --- /dev/null +++ b/xbmc/pvr/settings/PVRStringSettingValues.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 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 <string> +#include <utility> +#include <vector> + +struct PVR_ATTRIBUTE_STRING_VALUE; + +namespace PVR +{ +using SettingStringValue = std::pair<std::string, std::string>; + +class CPVRStringSettingValues +{ +public: + CPVRStringSettingValues() = default; + CPVRStringSettingValues(struct PVR_ATTRIBUTE_STRING_VALUE* values, + unsigned int valuesSize, + const std::string& defaultValue, + int defaultDescriptionResourceId = 0); + + virtual ~CPVRStringSettingValues() = default; + + bool operator==(const CPVRStringSettingValues& right) const; + + const std::vector<SettingStringValue>& GetValues() const { return m_values; } + const std::string& GetDefaultValue() const { return m_defaultValue; } + +private: + std::vector<SettingStringValue> m_values; + std::string m_defaultValue; +}; +} // namespace PVR diff --git a/xbmc/pvr/settings/PVRTimerSettingDefinition.cpp b/xbmc/pvr/settings/PVRTimerSettingDefinition.cpp new file mode 100644 index 0000000000000..cfabb487546c0 --- /dev/null +++ b/xbmc/pvr/settings/PVRTimerSettingDefinition.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2024 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 "PVRTimerSettingDefinition.h" + +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h" +#include "utils/Variant.h" +#include "utils/log.h" + +namespace PVR +{ +std::vector<std::shared_ptr<const CPVRTimerSettingDefinition>> CPVRTimerSettingDefinition:: + CreateSettingDefinitionsList(int clientId, + unsigned int timerTypeId, + struct PVR_SETTING_DEFINITION** defs, + unsigned int settingDefsSize) +{ + std::vector<std::shared_ptr<const CPVRTimerSettingDefinition>> defsList; + if (defs && settingDefsSize > 0) + { + defsList.reserve(settingDefsSize); + for (unsigned int i = 0; i < settingDefsSize; ++i) + { + const PVR_SETTING_DEFINITION* def{defs[i]}; + if (def) + { + defsList.emplace_back( + std::make_shared<const CPVRTimerSettingDefinition>(clientId, timerTypeId, *def)); + } + } + } + return defsList; +} + +CPVRTimerSettingDefinition::CPVRTimerSettingDefinition(int clientId, + unsigned int timerTypeId, + const PVR_SETTING_DEFINITION& def) + : m_clientId(clientId), + m_timerTypeId(timerTypeId), + m_id(def.iId), + m_name(def.strName ? def.strName : ""), + m_type(def.eType), + m_readonlyConditions(def.iReadonlyConditions) +{ + if (def.intSettingDefinition) + m_intDefinition = CPVRIntSettingDefinition{*def.intSettingDefinition}; + if (def.stringSettingDefinition) + m_stringDefinition = CPVRStringSettingDefinition{*def.stringSettingDefinition}; +} + +bool CPVRTimerSettingDefinition::operator==(const CPVRTimerSettingDefinition& right) const +{ + return (m_id == right.m_id && m_name == right.m_name && m_type == right.m_type && + m_readonlyConditions == right.m_readonlyConditions && + m_intDefinition == right.m_intDefinition && + m_stringDefinition == right.m_stringDefinition); +} + +bool CPVRTimerSettingDefinition::operator!=(const CPVRTimerSettingDefinition& right) const +{ + return !(*this == right); +} + +CVariant CPVRTimerSettingDefinition::GetDefaultValue() const +{ + if (GetType() == PVR_SETTING_TYPE::INTEGER) + return CVariant{GetIntDefinition().GetDefaultValue()}; + else if (GetType() == PVR_SETTING_TYPE::STRING) + return CVariant{GetStringDefinition().GetDefaultValue()}; + + CLog::LogF(LOGERROR, "Unknown setting type for custom property"); + return {}; +} + +bool CPVRTimerSettingDefinition::IsReadonlyForTimerState(PVR_TIMER_STATE timerState) const +{ + switch (timerState) + { + case PVR_TIMER_STATE_DISABLED: + return m_readonlyConditions & PVR_SETTING_READONLY_CONDITION_TIMER_DISABLED; + case PVR_TIMER_STATE_SCHEDULED: + case PVR_TIMER_STATE_CONFLICT_OK: + case PVR_TIMER_STATE_CONFLICT_NOK: + return m_readonlyConditions & PVR_SETTING_READONLY_CONDITION_TIMER_SCHEDULED; + case PVR_TIMER_STATE_RECORDING: + return m_readonlyConditions & PVR_SETTING_READONLY_CONDITION_TIMER_RECORDING; + case PVR_TIMER_STATE_COMPLETED: + case PVR_TIMER_STATE_ABORTED: + case PVR_TIMER_STATE_CANCELLED: + case PVR_TIMER_STATE_ERROR: + return m_readonlyConditions & PVR_SETTING_READONLY_CONDITION_TIMER_COMPLETED; + default: + CLog::LogF(LOGWARNING, "Unhandled timer state {}", timerState); + break; + } + return false; +} +} // namespace PVR diff --git a/xbmc/pvr/settings/PVRTimerSettingDefinition.h b/xbmc/pvr/settings/PVRTimerSettingDefinition.h new file mode 100644 index 0000000000000..beec742a48fea --- /dev/null +++ b/xbmc/pvr/settings/PVRTimerSettingDefinition.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 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 "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h" +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h" +#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID +#include "pvr/settings/PVRIntSettingDefinition.h" +#include "pvr/settings/PVRStringSettingDefinition.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +class CVariant; + +namespace PVR +{ +class CPVRTimerSettingDefinition +{ +public: + static std::vector<std::shared_ptr<const CPVRTimerSettingDefinition>> + CreateSettingDefinitionsList(int clientId, + unsigned int timerTypeId, + struct PVR_SETTING_DEFINITION** defs, + unsigned int settingDefsSize); + + CPVRTimerSettingDefinition(int clientId, + unsigned int timerTypeId, + const PVR_SETTING_DEFINITION& def); + + virtual ~CPVRTimerSettingDefinition() = default; + + bool operator==(const CPVRTimerSettingDefinition& right) const; + bool operator!=(const CPVRTimerSettingDefinition& right) const; + + int GetClientId() const { return m_clientId; } + unsigned int GetTimerTypeId() const { return m_timerTypeId; } + unsigned int GetId() const { return m_id; } + const std::string& GetName() const { return m_name; } + PVR_SETTING_TYPE GetType() const { return m_type; } + CVariant GetDefaultValue() const; + + bool IsIntDefinition() const { return m_type == PVR_SETTING_TYPE::INTEGER; } + bool IsStringDefinition() const { return m_type == PVR_SETTING_TYPE::STRING; } + const CPVRIntSettingDefinition& GetIntDefinition() const { return m_intDefinition; } + const CPVRStringSettingDefinition& GetStringDefinition() const { return m_stringDefinition; } + + bool IsReadonlyForTimerState(PVR_TIMER_STATE timerState) const; + +private: + int m_clientId{PVR_CLIENT_INVALID_UID}; + unsigned int m_timerTypeId{PVR_TIMER_TYPE_NONE}; + unsigned int m_id{0}; + std::string m_name; + PVR_SETTING_TYPE m_type{PVR_SETTING_TYPE::INTEGER}; + uint64_t m_readonlyConditions{PVR_SETTING_READONLY_CONDITION_NONE}; + CPVRIntSettingDefinition m_intDefinition; + CPVRStringSettingDefinition m_stringDefinition; +}; +} // namespace PVR diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.cpp b/xbmc/pvr/timers/PVRTimerInfoTag.cpp index 58e7403a8663d..50c61a3871d8a 100644 --- a/xbmc/pvr/timers/PVRTimerInfoTag.cpp +++ b/xbmc/pvr/timers/PVRTimerInfoTag.cpp @@ -9,6 +9,7 @@ #include "PVRTimerInfoTag.h" #include "ServiceBroker.h" +#include "addons/AddonVersion.h" #include "guilib/LocalizeStrings.h" #include "pvr/PVRDatabase.h" #include "pvr/PVRManager.h" @@ -89,7 +90,8 @@ CPVRTimerInfoTag::CPVRTimerInfoTag(bool bRadio /* = false */) CPVRTimerInfoTag::CPVRTimerInfoTag(const PVR_TIMER& timer, const std::shared_ptr<CPVRChannel>& channel, - unsigned int iClientId) + unsigned int iClientId, + const ADDON::CAddonVersion& addonApiVersion) : m_strTitle(timer.strTitle ? timer.strTitle : ""), m_strEpgSearchString(timer.strEpgSearchString ? timer.strEpgSearchString : ""), m_bFullTextEpgSearch(timer.bFullTextEpgSearch), @@ -133,6 +135,22 @@ CPVRTimerInfoTag::CPVRTimerInfoTag(const PVR_TIMER& timer, if (m_iClientIndex == PVR_TIMER_NO_CLIENT_INDEX) CLog::LogF(LOGERROR, "Invalid client index supplied by client {} (must be > 0)!", m_iClientId); + //! @todo version check can be removed with next incompatible API bump + static const ADDON::CAddonVersion customSettingsMinApiVersion{"9.1.0"}; + if (addonApiVersion >= customSettingsMinApiVersion) + { + for (unsigned int i = 0; i < timer.iCustomPropsSize; ++i) + { + const PVR_SETTING_KEY_VALUE_PAIR& prop{timer.customProps[i]}; + if (prop.eType == PVR_SETTING_TYPE::INTEGER) + m_customProps.insert({prop.iKey, {prop.eType, CVariant{prop.iValue}}}); + else if (prop.eType == PVR_SETTING_TYPE::STRING) + m_customProps.insert({prop.iKey, {prop.eType, CVariant{prop.strValue}}}); + else + CLog::LogF(LOGERROR, "Unknown setting type for custom property"); + } + } + const std::shared_ptr<const CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId); if (client && client->GetClientCapabilities().SupportsTimers()) @@ -221,7 +239,8 @@ bool CPVRTimerInfoTag::operator==(const CPVRTimerInfoTag& right) const m_iRadioChildTimersActive == right.m_iRadioChildTimersActive && m_iRadioChildTimersConflictNOK == right.m_iRadioChildTimersConflictNOK && m_iRadioChildTimersRecording == right.m_iRadioChildTimersRecording && - m_iRadioChildTimersErrors == right.m_iRadioChildTimersErrors); + m_iRadioChildTimersErrors == right.m_iRadioChildTimersErrors && + m_customProps == right.m_customProps); } bool CPVRTimerInfoTag::operator!=(const CPVRTimerInfoTag& right) const @@ -605,6 +624,7 @@ bool CPVRTimerInfoTag::UpdateEntry(const std::shared_ptr<const CPVRTimerInfoTag> m_strSummary = tag->m_strSummary; m_channel = tag->m_channel; m_bProbedEpgTag = tag->m_bProbedEpgTag; + m_customProps = tag->m_customProps; m_iTVChildTimersActive = tag->m_iTVChildTimersActive; m_iTVChildTimersConflictNOK = tag->m_iTVChildTimersConflictNOK; diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.h b/xbmc/pvr/timers/PVRTimerInfoTag.h index 44826ba6fdcb0..4aa17696eb358 100644 --- a/xbmc/pvr/timers/PVRTimerInfoTag.h +++ b/xbmc/pvr/timers/PVRTimerInfoTag.h @@ -9,15 +9,23 @@ #pragma once #include "XBDateTime.h" +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h" // PVR_SETTING_TYPE #include "pvr/timers/PVRTimerType.h" #include "threads/CriticalSection.h" #include "utils/ISerializable.h" +#include "utils/Variant.h" +#include <map> #include <memory> #include <string> struct PVR_TIMER; +namespace ADDON +{ +class CAddonVersion; +} + namespace PVR { class CPVRChannel; @@ -40,7 +48,8 @@ class CPVRTimerInfoTag final : public ISerializable explicit CPVRTimerInfoTag(bool bRadio = false); CPVRTimerInfoTag(const PVR_TIMER& timer, const std::shared_ptr<CPVRChannel>& channel, - unsigned int iClientId); + unsigned int iClientId, + const ADDON::CAddonVersion& addonApiVersion); bool operator==(const CPVRTimerInfoTag& right) const; bool operator!=(const CPVRTimerInfoTag& right) const; @@ -514,6 +523,31 @@ class CPVRTimerInfoTag final : public ISerializable */ int RecordingGroup() const { return m_iRecordingGroup; } + /*! + * @brief custom property detail: type, value + */ + struct CustomPropDetails + { + PVR_SETTING_TYPE type{PVR_SETTING_TYPE::INTEGER}; + CVariant value; + + bool operator==(const CustomPropDetails& right) const + { + return type == right.type && value == right.value; + } + }; + + /*! + * @brief custom properties map: <prop id, details> + */ + using CustomPropsMap = std::map<unsigned int, CustomPropDetails>; + + /*! + * @brief Get custom properties for this tag. + * @return The list of properties or an empty list if none present. + */ + const CustomPropsMap& GetCustomProperties() const { return m_customProps; } + /*! * @brief Get the UID of the epg event associated with this timer tag, if any. * @return The UID or EPG_TAG_INVALID_UID. @@ -651,6 +685,7 @@ class CPVRTimerInfoTag final : public ISerializable mutable unsigned int m_iEpgUid; /*!< id of epg event associated with this timer, EPG_TAG_INVALID_UID if none. */ std::string m_strSeriesLink; /*!< series link */ + CustomPropsMap m_customProps; /*!< the map with custom properties supplied by the client. */ CDateTime m_StartTime; /*!< start time */ CDateTime m_StopTime; /*!< stop time */ diff --git a/xbmc/pvr/timers/PVRTimerType.cpp b/xbmc/pvr/timers/PVRTimerType.cpp index 232bb07f7a7e1..3df0f81808211 100644 --- a/xbmc/pvr/timers/PVRTimerType.cpp +++ b/xbmc/pvr/timers/PVRTimerType.cpp @@ -9,10 +9,12 @@ #include "PVRTimerType.h" #include "ServiceBroker.h" +#include "addons/AddonVersion.h" #include "guilib/LocalizeStrings.h" #include "pvr/PVRManager.h" #include "pvr/addons/PVRClient.h" #include "pvr/addons/PVRClients.h" +#include "pvr/settings/PVRTimerSettingDefinition.h" #include "utils/StringUtils.h" #include "utils/log.h" @@ -154,7 +156,9 @@ CPVRTimerType::CPVRTimerType() { } -CPVRTimerType::CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId) +CPVRTimerType::CPVRTimerType(const PVR_TIMER_TYPE& type, + int iClientId, + const ADDON::CAddonVersion& addonApiVersion) : m_iClientId(iClientId), m_iTypeId(type.iId), m_iAttributes(type.iAttributes), @@ -162,6 +166,13 @@ CPVRTimerType::CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId) { InitDescription(); InitAttributeValues(type); + + //! @todo version check can be removed with next incompatible API bump + static const ADDON::CAddonVersion customSettingsMinApiVersion{"9.1.0"}; + if (addonApiVersion >= customSettingsMinApiVersion) + { + InitCustomSettingDefinitions(type); + } } CPVRTimerType::CPVRTimerType(unsigned int iTypeId, @@ -182,7 +193,8 @@ bool CPVRTimerType::operator==(const CPVRTimerType& right) const m_lifetimeValues == right.m_lifetimeValues && m_maxRecordingsValues == right.m_maxRecordingsValues && m_preventDupEpisodesValues == right.m_preventDupEpisodesValues && - m_recordingGroupValues == right.m_recordingGroupValues); + m_recordingGroupValues == right.m_recordingGroupValues && + m_customSettingDefs == right.m_customSettingDefs); } bool CPVRTimerType::operator!=(const CPVRTimerType& right) const @@ -201,6 +213,7 @@ void CPVRTimerType::Update(const CPVRTimerType& type) m_maxRecordingsValues = type.m_maxRecordingsValues; m_preventDupEpisodesValues = type.m_preventDupEpisodesValues; m_recordingGroupValues = type.m_recordingGroupValues; + m_customSettingDefs = type.m_customSettingDefs; } void CPVRTimerType::InitDescription() @@ -256,7 +269,7 @@ void CPVRTimerType::InitPriorityValues(const PVR_TIMER_TYPE& type) else { // No priority supported. - m_priorityValues = {DEFAULT_RECORDING_PRIORITY}; + m_priorityValues = CPVRIntSettingValues{DEFAULT_RECORDING_PRIORITY}; } } @@ -280,7 +293,7 @@ void CPVRTimerType::InitLifetimeValues(const PVR_TIMER_TYPE& type) else { // No lifetime supported. - m_lifetimeValues = {DEFAULT_RECORDING_LIFETIME}; + m_lifetimeValues = CPVRIntSettingValues{DEFAULT_RECORDING_LIFETIME}; } } @@ -307,7 +320,7 @@ void CPVRTimerType::InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& typ else { // No prevent duplicate episodes supported. - m_preventDupEpisodesValues = {DEFAULT_RECORDING_DUPLICATEHANDLING}; + m_preventDupEpisodesValues = CPVRIntSettingValues{DEFAULT_RECORDING_DUPLICATEHANDLING}; } } @@ -316,3 +329,9 @@ void CPVRTimerType::InitRecordingGroupValues(const PVR_TIMER_TYPE& type) m_recordingGroupValues = {type.recordingGroup, type.iRecordingGroupSize, type.iRecordingGroupDefault, 811 /* Recording group */}; } + +void CPVRTimerType::InitCustomSettingDefinitions(const PVR_TIMER_TYPE& type) +{ + m_customSettingDefs = CPVRTimerSettingDefinition::CreateSettingDefinitionsList( + m_iClientId, m_iTypeId, type.customSettingDefs, type.iCustomSettingDefsSize); +} diff --git a/xbmc/pvr/timers/PVRTimerType.h b/xbmc/pvr/timers/PVRTimerType.h index e2ac4d6c49678..717576a774308 100644 --- a/xbmc/pvr/timers/PVRTimerType.h +++ b/xbmc/pvr/timers/PVRTimerType.h @@ -19,9 +19,15 @@ struct PVR_TIMER_TYPE; +namespace ADDON +{ +class CAddonVersion; +} + namespace PVR { class CPVRClient; +class CPVRTimerSettingDefinition; static const int DEFAULT_RECORDING_PRIORITY = 50; static const int DEFAULT_RECORDING_LIFETIME = 99; // days @@ -64,7 +70,9 @@ class CPVRTimerType int iClientId); CPVRTimerType(); - CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId); + CPVRTimerType(const PVR_TIMER_TYPE& type, + int iClientId, + const ADDON::CAddonVersion& addonApiVersion); CPVRTimerType(unsigned int iTypeId, uint64_t iAttributes, const std::string& strDescription = ""); virtual ~CPVRTimerType(); @@ -461,6 +469,16 @@ class CPVRTimerType */ int GetRecordingGroupDefault() const { return m_recordingGroupValues.GetDefaultValue(); } + /*! + * @brief Get custom setting definitions for this type. + * @return The list of settings or an empty list if none present. + */ + const std::vector<std::shared_ptr<const CPVRTimerSettingDefinition>>& + GetCustomSettingDefinitions() const + { + return m_customSettingDefs; + } + private: void InitDescription(); void InitAttributeValues(const PVR_TIMER_TYPE& type); @@ -469,6 +487,7 @@ class CPVRTimerType void InitMaxRecordingsValues(const PVR_TIMER_TYPE& type); void InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& type); void InitRecordingGroupValues(const PVR_TIMER_TYPE& type); + void InitCustomSettingDefinitions(const PVR_TIMER_TYPE& type); int m_iClientId = PVR_CLIENT_INVALID_UID; unsigned int m_iTypeId; @@ -479,5 +498,6 @@ class CPVRTimerType CPVRIntSettingValues m_maxRecordingsValues{0}; CPVRIntSettingValues m_preventDupEpisodesValues{DEFAULT_RECORDING_DUPLICATEHANDLING}; CPVRIntSettingValues m_recordingGroupValues{0}; + std::vector<std::shared_ptr<const CPVRTimerSettingDefinition>> m_customSettingDefs; }; } // namespace PVR From fb2ae664ac5cce08fca5ac26a433170a87500bc1 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:25:02 +0200 Subject: [PATCH 449/651] [PVR] Timer settings dialog: Fix settings read-only conditions for in-progress recordings. --- xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp index 07f9d221eac90..f314e16bf7412 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp @@ -1365,8 +1365,8 @@ bool CGUIDialogPVRTimerSettings::TypeReadOnlyCondition(const std::string& condit if (pThis->m_timerInfoTag->State() == PVR_TIMER_STATE_RECORDING) { if (cond == SETTING_TMR_TYPE || cond == SETTING_TMR_CHANNEL || cond == SETTING_TMR_BEGIN_PRE || - cond == SETTING_TMR_START_DAY || cond == SETTING_TMR_BEGIN || SETTING_TMR_PRIORITY || - cond == SETTING_TMR_DIR) + cond == SETTING_TMR_START_DAY || cond == SETTING_TMR_BEGIN || + cond == SETTING_TMR_PRIORITY || cond == SETTING_TMR_DIR) return false; } From 45e538ab481a441da0526ed22178aa04c1dca0a7 Mon Sep 17 00:00:00 2001 From: fritsch <Peter.Fruehberger@gmail.com> Date: Fri, 6 Sep 2024 21:54:00 +0200 Subject: [PATCH 450/651] AESinkAudiotrack: Use 192 khz for TrueHD The 48 khz is historically chosen. There is no way, that this bandwidth would be enough to do DTS-HD-MA or TrueHD. With this new check, we would not list that format anymore - as in the past - it auto fellback to normal DTS and on some machines failed for TrueHD directly. Packers of FireTV only pack DTS-HD-Core, so we keep it on 48 khz here. See also: https://forum.kodi.tv/showthread.php?tid=378616 --- .../AudioEngine/Sinks/AESinkAUDIOTRACK.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp index e0a42c62a437b..149bc7a2681b5 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp @@ -329,12 +329,19 @@ bool CAESinkAUDIOTRACK::Initialize(AEAudioFormat &format, std::string &device) m_encoding = AEStreamFormatToATFormat(m_format.m_streamInfo.m_type); m_format.m_channelLayout = AE_CH_LAYOUT_2_0; - if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD_MA || - m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD) + if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD_MA) { + // we keep the 48 khz sample rate, reason: Androids packer only packs DTS Core + // even if we ask for DTS-HD-MA it seems. m_format.m_channelLayout = AE_CH_LAYOUT_7_1; } + if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD) + { + m_format.m_channelLayout = AE_CH_LAYOUT_7_1; + m_sink_sampleRate = 192000; + } + // EAC3 needs real samplerate not the modulation if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_EAC3) m_sink_sampleRate = m_format.m_streamInfo.m_sampleRate; @@ -1073,6 +1080,8 @@ void CAESinkAUDIOTRACK::UpdateAvailablePassthroughCapabilities(bool isRaw) m_info.m_wantsIECPassthrough = false; m_info.m_dataFormats.push_back(AE_FMT_RAW); m_info.m_streamTypes.clear(); + bool supports_192khz = m_sink_sampleRates.find(192000) != m_sink_sampleRates.end(); + if (isRaw) { bool canDoAC3 = false; @@ -1126,9 +1135,9 @@ void CAESinkAUDIOTRACK::UpdateAvailablePassthroughCapabilities(bool isRaw) m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA); } } - if (CJNIAudioFormat::ENCODING_DOLBY_TRUEHD != -1) + if (CJNIAudioFormat::ENCODING_DOLBY_TRUEHD != -1 && supports_192khz) { - if (VerifySinkConfiguration(48000, AEChannelMapToAUDIOTRACKChannelMask(AE_CH_LAYOUT_7_1), + if (VerifySinkConfiguration(192000, AEChannelMapToAUDIOTRACKChannelMask(AE_CH_LAYOUT_7_1), CJNIAudioFormat::ENCODING_DOLBY_TRUEHD, true)) { CLog::Log(LOGDEBUG, "Firmware implements TrueHD RAW"); @@ -1146,7 +1155,6 @@ void CAESinkAUDIOTRACK::UpdateAvailablePassthroughCapabilities(bool isRaw) CJNIAudioFormat::ENCODING_IEC61937); if (supports_iec) { - bool supports_192khz = m_sink_sampleRates.find(192000) != m_sink_sampleRates.end(); m_info.m_wantsIECPassthrough = true; m_info.m_streamTypes.clear(); m_info.m_dataFormats.push_back(AE_FMT_RAW); From 2defdf63bcd9508eb9627c5d9c2dc876557f9a80 Mon Sep 17 00:00:00 2001 From: sarbes <sarbes@kodi.tv> Date: Sat, 7 Sep 2024 18:55:39 +0200 Subject: [PATCH 451/651] GLES: Implement fast HQ scalers (#24611) --- system/shaders/GLES/3.1/gles310_yuv2rgb.vert | 28 ++++ .../GLES/3.1/gles310_yuv2rgb_filter.frag | 129 ++++++++++++++++++ .../VideoRenderers/LinuxRendererGLES.cpp | 34 ++++- .../VideoShaders/YUV2RGBShaderGLES.cpp | 71 +++++++++- .../VideoShaders/YUV2RGBShaderGLES.h | 22 ++- xbmc/rendering/gles/RenderSystemGLES.cpp | 19 ++- xbmc/rendering/gles/RenderSystemGLES.h | 4 +- 7 files changed, 297 insertions(+), 10 deletions(-) create mode 100644 system/shaders/GLES/3.1/gles310_yuv2rgb.vert create mode 100644 system/shaders/GLES/3.1/gles310_yuv2rgb_filter.frag diff --git a/system/shaders/GLES/3.1/gles310_yuv2rgb.vert b/system/shaders/GLES/3.1/gles310_yuv2rgb.vert new file mode 100644 index 0000000000000..7f4dcdf64923d --- /dev/null +++ b/system/shaders/GLES/3.1/gles310_yuv2rgb.vert @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 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. + */ + +#version 310 es + +in vec4 m_attrpos; +in vec2 m_attrcordY; +in vec2 m_attrcordU; +in vec2 m_attrcordV; +out vec2 m_cordY; +out vec2 m_cordU; +out vec2 m_cordV; +uniform mat4 m_proj; +uniform mat4 m_model; + +void main() +{ + mat4 mvp = m_proj * m_model; + gl_Position = mvp * m_attrpos; + m_cordY = m_attrcordY; + m_cordU = m_attrcordU; + m_cordV = m_attrcordV; +} diff --git a/system/shaders/GLES/3.1/gles310_yuv2rgb_filter.frag b/system/shaders/GLES/3.1/gles310_yuv2rgb_filter.frag new file mode 100644 index 0000000000000..cabd56e3a2c45 --- /dev/null +++ b/system/shaders/GLES/3.1/gles310_yuv2rgb_filter.frag @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 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. + */ + +#version 310 es + +precision highp float; + +uniform sampler2D m_sampY; +uniform sampler2D m_sampU; +uniform sampler2D m_sampV; +uniform vec2 m_step; +uniform mat4 m_yuvmat; +uniform float m_stretch; +uniform float m_alpha; +uniform sampler2D m_kernelTex; +uniform mat3 m_primMat; +uniform float m_gammaDstInv; +uniform float m_gammaSrc; +uniform float m_toneP1; +uniform float m_luminance; +uniform vec3 m_coefsDst; +in vec2 m_cordY; +in vec2 m_cordU; +in vec2 m_cordV; +out vec4 fragColor; + +vec4[4] load4x4_0(sampler2D sampler, vec2 pos) +{ + vec4[4] tex4x4; + vec4 tex2x2 = textureGather(sampler, pos, 0); + tex4x4[0].xy = tex2x2.wz; + tex4x4[1].xy = tex2x2.xy; + tex2x2 = textureGatherOffset(sampler, pos, ivec2(2,0), 0); + tex4x4[0].zw = tex2x2.wz; + tex4x4[1].zw = tex2x2.xy; + tex2x2 = textureGatherOffset(sampler, pos, ivec2(0,2), 0); + tex4x4[2].xy = tex2x2.wz; + tex4x4[3].xy = tex2x2.xy; + tex2x2 = textureGatherOffset(sampler, pos, ivec2(2,2), 0); + tex4x4[2].zw = tex2x2.wz; + tex4x4[3].zw = tex2x2.xy; + return tex4x4; +} + +float filter_0(sampler2D sampler, vec2 coord) +{ + vec2 pos = coord + m_step * 0.5; + vec2 f = fract(pos / m_step); + + vec4 linetaps = texture(m_kernelTex, vec2(1.0 - f.x, 0.)); + vec4 coltaps = texture(m_kernelTex, vec2(1.0 - f.y, 0.)); + linetaps /= linetaps.r + linetaps.g + linetaps.b + linetaps.a; + coltaps /= coltaps.r + coltaps.g + coltaps.b + coltaps.a; + mat4 conv; + conv[0] = linetaps * coltaps.x; + conv[1] = linetaps * coltaps.y; + conv[2] = linetaps * coltaps.z; + conv[3] = linetaps * coltaps.w; + + vec2 startPos = (-1.0 - f) * m_step + pos; + vec4[4] tex4x4 = load4x4_0(sampler, startPos); + vec4 imageLine0 = tex4x4[0]; + vec4 imageLine1 = tex4x4[1]; + vec4 imageLine2 = tex4x4[2]; + vec4 imageLine3 = tex4x4[3]; + + return dot(imageLine0, conv[0]) + + dot(imageLine1, conv[1]) + + dot(imageLine2, conv[2]) + + dot(imageLine3, conv[3]); +} + +void main() +{ + vec4 rgb; + vec4 yuv; + +#if defined(XBMC_YV12) || defined(XBMC_NV12) + + yuv = vec4(filter_0(m_sampY, m_cordY), + texture2D(m_sampU, m_cordU).g, + texture2D(m_sampV, m_cordV).a, + 1.0); + +#elif defined(XBMC_NV12_RRG) + + yuv = vec4(filter_0(m_sampY, m_cordY), + texture2D(m_sampU, m_cordU).r, + texture2D(m_sampV, m_cordV).g, + 1.0); + +#endif + + rgb = m_yuvmat * yuv; + rgb.a = m_alpha; + +#if defined(XBMC_COL_CONVERSION) + rgb.rgb = pow(max(vec3(0), rgb.rgb), vec3(m_gammaSrc)); + rgb.rgb = max(vec3(0), m_primMat * rgb.rgb); + rgb.rgb = pow(rgb.rgb, vec3(m_gammaDstInv)); + +#if defined(KODI_TONE_MAPPING_REINHARD) + float luma = dot(rgb.rgb, m_coefsDst); + rgb.rgb *= reinhard(luma) / luma; + +#elif defined(KODI_TONE_MAPPING_ACES) + rgb.rgb = inversePQ(rgb.rgb); + rgb.rgb *= (10000.0 / m_luminance) * (2.0 / m_toneP1); + rgb.rgb = aces(rgb.rgb); + rgb.rgb *= (1.24 / m_toneP1); + rgb.rgb = pow(rgb.rgb, vec3(0.27)); + +#elif defined(KODI_TONE_MAPPING_HABLE) + rgb.rgb = inversePQ(rgb.rgb); + rgb.rgb *= m_toneP1; + float wp = m_luminance / 100.0; + rgb.rgb = hable(rgb.rgb * wp) / hable(vec3(wp)); + rgb.rgb = pow(rgb.rgb, vec3(1.0 / 2.2)); +#endif + +#endif + + fragColor = rgb; +} diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp index 97663f9c09746..67275f920b8e6 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp @@ -584,6 +584,8 @@ void CLinuxRendererGLES::UpdateVideoFilter() } m_scalingMethodGui = m_videoSettings.m_ScalingMethod; + if (m_scalingMethod != m_scalingMethodGui) + m_reloadShaders = true; m_scalingMethod = m_scalingMethodGui; m_viewRect = viewRect; @@ -616,6 +618,8 @@ void CLinuxRendererGLES::UpdateVideoFilter() return; } case VS_SCALINGMETHOD_LINEAR: + case VS_SCALINGMETHOD_LANCZOS3_FAST: + case VS_SCALINGMETHOD_SPLINE36_FAST: { CLog::Log(LOGINFO, "GLES: Selecting single pass rendering"); SetTextureFilter(GL_LINEAR); @@ -623,8 +627,6 @@ void CLinuxRendererGLES::UpdateVideoFilter() return; } case VS_SCALINGMETHOD_LANCZOS2: - case VS_SCALINGMETHOD_SPLINE36_FAST: - case VS_SCALINGMETHOD_LANCZOS3_FAST: case VS_SCALINGMETHOD_SPLINE36: case VS_SCALINGMETHOD_LANCZOS3: case VS_SCALINGMETHOD_CUBIC_B_SPLINE: @@ -709,9 +711,19 @@ void CLinuxRendererGLES::LoadShaders(int field) EShaderFormat shaderFormat = GetShaderFormat(); m_toneMapMethod = m_videoSettings.m_ToneMapMethod; - m_pYUVProgShader = new YUV2RGBProgressiveShader( - shaderFormat, m_passthroughHDR ? m_srcPrimaries : AVColorPrimaries::AVCOL_PRI_BT709, - m_srcPrimaries, m_toneMap, m_toneMapMethod); + if (m_scalingMethod == VS_SCALINGMETHOD_LANCZOS3_FAST || + m_scalingMethod == VS_SCALINGMETHOD_SPLINE36_FAST) + { + m_pYUVProgShader = new YUV2RGBFilterShader( + shaderFormat, m_passthroughHDR ? m_srcPrimaries : AVColorPrimaries::AVCOL_PRI_BT709, + m_srcPrimaries, m_toneMap, m_toneMapMethod, m_scalingMethod); + } + else + { + m_pYUVProgShader = new YUV2RGBProgressiveShader( + shaderFormat, m_passthroughHDR ? m_srcPrimaries : AVColorPrimaries::AVCOL_PRI_BT709, + m_srcPrimaries, m_toneMap, m_toneMapMethod); + } m_pYUVProgShader->SetConvertFullColorRange(m_fullRange); m_pYUVBobShader = new YUV2RGBBobShader( shaderFormat, m_passthroughHDR ? m_srcPrimaries : AVColorPrimaries::AVCOL_PRI_BT709, @@ -1790,6 +1802,18 @@ bool CLinuxRendererGLES::Supports(ESCALINGMETHOD method) const method == VS_SCALINGMETHOD_SPLINE36 || method == VS_SCALINGMETHOD_LANCZOS3) { + if (method == VS_SCALINGMETHOD_SPLINE36_FAST || method == VS_SCALINGMETHOD_LANCZOS3_FAST) + { +#if defined(GL_ES_VERSION_3_0) + // we need GLES 3.0 headers for GL_RGBA16f, but GLES 3.1 for the shader + uint32_t major, minor; + m_renderSystem->GetRenderVersion(major, minor); + if (major < 3 || minor == 0) + return false; +#else + return false; +#endif + } // if scaling is below level, avoid hq scaling float scaleX = fabs((static_cast<float>(m_sourceWidth) - m_destRect.Width()) / m_sourceWidth) * 100; float scaleY = fabs((static_cast<float>(m_sourceHeight) - m_destRect.Height()) / m_sourceHeight) * 100; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.cpp index 63e4d304a4a9a..5f4e63090a317 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2007 d4rk - * Copyright (C) 2007-2018 Team Kodi + * Copyright (C) 2007-2024 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later @@ -10,6 +10,7 @@ #include "YUV2RGBShaderGLES.h" #include "../RenderFlags.h" +#include "ConvolutionKernels.h" #include "ToneMappers.h" #include "settings/AdvancedSettings.h" #include "utils/GLUtils.h" @@ -267,3 +268,71 @@ bool YUV2RGBBobShader::OnEnabled() VerifyGLState(); return true; } + +//------------------------------------------------------------------------------ +// YUV2RGBFilterShader +//------------------------------------------------------------------------------ + +YUV2RGBFilterShader::YUV2RGBFilterShader(EShaderFormat format, + AVColorPrimaries dstPrimaries, + AVColorPrimaries srcPrimaries, + bool toneMap, + ETONEMAPMETHOD toneMapMethod, + ESCALINGMETHOD method) + : BaseYUV2RGBGLSLShader(format, dstPrimaries, srcPrimaries, toneMap, toneMapMethod) +{ + m_scaling = method; + PixelShader()->LoadSource("gles310_yuv2rgb_filter.frag", m_defines); + VertexShader()->LoadSource("gles310_yuv2rgb.vert"); + PixelShader()->AppendSource("gl_output.glsl"); + + PixelShader()->InsertSource("gl_tonemap.glsl", "void main()"); +} + +YUV2RGBFilterShader::~YUV2RGBFilterShader() +{ + if (m_kernelTex) + glDeleteTextures(1, &m_kernelTex); + m_kernelTex = 0; +} + +void YUV2RGBFilterShader::OnCompiledAndLinked() +{ + BaseYUV2RGBGLSLShader::OnCompiledAndLinked(); + m_hKernTex = glGetUniformLocation(ProgramHandle(), "m_kernelTex"); + + if (m_scaling != VS_SCALINGMETHOD_LANCZOS3_FAST && m_scaling != VS_SCALINGMETHOD_SPLINE36_FAST) + m_scaling = VS_SCALINGMETHOD_LANCZOS3_FAST; + + CConvolutionKernel kernel(m_scaling, 256); + + if (m_kernelTex) + { + glDeleteTextures(1, &m_kernelTex); + m_kernelTex = 0; + } + glGenTextures(1, &m_kernelTex); + + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_2D, m_kernelTex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + + GLvoid* data = (GLvoid*)kernel.GetFloatPixels(); +#if defined(GL_ES_VERSION_3_0) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, kernel.GetSize(), 1, 0, GL_RGBA, GL_FLOAT, data); +#endif + glActiveTexture(GL_TEXTURE0); + VerifyGLState(); +} + +bool YUV2RGBFilterShader::OnEnabled() +{ + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_2D, m_kernelTex); + glUniform1i(m_hKernTex, 3); + glActiveTexture(GL_TEXTURE0); + + return BaseYUV2RGBGLSLShader::OnEnabled(); +} diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.h index 917f0f35f49a2..75bf05f3254b4 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.h +++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2018 Team Kodi + * Copyright (C) 2007-2024 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later @@ -135,5 +135,25 @@ class BaseYUV2RGBGLSLShader : public CGLSLShaderProgram GLint m_hField = -1; }; + class YUV2RGBFilterShader : public BaseYUV2RGBGLSLShader + { + public: + YUV2RGBFilterShader(EShaderFormat format, + AVColorPrimaries dstPrimaries, + AVColorPrimaries srcPrimaries, + bool toneMap, + ETONEMAPMETHOD toneMapMethod, + ESCALINGMETHOD method); + ~YUV2RGBFilterShader() override; + + protected: + void OnCompiledAndLinked() override; + bool OnEnabled() override; + + GLuint m_kernelTex = 0; + GLint m_hKernTex = -1; + ESCALINGMETHOD m_scaling = VS_SCALINGMETHOD_LANCZOS3_FAST; + }; + } // namespace GLES } // end namespace diff --git a/xbmc/rendering/gles/RenderSystemGLES.cpp b/xbmc/rendering/gles/RenderSystemGLES.cpp index 471938a7142b0..ef2261ff186b1 100644 --- a/xbmc/rendering/gles/RenderSystemGLES.cpp +++ b/xbmc/rendering/gles/RenderSystemGLES.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2018 Team Kodi + * Copyright (C) 2005-2024 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later @@ -8,11 +8,13 @@ #include "RenderSystemGLES.h" +#include "URL.h" #include "guilib/DirtyRegion.h" #include "guilib/GUITextureGLES.h" #include "rendering/MatrixGL.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" +#include "utils/FileUtils.h" #include "utils/GLUtils.h" #include "utils/MathUtils.h" #include "utils/SystemInfo.h" @@ -774,3 +776,18 @@ GLint CRenderSystemGLES::GUIShaderGetCoordStep() return -1; } + +std::string CRenderSystemGLES::GetShaderPath(const std::string& filename) +{ + std::string path = "GLES/2.0/"; + + if (m_RenderVersionMajor >= 3 && m_RenderVersionMinor >= 1) + { + std::string file = "special://xbmc/system/shaders/GLES/3.1/" + filename; + const CURL pathToUrl(file); + if (CFileUtils::Exists(pathToUrl.Get())) + return "GLES/3.1/"; + } + + return path; +} diff --git a/xbmc/rendering/gles/RenderSystemGLES.h b/xbmc/rendering/gles/RenderSystemGLES.h index 9c19bf6c28be9..1ea0ea60a5c02 100644 --- a/xbmc/rendering/gles/RenderSystemGLES.h +++ b/xbmc/rendering/gles/RenderSystemGLES.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2018 Team Kodi + * Copyright (C) 2005-2024 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later @@ -111,7 +111,7 @@ class CRenderSystemGLES : public CRenderSystemBase void Project(float &x, float &y, float &z) override; - std::string GetShaderPath(const std::string &filename) override { return "GLES/2.0/"; } + std::string GetShaderPath(const std::string& filename) override; void InitialiseShaders(); void ReleaseShaders(); From a1f0b04123018c4f2a7533fa7311baf9d8722088 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving <spiff@kodi.tv> Date: Tue, 23 Jul 2024 13:46:28 +0200 Subject: [PATCH 452/651] changed: move CFileItem::FillInDefaultIcon to ArtUtils --- xbmc/FileItem.cpp | 130 +------------------------- xbmc/FileItem.h | 1 - xbmc/FileItemList.cpp | 3 +- xbmc/GUIInfoManager.cpp | 5 +- xbmc/filesystem/ZeroconfDirectory.cpp | 6 +- xbmc/pictures/PictureThumbLoader.cpp | 5 +- xbmc/utils/ArtUtils.cpp | 123 ++++++++++++++++++++++++ xbmc/utils/ArtUtils.h | 3 + xbmc/utils/test/TestArtUtils.cpp | 51 ++++++++++ 9 files changed, 192 insertions(+), 135 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 7de2c22794514..bf3a4d6f6a058 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -116,7 +116,7 @@ CFileItem::CFileItem(const CMusicInfoTag& music) m_strPath = music.GetURL(); m_bIsFolder = URIUtils::HasSlashAtEnd(m_strPath); *GetMusicInfoTag() = music; - FillInDefaultIcon(); + ART::FillInDefaultIcon(*this); FillInMimeType(false); } @@ -1220,130 +1220,6 @@ bool CFileItem::IsReadOnly() const return !CUtil::SupportsWriteFileOperations(m_strPath); } -void CFileItem::FillInDefaultIcon() -{ - if (URIUtils::IsPVRGuideItem(m_strPath)) - { - // epg items never have a default icon. no need to execute this expensive method. - // when filling epg grid window, easily tens of thousands of epg items are processed. - return; - } - - //CLog::Log(LOGINFO, "FillInDefaultIcon({})", pItem->GetLabel()); - // find the default icon for a file or folder item - // for files this can be the (depending on the file type) - // default picture for photo's - // default picture for songs - // default picture for videos - // default picture for shortcuts - // default picture for playlists - // - // for folders - // for .. folders the default picture for parent folder - // for other folders the defaultFolder.png - - if (GetArt("icon").empty()) - { - if (!m_bIsFolder) - { - /* To reduce the average runtime of this code, this list should - * be ordered with most frequently seen types first. Also bear - * in mind the complexity of the code behind the check in the - * case of IsWhatever() returns false. - */ - if (IsPVRChannel()) - { - if (GetPVRChannelInfoTag()->IsRadio()) - SetArt("icon", "DefaultMusicSongs.png"); - else - SetArt("icon", "DefaultTVShows.png"); - } - else if ( IsLiveTV() ) - { - // Live TV Channel - SetArt("icon", "DefaultTVShows.png"); - } - else if ( URIUtils::IsArchive(m_strPath) ) - { // archive - SetArt("icon", "DefaultFile.png"); - } - else if ( IsUsablePVRRecording() ) - { - // PVR recording - SetArt("icon", "DefaultVideo.png"); - } - else if ( IsDeletedPVRRecording() ) - { - // PVR deleted recording - SetArt("icon", "DefaultVideoDeleted.png"); - } - else if (IsPVRProvider()) - { - SetArt("icon", "DefaultPVRProvider.png"); - } - else if (PLAYLIST::IsPlayList(*this) || PLAYLIST::IsSmartPlayList(*this)) - { - SetArt("icon", "DefaultPlaylist.png"); - } - else if (MUSIC::IsAudio(*this)) - { - // audio - SetArt("icon", "DefaultAudio.png"); - } - else if (VIDEO::IsVideo(*this)) - { - // video - SetArt("icon", "DefaultVideo.png"); - } - else if (IsPVRTimer()) - { - SetArt("icon", "DefaultVideo.png"); - } - else if ( IsPicture() ) - { - // picture - SetArt("icon", "DefaultPicture.png"); - } - else if ( IsPythonScript() ) - { - SetArt("icon", "DefaultScript.png"); - } - else if (IsFavourite()) - { - SetArt("icon", "DefaultFavourites.png"); - } - else - { - // default icon for unknown file type - SetArt("icon", "DefaultFile.png"); - } - } - else - { - if (PLAYLIST::IsPlayList(*this) || PLAYLIST::IsSmartPlayList(*this)) - { - SetArt("icon", "DefaultPlaylist.png"); - } - else if (IsParentFolder()) - { - SetArt("icon", "DefaultFolderBack.png"); - } - else - { - SetArt("icon", "DefaultFolder.png"); - } - } - } - // Set the icon overlays (if applicable) - if (!HasOverlay() && !HasProperty("icon_never_overlay")) - { - if (URIUtils::IsInRAR(m_strPath)) - SetOverlayImage(CGUIListItem::ICON_OVERLAY_RAR); - else if (URIUtils::IsInZIP(m_strPath)) - SetOverlayImage(CGUIListItem::ICON_OVERLAY_ZIP); - } -} - void CFileItem::RemoveExtension() { if (m_bIsFolder) @@ -1726,7 +1602,7 @@ void CFileItem::SetFromVideoInfoTag(const CVideoInfoTag &video) if (video.m_iSeason == 0) SetProperty("isspecial", "true"); - FillInDefaultIcon(); + ART::FillInDefaultIcon(*this); FillInMimeType(false); } @@ -1794,7 +1670,7 @@ void CFileItem::SetFromMusicInfoTag(const MUSIC_INFO::CMusicInfoTag& music) SetArt("thumb", thumb.GetValueToSave(GetArt("thumb"))); *GetMusicInfoTag() = music; - FillInDefaultIcon(); + ART::FillInDefaultIcon(*this); FillInMimeType(false); } diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index 15f7f50f04316..239f6ffe23c04 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -226,7 +226,6 @@ class CFileItem : void RemoveExtension(); void CleanString(); - void FillInDefaultIcon(); void SetFileSizeLabel(); void SetLabel(const std::string &strLabel) override; VideoDbContentType GetVideoContentType() const; diff --git a/xbmc/FileItemList.cpp b/xbmc/FileItemList.cpp index cb8e918001476..5f9bfbb8dd646 100644 --- a/xbmc/FileItemList.cpp +++ b/xbmc/FileItemList.cpp @@ -23,6 +23,7 @@ #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/Archive.h" +#include "utils/ArtUtils.h" #include "utils/Crc32.h" #include "utils/FileExtensionProvider.h" #include "utils/Random.h" @@ -545,7 +546,7 @@ void CFileItemList::FillInDefaultIcons() for (int i = 0; i < (int)m_items.size(); ++i) { CFileItemPtr pItem = m_items[i]; - pItem->FillInDefaultIcon(); + ART::FillInDefaultIcon(*pItem); } } diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index b7cd78b54cbaa..4bfc07d3d138c 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -26,6 +26,7 @@ #include "messaging/ApplicationMessenger.h" #include "playlists/PlayListTypes.h" #include "settings/SkinSettings.h" +#include "utils/ArtUtils.h" #include "utils/CharsetConverter.h" #include "utils/FileUtils.h" #include "utils/StringUtils.h" @@ -11313,7 +11314,7 @@ void CGUIInfoManager::UpdateCurrentItem(const CFileItem &item) void CGUIInfoManager::SetCurrentItem(const CFileItem &item) { *m_currentFile = item; - m_currentFile->FillInDefaultIcon(); + ART::FillInDefaultIcon(*m_currentFile); m_infoProviders.InitCurrentItem(m_currentFile); @@ -11327,7 +11328,7 @@ void CGUIInfoManager::SetCurrentAlbumThumb(const std::string &thumbFileName) else { m_currentFile->SetArt("thumb", ""); - m_currentFile->FillInDefaultIcon(); + ART::FillInDefaultIcon(*m_currentFile); } } diff --git a/xbmc/filesystem/ZeroconfDirectory.cpp b/xbmc/filesystem/ZeroconfDirectory.cpp index d05184e394bdf..209f5f37b045a 100644 --- a/xbmc/filesystem/ZeroconfDirectory.cpp +++ b/xbmc/filesystem/ZeroconfDirectory.cpp @@ -13,12 +13,14 @@ #include "FileItemList.h" #include "URL.h" #include "network/ZeroconfBrowser.h" +#include "utils/ArtUtils.h" #include "utils/URIUtils.h" #include "utils/log.h" #include <cassert> #include <stdexcept> +using namespace KODI; using namespace XFILE; CZeroconfDirectory::CZeroconfDirectory() @@ -137,7 +139,7 @@ bool GetDirectoryFromTxtRecords(const CZeroconfBrowser::ZeroconfService& zerocon item->SetLabelPreformatted(true); //just set the default folder icon - item->FillInDefaultIcon(); + ART::FillInDefaultIcon(*item); item->m_bIsShareOrDrive=true; items.Add(item); ret = true; @@ -173,7 +175,7 @@ bool CZeroconfDirectory::GetDirectory(const CURL& url, CFileItemList &items) item->SetLabel(it.GetName() + " (" + protocol + ")"); item->SetLabelPreformatted(true); //just set the default folder icon - item->FillInDefaultIcon(); + ART::FillInDefaultIcon(*item); items.Add(item); } } diff --git a/xbmc/pictures/PictureThumbLoader.cpp b/xbmc/pictures/PictureThumbLoader.cpp index d26c7bab38061..bdbc020347b92 100644 --- a/xbmc/pictures/PictureThumbLoader.cpp +++ b/xbmc/pictures/PictureThumbLoader.cpp @@ -23,6 +23,7 @@ #include "settings/AdvancedSettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" +#include "utils/ArtUtils.h" #include "utils/FileExtensionProvider.h" #include "utils/FileUtils.h" #include "utils/URIUtils.h" @@ -100,7 +101,7 @@ bool CPictureThumbLoader::LoadItemCached(CFileItem* pItem) CServiceBroker::GetTextureCache()->BackgroundCacheImage(thumb); pItem->SetArt("thumb", thumb); } - pItem->FillInDefaultIcon(); + ART::FillInDefaultIcon(*pItem); return true; } @@ -215,6 +216,6 @@ void CPictureThumbLoader::ProcessFoldersAndArchives(CFileItem *pItem) } } // refill in the icon to get it to update - pItem->FillInDefaultIcon(); + ART::FillInDefaultIcon(*pItem); } } diff --git a/xbmc/utils/ArtUtils.cpp b/xbmc/utils/ArtUtils.cpp index 80f09c3e8e9f7..9223ca76b57a7 100644 --- a/xbmc/utils/ArtUtils.cpp +++ b/xbmc/utils/ArtUtils.cpp @@ -14,7 +14,10 @@ #include "filesystem/Directory.h" #include "filesystem/File.h" #include "filesystem/StackDirectory.h" +#include "music/MusicFileItemClassify.h" #include "network/NetworkFileItemClassify.h" +#include "playlists/PlayListFileItemClassify.h" +#include "pvr/channels/PVRChannel.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" #include "utils/FileExtensionProvider.h" @@ -28,6 +31,126 @@ using namespace XFILE; namespace KODI::ART { +void FillInDefaultIcon(CFileItem& item) +{ + if (URIUtils::IsPVRGuideItem(item.GetPath())) + { + // epg items never have a default icon. no need to execute this expensive method. + // when filling epg grid window, easily tens of thousands of epg items are processed. + return; + } + + // find the default icon for a file or folder item + // for files this can be the (depending on the file type) + // default picture for photo's + // default picture for songs + // default picture for videos + // default picture for shortcuts + // default picture for playlists + // + // for folders + // for .. folders the default picture for parent folder + // for other folders the defaultFolder.png + + if (item.GetArt("icon").empty()) + { + if (!item.m_bIsFolder) + { + /* To reduce the average runtime of this code, this list should + * be ordered with most frequently seen types first. Also bear + * in mind the complexity of the code behind the check in the + * case of IsWhatever() returns false. + * @todo get rid of PVR compile time dependency + */ + if (item.IsPVRChannel()) + { + if (item.GetPVRChannelInfoTag()->IsRadio()) + item.SetArt("icon", "DefaultMusicSongs.png"); + else + item.SetArt("icon", "DefaultTVShows.png"); + } + else if (item.IsLiveTV()) + { + // Live TV Channel + item.SetArt("icon", "DefaultTVShows.png"); + } + else if (URIUtils::IsArchive(item.GetPath())) + { // archive + item.SetArt("icon", "DefaultFile.png"); + } + else if (item.IsUsablePVRRecording()) + { + // PVR recording + item.SetArt("icon", "DefaultVideo.png"); + } + else if (item.IsDeletedPVRRecording()) + { + // PVR deleted recording + item.SetArt("icon", "DefaultVideoDeleted.png"); + } + else if (PLAYLIST::IsPlayList(item) || PLAYLIST::IsSmartPlayList(item)) + { + item.SetArt("icon", "DefaultPlaylist.png"); + } + else if (MUSIC::IsAudio(item)) + { + // audio + item.SetArt("icon", "DefaultAudio.png"); + } + else if (VIDEO::IsVideo(item)) + { + // video + item.SetArt("icon", "DefaultVideo.png"); + } + else if (item.IsPVRTimer()) + { + item.SetArt("icon", "DefaultVideo.png"); + } + else if (item.IsPicture()) + { + // picture + item.SetArt("icon", "DefaultPicture.png"); + } + else if (item.IsPythonScript()) + { + item.SetArt("icon", "DefaultScript.png"); + } + else if (item.IsFavourite()) + { + item.SetArt("icon", "DefaultFavourites.png"); + } + else + { + // default icon for unknown file type + item.SetArt("icon", "DefaultFile.png"); + } + } + else + { + if (PLAYLIST::IsPlayList(item) || PLAYLIST::IsSmartPlayList(item)) + { + item.SetArt("icon", "DefaultPlaylist.png"); + } + else if (item.IsParentFolder()) + { + item.SetArt("icon", "DefaultFolderBack.png"); + } + else + { + item.SetArt("icon", "DefaultFolder.png"); + } + } + } + // Set the icon overlays (if applicable) + if (!item.HasOverlay() && !item.HasProperty("icon_never_overlay")) + { + if (URIUtils::IsInRAR(item.GetPath())) + item.SetOverlayImage(CGUIListItem::ICON_OVERLAY_RAR); + else if (URIUtils::IsInZIP(item.GetPath())) + item.SetOverlayImage(CGUIListItem::ICON_OVERLAY_ZIP); + } +} + std::string GetLocalFanart(const CFileItem& item) { if (VIDEO::IsVideoDb(item)) diff --git a/xbmc/utils/ArtUtils.h b/xbmc/utils/ArtUtils.h index 564b42b056469..8ae0575e0c788 100644 --- a/xbmc/utils/ArtUtils.h +++ b/xbmc/utils/ArtUtils.h @@ -15,6 +15,9 @@ class CFileItem; namespace KODI::ART { +//! \brief Set default icon for item. +void FillInDefaultIcon(CFileItem& item); + /*! \brief Get the local fanart for item if it exists \return path to the local fanart for this item, or empty if none exists diff --git a/xbmc/utils/test/TestArtUtils.cpp b/xbmc/utils/test/TestArtUtils.cpp index 324836b233b09..99b9b4fbc2861 100644 --- a/xbmc/utils/test/TestArtUtils.cpp +++ b/xbmc/utils/test/TestArtUtils.cpp @@ -76,6 +76,42 @@ const auto local_fanart_tests = std::array{ FanartTest{"videodb://movies/1", "foo-fanart.jpg"}, }; +struct IconTest +{ + std::string path; + std::string icon; + std::string overlay{}; + bool isFolder = false; + bool valid = true; + bool no_overlay = false; +}; + +class FillInDefaultIconTest : public testing::WithParamInterface<IconTest>, public testing::Test +{ +}; + +const auto icon_tests = std::array{ + IconTest{"pvr://guide", "", "", false, false}, + IconTest{"/home/user/test.pvr", "DefaultTVShows.png"}, + IconTest{"/home/user/test.zip", "DefaultFile.png"}, + IconTest{"/home/user/test.mp3", "DefaultAudio.png"}, + IconTest{"/home/user/test.avi", "DefaultVideo.png"}, + IconTest{"/home/user/test.jpg", "DefaultPicture.png"}, + IconTest{"/home/user/test.m3u", "DefaultPlaylist.png"}, + IconTest{"/home/user/test.xsp", "DefaultPlaylist.png"}, + IconTest{"/home/user/test.py", "DefaultScript.png"}, + IconTest{"favourites://1", "DefaultFavourites.png"}, + IconTest{"/home/user/test.fil", "DefaultFile.png"}, + IconTest{"/home/user/test.m3u", "DefaultPlaylist.png", "", true}, + IconTest{"/home/user/test.xsp", "DefaultPlaylist.png", "", true}, + IconTest{"..", "DefaultFolderBack.png", "", true}, + IconTest{"/home/user/test/", "DefaultFolder.png", "", true}, + IconTest{"zip://%2fhome%2fuser%2fbar.zip/foo.avi", "DefaultVideo.png", "OverlayZIP.png"}, + IconTest{"zip://%2fhome%2fuser%2fbar.zip/foo.avi", "DefaultVideo.png", "", false, true, true}, + IconTest{"rar://%2fhome%2fuser%2fbar.rar/foo.avi", "DefaultVideo.png", "OverlayRAR.png"}, + IconTest{"rar://%2fhome%2fuser%2fbar.rar/foo.avi", "DefaultVideo.png", "", false, true, true}, +}; + struct TbnTest { std::string path; @@ -89,6 +125,21 @@ class GetTbnTest : public testing::WithParamInterface<TbnTest>, public testing:: } // namespace +TEST_P(FillInDefaultIconTest, FillInDefaultIcon) +{ + CFileItem item(GetParam().path, GetParam().isFolder); + if (!GetParam().valid) + item.SetArt("icon", "InvalidImage.png"); + item.SetLabel(GetParam().path); + if (GetParam().no_overlay) + item.SetProperty("icon_never_overlay", true); + ART::FillInDefaultIcon(item); + EXPECT_EQ(item.GetArt("icon"), GetParam().valid ? GetParam().icon : "InvalidImage.png"); + EXPECT_EQ(item.GetOverlayImage(), GetParam().overlay); +} + +INSTANTIATE_TEST_SUITE_P(TestArtUtils, FillInDefaultIconTest, testing::ValuesIn(icon_tests)); + TEST_P(GetLocalFanartTest, GetLocalFanart) { std::string path, file_path, uniq; From e9c361ba06bb990df51c4ab40e94f6c2daf75286 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:27:22 +0200 Subject: [PATCH 453/651] [PVR] Some variable naming cleanup after review feedback. --- xbmc/pvr/addons/PVRClientCapabilities.cpp | 8 ++++---- xbmc/pvr/settings/PVRIntSettingValues.cpp | 10 +++++----- xbmc/pvr/settings/PVRStringSettingValues.cpp | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/xbmc/pvr/addons/PVRClientCapabilities.cpp b/xbmc/pvr/addons/PVRClientCapabilities.cpp index a0b1f6cfca4e2..5b5196f22cdc7 100644 --- a/xbmc/pvr/addons/PVRClientCapabilities.cpp +++ b/xbmc/pvr/addons/PVRClientCapabilities.cpp @@ -55,13 +55,13 @@ void CPVRClientCapabilities::InitRecordingsLifetimeValues() { const auto& lifetime{m_addonCapabilities->recordingsLifetimeValues[i]}; const int iValue{lifetime.iValue}; - std::string strDescr{lifetime.strDescription ? lifetime.strDescription : ""}; - if (strDescr.empty()) + std::string description{lifetime.strDescription ? lifetime.strDescription : ""}; + if (description.empty()) { // No description given by addon. Create one from value. - strDescr = std::to_string(iValue); + description = std::to_string(iValue); } - m_recordingsLifetimeValues.emplace_back(strDescr, iValue); + m_recordingsLifetimeValues.emplace_back(description, iValue); } } else if (SupportsRecordingsLifetimeChange()) diff --git a/xbmc/pvr/settings/PVRIntSettingValues.cpp b/xbmc/pvr/settings/PVRIntSettingValues.cpp index 680671badb94f..62a69fcae1c6b 100644 --- a/xbmc/pvr/settings/PVRIntSettingValues.cpp +++ b/xbmc/pvr/settings/PVRIntSettingValues.cpp @@ -29,17 +29,17 @@ CPVRIntSettingValues::CPVRIntSettingValues(struct PVR_ATTRIBUTE_INT_VALUE* value { const int value{values[i].iValue}; const char* desc{values[i].strDescription}; - std::string strDescr{desc ? desc : ""}; - if (strDescr.empty()) + std::string description{desc ? desc : ""}; + if (description.empty()) { // No description given by addon. Create one from value. if (defaultDescriptionResourceId > 0) - strDescr = StringUtils::Format( + description = StringUtils::Format( "{} {}", g_localizeStrings.Get(defaultDescriptionResourceId), value); else - strDescr = std::to_string(value); + description = std::to_string(value); } - m_values.emplace_back(strDescr, value); + m_values.emplace_back(description, value); } } } diff --git a/xbmc/pvr/settings/PVRStringSettingValues.cpp b/xbmc/pvr/settings/PVRStringSettingValues.cpp index ef5c1818270e6..e3227aea954de 100644 --- a/xbmc/pvr/settings/PVRStringSettingValues.cpp +++ b/xbmc/pvr/settings/PVRStringSettingValues.cpp @@ -29,17 +29,17 @@ CPVRStringSettingValues::CPVRStringSettingValues(struct PVR_ATTRIBUTE_STRING_VAL { const std::string value{values[i].strValue ? values[i].strValue : ""}; const char* desc{values[i].strDescription}; - std::string strDescr{desc ? desc : ""}; - if (strDescr.empty()) + std::string description{desc ? desc : ""}; + if (description.empty()) { // No description given by addon. Create one from value. if (defaultDescriptionResourceId > 0) - strDescr = StringUtils::Format( + description = StringUtils::Format( "{} {}", g_localizeStrings.Get(defaultDescriptionResourceId), value); else - strDescr = value; + description = value; } - m_values.emplace_back(strDescr, value); + m_values.emplace_back(description, value); } } } From 042fa92848bda768ed1f78c26a5b48a05dc0faef Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 10 Sep 2024 08:56:46 +0200 Subject: [PATCH 454/651] [utils] KODI::ART::FillInDefaultIcon: Remove PVR compile time dependency. --- xbmc/utils/ArtUtils.cpp | 4 +--- xbmc/utils/URIUtils.cpp | 13 +++++++++++++ xbmc/utils/URIUtils.h | 1 + xbmc/utils/test/TestURIUtils.cpp | 13 +++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/xbmc/utils/ArtUtils.cpp b/xbmc/utils/ArtUtils.cpp index 9223ca76b57a7..55f60d25a0182 100644 --- a/xbmc/utils/ArtUtils.cpp +++ b/xbmc/utils/ArtUtils.cpp @@ -17,7 +17,6 @@ #include "music/MusicFileItemClassify.h" #include "network/NetworkFileItemClassify.h" #include "playlists/PlayListFileItemClassify.h" -#include "pvr/channels/PVRChannel.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" #include "utils/FileExtensionProvider.h" @@ -60,11 +59,10 @@ void FillInDefaultIcon(CFileItem& item) * be ordered with most frequently seen types first. Also bear * in mind the complexity of the code behind the check in the * case of IsWhatever() returns false. - * @todo get rid of PVR compile time dependency */ if (item.IsPVRChannel()) { - if (item.GetPVRChannelInfoTag()->IsRadio()) + if (URIUtils::IsPVRRadioChannel(item.GetPath())) item.SetArt("icon", "DefaultMusicSongs.png"); else item.SetArt("icon", "DefaultTVShows.png"); diff --git a/xbmc/utils/URIUtils.cpp b/xbmc/utils/URIUtils.cpp index 9b862a7f438e3..6ba6a6c6f2589 100644 --- a/xbmc/utils/URIUtils.cpp +++ b/xbmc/utils/URIUtils.cpp @@ -998,6 +998,19 @@ bool URIUtils::IsPVRChannel(const std::string& strFile) return IsProtocol(strFile, "pvr") && CPVRChannelsPath(strFile).IsChannel(); } +bool URIUtils::IsPVRRadioChannel(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsPVRRadioChannel(CStackDirectory::GetFirstStackedFile(strFile)); + + if (IsProtocol(strFile, "pvr")) + { + const CPVRChannelsPath path{strFile}; + return path.IsChannel() && path.IsRadio(); + } + return false; +} + bool URIUtils::IsPVRChannelGroup(const std::string& strFile) { if (IsStack(strFile)) diff --git a/xbmc/utils/URIUtils.h b/xbmc/utils/URIUtils.h index 10f9d3995ed1b..171157ed3d875 100644 --- a/xbmc/utils/URIUtils.h +++ b/xbmc/utils/URIUtils.h @@ -184,6 +184,7 @@ class URIUtils static bool IsLibraryContent(const std::string& strFile); static bool IsPVR(const std::string& strFile); static bool IsPVRChannel(const std::string& strFile); + static bool IsPVRRadioChannel(const std::string& strFile); static bool IsPVRChannelGroup(const std::string& strFile); static bool IsPVRGuideItem(const std::string& strFile); diff --git a/xbmc/utils/test/TestURIUtils.cpp b/xbmc/utils/test/TestURIUtils.cpp index 3c720b49808a5..95b66b639be7b 100644 --- a/xbmc/utils/test/TestURIUtils.cpp +++ b/xbmc/utils/test/TestURIUtils.cpp @@ -322,6 +322,19 @@ TEST_F(TestURIUtils, IsLiveTV) EXPECT_TRUE(URIUtils::IsLiveTV("whatever://path/to/file.pvr")); } +TEST_F(TestURIUtils, IsPVRRadioChannel) +{ + // pvr://channels/(tv|radio)/<groupname>@<clientid>/<instanceid>@<addonid>_<channeluid>.pvr + EXPECT_TRUE( + URIUtils::IsPVRRadioChannel("pvr://channels/radio/groupname@0815/1@pvr.demo_4711.pvr")); + EXPECT_FALSE(URIUtils::IsPVRRadioChannel( + "pvr://channels/tv/groupname@0815/1@pvr.demo_4711.pvr")); // a tv channel + EXPECT_FALSE( + URIUtils::IsPVRRadioChannel("pvr://channels/radio/")); // root folder for all radio channels + EXPECT_FALSE( + URIUtils::IsPVRRadioChannel("pvr://channels/radio/groupname@0815/")); // a radio channel group +} + TEST_F(TestURIUtils, IsMultiPath) { EXPECT_TRUE(URIUtils::IsMultiPath("multipath://path/to/file")); From 72b68b6f75ea835699d6302487991dc0123ff945 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:23:07 +0200 Subject: [PATCH 455/651] [utils] KODI::ART::FillInDefaultIcon: Re-add support for PVR Providers. --- xbmc/utils/ArtUtils.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xbmc/utils/ArtUtils.cpp b/xbmc/utils/ArtUtils.cpp index 55f60d25a0182..b1b6bb306d9d0 100644 --- a/xbmc/utils/ArtUtils.cpp +++ b/xbmc/utils/ArtUtils.cpp @@ -86,6 +86,10 @@ void FillInDefaultIcon(CFileItem& item) // PVR deleted recording item.SetArt("icon", "DefaultVideoDeleted.png"); } + else if (item.IsPVRProvider()) + { + item.SetArt("icon", "DefaultPVRProvider.png"); + } else if (PLAYLIST::IsPlayList(item) || PLAYLIST::IsSmartPlayList(item)) { item.SetArt("icon", "DefaultPlaylist.png"); From 1b670617cff60bce2fd75ec520d7c96b008ffa45 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving <spiff@kodi.tv> Date: Tue, 10 Sep 2024 14:44:53 +0200 Subject: [PATCH 456/651] changed: move CFileItem::GetFolderThumb to ArtUtils --- xbmc/FileItem.cpp | 22 +--------------- xbmc/FileItem.h | 2 -- xbmc/ThumbLoader.cpp | 2 +- xbmc/dialogs/GUIDialogContextMenu.cpp | 3 ++- xbmc/utils/ArtUtils.cpp | 19 ++++++++++++++ xbmc/utils/ArtUtils.h | 10 +++++++- xbmc/utils/test/TestArtUtils.cpp | 36 +++++++++++++++++++++++++++ 7 files changed, 68 insertions(+), 26 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index bf3a4d6f6a058..9efa598f827b8 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -1868,7 +1868,7 @@ std::string CFileItem::GetUserMusicThumb(bool alwaysCheckRemote /* = false */, b for (const auto& i : thumbs) { std::string strFileName = i.asString(); - std::string folderThumb(GetFolderThumb(strFileName)); + std::string folderThumb(ART::GetFolderThumb(*this, strFileName)); if (CFile::Exists(folderThumb)) // folder.jpg return folderThumb; size_t period = strFileName.find_last_of('.'); @@ -2028,26 +2028,6 @@ std::string CFileItem::GetLocalArt(const std::string& artFile, bool useFolder) c return ""; } -std::string CFileItem::GetFolderThumb(const std::string &folderJPG /* = "folder.jpg" */) const -{ - std::string strFolder = m_strPath; - - if (IsStack() || - URIUtils::IsInRAR(strFolder) || - URIUtils::IsInZIP(strFolder)) - { - URIUtils::GetParentPath(m_strPath,strFolder); - } - - if (IsMultiPath()) - strFolder = CMultiPathDirectory::GetFirstPath(m_strPath); - - if (IsPlugin()) - return ""; - - return URIUtils::AddFileToFolder(strFolder, folderJPG); -} - std::string CFileItem::GetMovieName(bool bUseFolderNames /* = false */) const { if (IsPlugin() && HasVideoInfoTag() && !GetVideoInfoTag()->m_strTitle.empty()) diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index 239f6ffe23c04..afaaf47c6a286 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -445,8 +445,6 @@ class CFileItem : */ std::string GetThumbHideIfUnwatched(const CFileItem* item) const; - // Gets the folder image associated with this item (defaults to folder.jpg) - std::string GetFolderThumb(const std::string &folderJPG = "folder.jpg") const; // Gets the correct movie title std::string GetMovieName(bool bUseFolderNames = false) const; diff --git a/xbmc/ThumbLoader.cpp b/xbmc/ThumbLoader.cpp index 40db919d5290f..1dfce68659424 100644 --- a/xbmc/ThumbLoader.cpp +++ b/xbmc/ThumbLoader.cpp @@ -114,7 +114,7 @@ std::string CProgramThumbLoader::GetLocalThumb(const CFileItem &item) // look for the thumb if (item.m_bIsFolder) { - std::string folderThumb = item.GetFolderThumb(); + const std::string folderThumb = ART::GetFolderThumb(item); if (CFileUtils::Exists(folderThumb)) return folderThumb; } diff --git a/xbmc/dialogs/GUIDialogContextMenu.cpp b/xbmc/dialogs/GUIDialogContextMenu.cpp index 6ecf3d11953a8..48c3aafc1bb5c 100644 --- a/xbmc/dialogs/GUIDialogContextMenu.cpp +++ b/xbmc/dialogs/GUIDialogContextMenu.cpp @@ -35,6 +35,7 @@ #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "storage/MediaManager.h" +#include "utils/ArtUtils.h" #include "utils/FileUtils.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" @@ -387,7 +388,7 @@ bool CGUIDialogContextMenu::OnContextButton(const std::string &type, const CFile items.Add(current); } // see if there's a local thumb for this item - std::string folderThumb = item->GetFolderThumb(); + std::string folderThumb = ART::GetFolderThumb(*item); if (CFileUtils::Exists(folderThumb)) { CFileItemPtr local(new CFileItem("thumb://Local", false)); diff --git a/xbmc/utils/ArtUtils.cpp b/xbmc/utils/ArtUtils.cpp index b1b6bb306d9d0..06de8f4bdfed8 100644 --- a/xbmc/utils/ArtUtils.cpp +++ b/xbmc/utils/ArtUtils.cpp @@ -13,6 +13,7 @@ #include "ServiceBroker.h" #include "filesystem/Directory.h" #include "filesystem/File.h" +#include "filesystem/MultiPathDirectory.h" #include "filesystem/StackDirectory.h" #include "music/MusicFileItemClassify.h" #include "network/NetworkFileItemClassify.h" @@ -153,6 +154,24 @@ void FillInDefaultIcon(CFileItem& item) } } +std::string GetFolderThumb(const CFileItem& item, const std::string& folderJPG /* = "folder.jpg" */) +{ + std::string strFolder = item.GetPath(); + + if (item.IsStack() || URIUtils::IsInRAR(strFolder) || URIUtils::IsInZIP(strFolder)) + { + URIUtils::GetParentPath(item.GetPath(), strFolder); + } + + if (item.IsMultiPath()) + strFolder = CMultiPathDirectory::GetFirstPath(item.GetPath()); + + if (item.IsPlugin()) + return ""; + + return URIUtils::AddFileToFolder(strFolder, folderJPG); +} + std::string GetLocalFanart(const CFileItem& item) { if (VIDEO::IsVideoDb(item)) diff --git a/xbmc/utils/ArtUtils.h b/xbmc/utils/ArtUtils.h index 8ae0575e0c788..07671d28dde1c 100644 --- a/xbmc/utils/ArtUtils.h +++ b/xbmc/utils/ArtUtils.h @@ -18,6 +18,14 @@ namespace KODI::ART //! \brief Set default icon for item. void FillInDefaultIcon(CFileItem& item); +/*! + * \brief Get the folder image associated with item. + * \param item Item to get folder image for + * \param folderJPG Thumb file to use + * \return Folder thumb file appropriate for item + */ +std::string GetFolderThumb(const CFileItem& item, const std::string& folderJPG = "folder.jpg"); + /*! \brief Get the local fanart for item if it exists \return path to the local fanart for this item, or empty if none exists @@ -25,7 +33,7 @@ void FillInDefaultIcon(CFileItem& item); */ std::string GetLocalFanart(const CFileItem& item); -// Gets the .tbn file associated with an item +//! \brief Get the .tbn file associated with an item std::string GetTBNFile(const CFileItem& item); } // namespace KODI::ART diff --git a/xbmc/utils/test/TestArtUtils.cpp b/xbmc/utils/test/TestArtUtils.cpp index 99b9b4fbc2861..b2d850ce8027f 100644 --- a/xbmc/utils/test/TestArtUtils.cpp +++ b/xbmc/utils/test/TestArtUtils.cpp @@ -123,6 +123,33 @@ class GetTbnTest : public testing::WithParamInterface<TbnTest>, public testing:: { }; +struct FolderTest +{ + std::string path; + std::string thumb; + std::string result; +}; + +const auto folder_thumb_tests = std::array{ + FolderTest{"c:\\dir\\", "art.jpg", "c:\\dir\\art.jpg"}, + FolderTest{"/home/user/", "folder.jpg", "/home/user/folder.jpg"}, + FolderTest{"plugin://plugin.video.foo/", "folder.jpg", ""}, + FolderTest{"stack:///home/user/bar/foo-cd1.avi , /home/user/bar/foo-cd2.avi", "folder.jpg", + "/home/user/bar/folder.jpg"}, + FolderTest{"stack:///home/user/cd1/foo-cd1.avi , /home/user/cd2/foo-cd2.avi", "artist.jpg", + "/home/user/artist.jpg"}, + FolderTest{"zip://%2fhome%2fuser%2fbar.zip/foo.avi", "cover.png", + "zip://%2fhome%2fuser%2fbar.zip/cover.png"}, + FolderTest{"rar://%2fhome%2fuser%2fbar.rar/foo.avi", "cover.png", + "rar://%2fhome%2fuser%2fbar.rar/cover.png"}, + FolderTest{"multipath://%2fhome%2fuser%2fbar%2f/%2fhome%2fuser%2ffoo%2f", "folder.jpg", + "/home/user/bar/folder.jpg"}, +}; + +class FolderThumbTest : public testing::WithParamInterface<FolderTest>, public testing::Test +{ +}; + } // namespace TEST_P(FillInDefaultIconTest, FillInDefaultIcon) @@ -140,6 +167,15 @@ TEST_P(FillInDefaultIconTest, FillInDefaultIcon) INSTANTIATE_TEST_SUITE_P(TestArtUtils, FillInDefaultIconTest, testing::ValuesIn(icon_tests)); +TEST_P(FolderThumbTest, GetFolderThumb) +{ + CFileItem item(GetParam().path, true); + const std::string thumb = ART::GetFolderThumb(item, GetParam().thumb); + EXPECT_EQ(thumb, GetParam().result); +} + +INSTANTIATE_TEST_SUITE_P(TestArtUtils, FolderThumbTest, testing::ValuesIn(folder_thumb_tests)); + TEST_P(GetLocalFanartTest, GetLocalFanart) { std::string path, file_path, uniq; From 1efff51628eae7c5b22f55d8609bdd035763671a Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 10 Sep 2024 19:31:03 +0200 Subject: [PATCH 457/651] [video] Video navigation window: Replace context menu items 'Set actor/artist thumb' with 'Choose art' to make it possible again to add/and set other artwork, not only thumbnails. --- xbmc/dialogs/GUIDialogContextMenu.h | 2 -- xbmc/video/windows/GUIWindowVideoNav.cpp | 20 ++++---------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/xbmc/dialogs/GUIDialogContextMenu.h b/xbmc/dialogs/GUIDialogContextMenu.h index 10385bd20e5eb..7d44219c0f1ef 100644 --- a/xbmc/dialogs/GUIDialogContextMenu.h +++ b/xbmc/dialogs/GUIDialogContextMenu.h @@ -58,7 +58,6 @@ enum CONTEXT_BUTTON CONTEXT_BUTTON_CDDB, CONTEXT_BUTTON_SCAN, CONTEXT_BUTTON_SCAN_TO_LIBRARY, - CONTEXT_BUTTON_SET_ARTIST_THUMB, CONTEXT_BUTTON_SET_ART, CONTEXT_BUTTON_CANCEL_PARTYMODE, CONTEXT_BUTTON_MARK_WATCHED, @@ -70,7 +69,6 @@ enum CONTEXT_BUTTON CONTEXT_BUTTON_GO_TO_ARTIST, CONTEXT_BUTTON_GO_TO_ALBUM, CONTEXT_BUTTON_PLAY_OTHER, - CONTEXT_BUTTON_SET_ACTOR_THUMB, CONTEXT_BUTTON_UNLINK_BOOKMARK, CONTEXT_BUTTON_ACTIVATE, CONTEXT_BUTTON_GROUP_MANAGER, diff --git a/xbmc/video/windows/GUIWindowVideoNav.cpp b/xbmc/video/windows/GUIWindowVideoNav.cpp index 17c51cd46f845..d85294bbd9a55 100644 --- a/xbmc/video/windows/GUIWindowVideoNav.cpp +++ b/xbmc/video/windows/GUIWindowVideoNav.cpp @@ -818,13 +818,9 @@ void CGUIWindowVideoNav::GetContextButtons(int itemNumber, CContextButtons &butt { buttons.Add(CONTEXT_BUTTON_SCAN, 13349); } - if (node == NODE_TYPE_ACTOR && !dir.IsAllItem(item->GetPath()) && item->m_bIsFolder) { - if (StringUtils::StartsWithNoCase(m_vecItems->GetPath(), "videodb://musicvideos")) // mvids - buttons.Add(CONTEXT_BUTTON_SET_ARTIST_THUMB, 13359); - else - buttons.Add(CONTEXT_BUTTON_SET_ACTOR_THUMB, 20403); + buttons.Add(CONTEXT_BUTTON_SET_ART, 13511); // Choose art } } @@ -907,19 +903,11 @@ bool CGUIWindowVideoNav::OnContextButton(int itemNumber, CONTEXT_BUTTON button) } return true; } - - case CONTEXT_BUTTON_SET_ACTOR_THUMB: - case CONTEXT_BUTTON_SET_ARTIST_THUMB: + case CONTEXT_BUTTON_SET_ART: { - std::string type = MediaTypeSeason; - if (button == CONTEXT_BUTTON_SET_ACTOR_THUMB) - type = "actor"; - else if (button == CONTEXT_BUTTON_SET_ARTIST_THUMB) - type = MediaTypeArtist; - - bool result = CGUIDialogVideoInfo::ManageVideoItemArtwork(m_vecItems->Get(itemNumber), type); + const bool result{ + CGUIDialogVideoInfo::ChooseAndManageVideoItemArtwork(m_vecItems->Get(itemNumber))}; Refresh(); - return result; } case CONTEXT_BUTTON_GO_TO_ARTIST: From c61606f595728a78e05d509fedac81227442bedd Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:32:21 +0200 Subject: [PATCH 458/651] [PVR] Fix various 'Implicit conversion loses integer precision' warnings. --- xbmc/pvr/PVRPlaybackState.cpp | 3 +- xbmc/pvr/addons/PVRClient.cpp | 3 +- xbmc/pvr/addons/PVRClients.cpp | 21 ++++++----- xbmc/pvr/channels/PVRChannelGroup.cpp | 2 +- xbmc/pvr/channels/PVRRadioRDSInfoTag.cpp | 4 +- xbmc/pvr/epg/EpgContainer.cpp | 2 +- xbmc/pvr/epg/EpgInfoTag.cpp | 39 +++++++++++++++----- xbmc/pvr/epg/EpgInfoTag.h | 4 +- xbmc/pvr/epg/EpgSearchFilter.cpp | 4 +- xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp | 3 +- xbmc/pvr/guilib/PVRGUIProgressHandler.cpp | 4 +- xbmc/pvr/guilib/PVRGUIProgressHandler.h | 2 +- xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 11 ++++-- xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp | 38 ++++++++++++------- xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h | 2 +- xbmc/pvr/recordings/PVRRecording.cpp | 7 ++-- xbmc/pvr/timers/PVRTimerInfoTag.cpp | 2 +- xbmc/pvr/windows/GUIWindowPVRGuide.cpp | 4 +- 18 files changed, 99 insertions(+), 56 deletions(-) diff --git a/xbmc/pvr/PVRPlaybackState.cpp b/xbmc/pvr/PVRPlaybackState.cpp index 7535e45861012..e69ff0f9830d2 100644 --- a/xbmc/pvr/PVRPlaybackState.cpp +++ b/xbmc/pvr/PVRPlaybackState.cpp @@ -654,7 +654,8 @@ CDateTime CPVRPlaybackState::GetPlaybackTime(int iClientID, int iUniqueChannelID { // playing an epg tag on requested channel return epgTag->StartAsUTC() + - CDateTimeSpan(0, 0, 0, CServiceBroker::GetDataCacheCore().GetPlayTime() / 1000); + CDateTimeSpan(0, 0, 0, + static_cast<int>(CServiceBroker::GetDataCacheCore().GetPlayTime()) / 1000); } // not playing / playing live / playing timeshifted diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 162ad13e9a2e9..b0e2ef1a5297d 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -168,7 +168,8 @@ class CAddonRecording : public PVR_RECORDING iGenreType = recording.GenreType(); iGenreSubType = recording.GenreSubType(); iPlayCount = recording.GetLocalPlayCount(); - iLastPlayedPosition = std::lrint(recording.GetLocalResumePoint().timeInSeconds); + iLastPlayedPosition = + static_cast<int>(std::lrint(recording.GetLocalResumePoint().timeInSeconds)); bIsDeleted = recording.IsDeleted(); iEpgEventId = recording.BroadcastUid(); iChannelUid = recording.ChannelUid(); diff --git a/xbmc/pvr/addons/PVRClients.cpp b/xbmc/pvr/addons/PVRClients.cpp index 89d8de916c96f..a67fe724ee772 100644 --- a/xbmc/pvr/addons/PVRClients.cpp +++ b/xbmc/pvr/addons/PVRClients.cpp @@ -170,8 +170,9 @@ void CPVRClients::UpdateClients( unsigned int i = 0; for (const auto& client : clientsToCreate) { - progressHandler->UpdateProgress(client->Name(), i++, - clientsToCreate.size() + clientsToReCreate.size()); + progressHandler->UpdateProgress( + client->Name(), i++, + static_cast<unsigned int>(clientsToCreate.size() + clientsToReCreate.size())); const ADDON_STATUS status = client->Create(); @@ -191,8 +192,9 @@ void CPVRClients::UpdateClients( for (const auto& clientInfo : clientsToReCreate) { - progressHandler->UpdateProgress(clientInfo.second, i++, - clientsToCreate.size() + clientsToReCreate.size()); + progressHandler->UpdateProgress( + clientInfo.second, i++, + static_cast<unsigned int>(clientsToCreate.size() + clientsToReCreate.size())); // stop and recreate client StopClient(clientInfo.first, true /* restart */); @@ -305,8 +307,9 @@ std::shared_ptr<CPVRClient> CPVRClients::GetClient(int clientId) const int CPVRClients::CreatedClientAmount() const { std::unique_lock<CCriticalSection> lock(m_critSection); - return std::count_if(m_clientMap.cbegin(), m_clientMap.cend(), - [](const auto& client) { return client.second->ReadyToUse(); }); + return static_cast<int>(std::count_if(m_clientMap.cbegin(), m_clientMap.cend(), + [](const auto& client) + { return client.second->ReadyToUse(); })); } bool CPVRClients::HasCreatedClients() const @@ -444,9 +447,9 @@ int CPVRClients::EnabledClientAmount() const } ADDON::CAddonMgr& addonMgr = CServiceBroker::GetAddonMgr(); - return std::count_if(clientMap.cbegin(), clientMap.cend(), [&addonMgr](const auto& client) { - return !addonMgr.IsAddonDisabled(client.second->ID()); - }); + return static_cast<int>(std::count_if( + clientMap.cbegin(), clientMap.cend(), + [&addonMgr](const auto& client) { return !addonMgr.IsAddonDisabled(client.second->ID()); })); } bool CPVRClients::IsEnabledClient(int clientId) const diff --git a/xbmc/pvr/channels/PVRChannelGroup.cpp b/xbmc/pvr/channels/PVRChannelGroup.cpp index 3f68e31b77d4e..d4d1176ad313c 100644 --- a/xbmc/pvr/channels/PVRChannelGroup.cpp +++ b/xbmc/pvr/channels/PVRChannelGroup.cpp @@ -567,7 +567,7 @@ int CPVRChannelGroup::LoadFromDatabase(const std::vector<std::shared_ptr<CPVRCli DeleteGroupMembersFromDb(membersToDelete); - return results.size() - membersToDelete.size(); + return static_cast<int>(results.size() - membersToDelete.size()); } void CPVRChannelGroup::DeleteGroupMembersFromDb( diff --git a/xbmc/pvr/channels/PVRRadioRDSInfoTag.cpp b/xbmc/pvr/channels/PVRRadioRDSInfoTag.cpp index e1ee649debf70..66930d6704247 100644 --- a/xbmc/pvr/channels/PVRRadioRDSInfoTag.cpp +++ b/xbmc/pvr/channels/PVRRadioRDSInfoTag.cpp @@ -643,14 +643,14 @@ void CPVRRadioRDSInfoTag::SetProgramServiceText(const std::string& strPSText) for (size_t i = m_strProgramServiceText.MaxSize() / 2 + 1; i < m_strProgramServiceText.MaxSize(); ++i) { - m_strProgramServiceLine0 += m_strProgramServiceText.GetLine(i); + m_strProgramServiceLine0 += m_strProgramServiceText.GetLine(static_cast<unsigned int>(i)); m_strProgramServiceLine0 += ' '; } m_strProgramServiceLine1.erase(); for (size_t i = 0; i < m_strProgramServiceText.MaxSize() / 2; ++i) { - m_strProgramServiceLine1 += m_strProgramServiceText.GetLine(i); + m_strProgramServiceLine1 += m_strProgramServiceText.GetLine(static_cast<unsigned int>(i)); m_strProgramServiceLine1 += ' '; } } diff --git a/xbmc/pvr/epg/EpgContainer.cpp b/xbmc/pvr/epg/EpgContainer.cpp index d3c7d42c192d6..d2d744401b9d3 100644 --- a/xbmc/pvr/epg/EpgContainer.cpp +++ b/xbmc/pvr/epg/EpgContainer.cpp @@ -759,7 +759,7 @@ bool CPVREpgContainer::UpdateEPG(bool bOnlyPending /* = false */) if (progressHandler) progressHandler->UpdateProgress(epg->GetChannelData()->ChannelName(), ++iCounter, - epgsToUpdate.size()); + static_cast<unsigned int>(epgsToUpdate.size())); if ((!bOnlyPending || epg->UpdatePending()) && epg->Update(start, diff --git a/xbmc/pvr/epg/EpgInfoTag.cpp b/xbmc/pvr/epg/EpgInfoTag.cpp index 9f4f822376e1f..b731f685c78fd 100644 --- a/xbmc/pvr/epg/EpgInfoTag.cpp +++ b/xbmc/pvr/epg/EpgInfoTag.cpp @@ -22,6 +22,7 @@ #include "utils/Variant.h" #include "utils/log.h" +#include <chrono> #include <memory> #include <mutex> #include <string> @@ -245,27 +246,36 @@ float CPVREpgInfoTag::ProgressPercentage() const CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(currentTime); m_startTime.GetAsTime(startTime); m_endTime.GetAsTime(endTime); - int iDuration = endTime - startTime > 0 ? endTime - startTime : 3600; if (currentTime >= startTime && currentTime <= endTime) - fReturn = static_cast<float>(currentTime - startTime) * 100.0f / iDuration; + { + const std::chrono::duration<float> current{currentTime - startTime}; + const std::chrono::duration<float> total{endTime - startTime > 0 ? endTime - startTime + : 3600.0f}; + fReturn = current.count() * 100.0f / total.count(); + } else if (currentTime > endTime) + { fReturn = 100.0f; - + } return fReturn; } -int CPVREpgInfoTag::Progress() const +unsigned int CPVREpgInfoTag::Progress() const { time_t currentTime, startTime; CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(currentTime); m_startTime.GetAsTime(startTime); - int iDuration = currentTime - startTime; - if (iDuration <= 0) + if (currentTime > startTime) + { + const std::chrono::duration<unsigned int> duration{currentTime - startTime}; + return duration.count(); + } + else + { return 0; - - return iDuration; + } } void CPVREpgInfoTag::SetUniqueBroadcastID(unsigned int iUniqueBroadcastID) @@ -324,12 +334,21 @@ void CPVREpgInfoTag::SetEndFromUTC(const CDateTime& end) m_endTime = end; } -int CPVREpgInfoTag::GetDuration() const +unsigned int CPVREpgInfoTag::GetDuration() const { time_t start, end; m_startTime.GetAsTime(start); m_endTime.GetAsTime(end); - return end - start > 0 ? end - start : 3600; + + if (end > start) + { + const std::chrono::duration<unsigned int> duration{end - start}; + return duration.count(); + } + else + { + return 3600; + } } const std::string CPVREpgInfoTag::GetCastLabel() const diff --git a/xbmc/pvr/epg/EpgInfoTag.h b/xbmc/pvr/epg/EpgInfoTag.h index 55735aff98cd7..d5ae0518ec607 100644 --- a/xbmc/pvr/epg/EpgInfoTag.h +++ b/xbmc/pvr/epg/EpgInfoTag.h @@ -109,7 +109,7 @@ class CPVREpgInfoTag final : public ISerializable, * @brief Get the progress of this tag in seconds. * @return The current progress of this tag in seconds. */ - int Progress() const; + unsigned int Progress() const; /*! * @brief Get EPG ID of this tag. @@ -187,7 +187,7 @@ class CPVREpgInfoTag final : public ISerializable, * @brief Get the duration of this event in seconds. * @return The duration. */ - int GetDuration() const; + unsigned int GetDuration() const; /*! * @brief Get the title of this event. diff --git a/xbmc/pvr/epg/EpgSearchFilter.cpp b/xbmc/pvr/epg/EpgSearchFilter.cpp index f262c07725a20..0af425fd2a54d 100644 --- a/xbmc/pvr/epg/EpgSearchFilter.cpp +++ b/xbmc/pvr/epg/EpgSearchFilter.cpp @@ -296,10 +296,10 @@ bool CPVREpgSearchFilter::MatchDuration(const std::shared_ptr<const CPVREpgInfoT bool bReturn(true); if (m_iMinimumDuration != EPG_SEARCH_UNSET) - bReturn = (tag->GetDuration() > m_iMinimumDuration * 60); + bReturn = (tag->GetDuration() > static_cast<unsigned int>(m_iMinimumDuration) * 60); if (bReturn && m_iMaximumDuration != EPG_SEARCH_UNSET) - bReturn = (tag->GetDuration() < m_iMaximumDuration * 60); + bReturn = (tag->GetDuration() < static_cast<unsigned int>(m_iMaximumDuration) * 60); return bReturn; } diff --git a/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp b/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp index 11045c98d8126..20f6114387a4c 100644 --- a/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp +++ b/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp @@ -70,7 +70,8 @@ void CPVRGUIChannelIconUpdater::SearchAndUpdateMissingChannelIcons() const { const std::shared_ptr<CPVRChannel> channel = member->Channel(); - progressHandler->UpdateProgress(channel->ChannelName(), channelIndex++, members.size()); + progressHandler->UpdateProgress(channel->ChannelName(), channelIndex++, + static_cast<unsigned int>(members.size())); // skip if an icon is already set and exists if (CFileUtils::Exists(channel->IconPath())) diff --git a/xbmc/pvr/guilib/PVRGUIProgressHandler.cpp b/xbmc/pvr/guilib/PVRGUIProgressHandler.cpp index f8765227ddb31..791a613bfc805 100644 --- a/xbmc/pvr/guilib/PVRGUIProgressHandler.cpp +++ b/xbmc/pvr/guilib/PVRGUIProgressHandler.cpp @@ -43,7 +43,9 @@ void CPVRGUIProgressHandler::UpdateProgress(const std::string& strText, float fP } } -void CPVRGUIProgressHandler::UpdateProgress(const std::string& strText, int iCurrent, int iMax) +void CPVRGUIProgressHandler::UpdateProgress(const std::string& strText, + unsigned int iCurrent, + unsigned int iMax) { float fPercentage = (iCurrent * 100.0f) / iMax; if (!std::isnan(fPercentage)) diff --git a/xbmc/pvr/guilib/PVRGUIProgressHandler.h b/xbmc/pvr/guilib/PVRGUIProgressHandler.h index 6e7b72f20e51c..5c3aea1ecd723 100644 --- a/xbmc/pvr/guilib/PVRGUIProgressHandler.h +++ b/xbmc/pvr/guilib/PVRGUIProgressHandler.h @@ -41,7 +41,7 @@ namespace PVR * @param iCurrent The new current progress value, must be less or equal iMax. * @param iMax The new maximum progress value, must be greater or equal iCurrent. */ - void UpdateProgress(const std::string& strText, int iCurrent, int iMax); + void UpdateProgress(const std::string& strText, unsigned int iCurrent, unsigned int iMax); protected: // CThread implementation diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index d57aef2fd5a34..2ec4b22947eed 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -1450,14 +1450,17 @@ bool CPVRGUIInfo::GetPVRInt(const CFileItem* item, const CGUIInfo& info, int& iV iValue = GetTimeShiftSeekPercent(); return true; case PVR_ACTUAL_STREAM_SIG_PROGR: - iValue = std::lrintf(static_cast<float>(m_qualityInfo.Signal()) / 0xFFFF * 100); + iValue = + static_cast<int>(std::lrintf(static_cast<float>(m_qualityInfo.Signal()) / 0xFFFF * 100)); return true; case PVR_ACTUAL_STREAM_SNR_PROGR: - iValue = std::lrintf(static_cast<float>(m_qualityInfo.SNR()) / 0xFFFF * 100); + iValue = + static_cast<int>(std::lrintf(static_cast<float>(m_qualityInfo.SNR()) / 0xFFFF * 100)); return true; case PVR_BACKEND_DISKSPACE_PROGR: if (m_iBackendDiskTotal > 0) - iValue = std::lrintf(static_cast<float>(m_iBackendDiskUsed) / m_iBackendDiskTotal * 100); + iValue = static_cast<int>( + std::lrintf(static_cast<float>(m_iBackendDiskUsed) / m_iBackendDiskTotal * 100)); else iValue = 0xFF; return true; @@ -2176,7 +2179,7 @@ int CPVRGUIInfo::GetTimeShiftSeekPercent() const float percentPerSecond = 100.0f / totalTime; float percent = progress + percentPerSecond * seekSize; percent = std::max(0.0f, std::min(percent, 100.0f)); - return std::lrintf(percent); + return static_cast<int>(std::lrintf(percent)); } return progress; } diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp index 4691fd60332cb..e2c320a19c8a7 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp @@ -20,6 +20,7 @@ #include "settings/SettingsComponent.h" #include "utils/StringUtils.h" +#include <chrono> #include <cmath> #include <ctime> #include <memory> @@ -82,7 +83,8 @@ void CPVRGUITimesInfo::UpdatePlayingTag() else if (m_iTimeshiftEndTime > m_iTimeshiftStartTime) { m_playingEpgTag.reset(); - m_iDuration = m_iTimeshiftEndTime - m_iTimeshiftStartTime; + std::chrono::duration<unsigned int> duration{m_iTimeshiftEndTime - m_iTimeshiftStartTime}; + m_iDuration = duration.count(); } else { @@ -230,7 +232,9 @@ void CPVRGUITimesInfo::UpdateTimeshiftProgressData() ////////////////////////////////////////////////////////////////////////////////////// // duration ////////////////////////////////////////////////////////////////////////////////////// - m_iTimeshiftProgressDuration = m_iTimeshiftProgressEndTime - m_iTimeshiftProgressStartTime; + std::chrono::duration<unsigned int> duration{m_iTimeshiftProgressEndTime - + m_iTimeshiftProgressStartTime}; + m_iTimeshiftProgressDuration = duration.count(); } void CPVRGUITimesInfo::Update() @@ -350,25 +354,28 @@ int CPVRGUITimesInfo::GetRemainingTime(const std::shared_ptr<const CPVREpgInfoTa if (epgTag && m_playingEpgTag && *epgTag != *m_playingEpgTag) return epgTag->GetDuration() - epgTag->Progress(); else - return m_iDuration - GetElapsedTime(); + return static_cast<int>(m_iDuration - GetElapsedTime()); } int CPVRGUITimesInfo::GetTimeshiftProgress() const { std::unique_lock<CCriticalSection> lock(m_critSection); - return std::lrintf(static_cast<float>(m_iTimeshiftPlayTime - m_iTimeshiftStartTime) / (m_iTimeshiftEndTime - m_iTimeshiftStartTime) * 100); + const std::chrono::duration<float> current{m_iTimeshiftPlayTime - m_iTimeshiftStartTime}; + const std::chrono::duration<float> total{m_iTimeshiftEndTime - m_iTimeshiftStartTime}; + return static_cast<int>(std::lrintf(current.count() / total.count() * 100)); } int CPVRGUITimesInfo::GetTimeshiftProgressDuration() const { std::unique_lock<CCriticalSection> lock(m_critSection); - return m_iTimeshiftProgressDuration; + return static_cast<int>(m_iTimeshiftProgressDuration); } int CPVRGUITimesInfo::GetTimeshiftProgressPlayPosition() const { std::unique_lock<CCriticalSection> lock(m_critSection); - return std::lrintf(static_cast<float>(m_iTimeshiftPlayTime - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100); + const std::chrono::duration<float> duration{m_iTimeshiftPlayTime - m_iTimeshiftProgressStartTime}; + return static_cast<int>(std::lrintf(duration.count() / m_iTimeshiftProgressDuration * 100)); } int CPVRGUITimesInfo::GetTimeshiftProgressEpgStart() const @@ -378,7 +385,8 @@ int CPVRGUITimesInfo::GetTimeshiftProgressEpgStart() const { time_t epgStart = 0; m_playingEpgTag->StartAsUTC().GetAsTime(epgStart); - return std::lrintf(static_cast<float>(epgStart - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100); + const std::chrono::duration<float> duration{epgStart - m_iTimeshiftProgressStartTime}; + return static_cast<int>(std::lrintf(duration.count() / m_iTimeshiftProgressDuration * 100)); } return 0; } @@ -390,7 +398,8 @@ int CPVRGUITimesInfo::GetTimeshiftProgressEpgEnd() const { time_t epgEnd = 0; m_playingEpgTag->EndAsUTC().GetAsTime(epgEnd); - return std::lrintf(static_cast<float>(epgEnd - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100); + const std::chrono::duration<float> duration{epgEnd - m_iTimeshiftProgressStartTime}; + return static_cast<int>(std::lrintf(duration.count() / m_iTimeshiftProgressDuration * 100)); } return 0; } @@ -398,13 +407,16 @@ int CPVRGUITimesInfo::GetTimeshiftProgressEpgEnd() const int CPVRGUITimesInfo::GetTimeshiftProgressBufferStart() const { std::unique_lock<CCriticalSection> lock(m_critSection); - return std::lrintf(static_cast<float>(m_iTimeshiftStartTime - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100); + const std::chrono::duration<float> duration{m_iTimeshiftStartTime - + m_iTimeshiftProgressStartTime}; + return static_cast<int>(std::lrintf(duration.count() / m_iTimeshiftProgressDuration * 100)); } int CPVRGUITimesInfo::GetTimeshiftProgressBufferEnd() const { std::unique_lock<CCriticalSection> lock(m_critSection); - return std::lrintf(static_cast<float>(m_iTimeshiftEndTime - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100); + const std::chrono::duration<float> duration{m_iTimeshiftEndTime - m_iTimeshiftProgressStartTime}; + return static_cast<int>(std::lrintf(duration.count() / m_iTimeshiftProgressDuration * 100)); } int CPVRGUITimesInfo::GetEpgEventDuration(const std::shared_ptr<const CPVREpgInfoTag>& epgTag) const @@ -413,16 +425,16 @@ int CPVRGUITimesInfo::GetEpgEventDuration(const std::shared_ptr<const CPVREpgInf if (epgTag && m_playingEpgTag && *epgTag != *m_playingEpgTag) return epgTag->GetDuration(); else - return m_iDuration; + return static_cast<int>(m_iDuration); } int CPVRGUITimesInfo::GetEpgEventProgress(const std::shared_ptr<const CPVREpgInfoTag>& epgTag) const { std::unique_lock<CCriticalSection> lock(m_critSection); if (epgTag && m_playingEpgTag && *epgTag != *m_playingEpgTag) - return std::lrintf(epgTag->ProgressPercentage()); + return static_cast<int>(std::lrintf(epgTag->ProgressPercentage())); else - return std::lrintf(static_cast<float>(GetElapsedTime()) / m_iDuration * 100); + return static_cast<int>(std::lrintf(static_cast<float>(GetElapsedTime()) / m_iDuration * 100)); } bool CPVRGUITimesInfo::IsTimeshifting() const diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h b/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h index 73b194f78ee05..7231b5880d572 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h +++ b/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h @@ -81,7 +81,7 @@ namespace PVR time_t m_iTimeshiftStartTime; time_t m_iTimeshiftEndTime; time_t m_iTimeshiftPlayTime; - unsigned int m_iTimeshiftOffset; + int64_t m_iTimeshiftOffset; time_t m_iTimeshiftProgressStartTime; time_t m_iTimeshiftProgressEndTime; diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp index 7e9c4fae5af6e..9c5f9d4751af3 100644 --- a/xbmc/pvr/recordings/PVRRecording.cpp +++ b/xbmc/pvr/recordings/PVRRecording.cpp @@ -335,8 +335,8 @@ bool CPVRRecording::SetResumePoint(const CBookmark& resumePoint) const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId); if (client && client->GetClientCapabilities().SupportsRecordingsLastPlayedPosition()) { - if (client->SetRecordingLastPlayedPosition(*this, lrint(resumePoint.timeInSeconds)) != - PVR_ERROR_NO_ERROR) + if (client->SetRecordingLastPlayedPosition( + *this, static_cast<int>(std::lrint(resumePoint.timeInSeconds))) != PVR_ERROR_NO_ERROR) return false; } @@ -350,7 +350,8 @@ bool CPVRRecording::SetResumePoint(double timeInSeconds, const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId); if (client && client->GetClientCapabilities().SupportsRecordingsLastPlayedPosition()) { - if (client->SetRecordingLastPlayedPosition(*this, lrint(timeInSeconds)) != PVR_ERROR_NO_ERROR) + if (client->SetRecordingLastPlayedPosition( + *this, static_cast<int>(std::lrint(timeInSeconds))) != PVR_ERROR_NO_ERROR) return false; } diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.cpp b/xbmc/pvr/timers/PVRTimerInfoTag.cpp index 50c61a3871d8a..6f5ea746eabfc 100644 --- a/xbmc/pvr/timers/PVRTimerInfoTag.cpp +++ b/xbmc/pvr/timers/PVRTimerInfoTag.cpp @@ -1170,7 +1170,7 @@ int CPVRTimerInfoTag::GetDuration() const time_t start, end; m_StartTime.GetAsTime(start); m_StopTime.GetAsTime(end); - return end - start > 0 ? end - start : 3600; + return end - start > 0 ? static_cast<int>(end - start) : 3600; } CDateTime CPVRTimerInfoTag::FirstDayAsUTC() const diff --git a/xbmc/pvr/windows/GUIWindowPVRGuide.cpp b/xbmc/pvr/windows/GUIWindowPVRGuide.cpp index fb8c909797efc..05f7669f85aa5 100644 --- a/xbmc/pvr/windows/GUIWindowPVRGuide.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRGuide.cpp @@ -283,7 +283,7 @@ CFileItemPtr CGUIWindowPVRGuideBase::GetCurrentListItem(int offset /*= 0*/) int CGUIWindowPVRGuideBase::GetCurrentListItemIndex(const std::shared_ptr<const CFileItem>& item) { - return item ? item->GetProperty("TimelineIndex").asInteger() : -1; + return item ? item->GetProperty("TimelineIndex").asInteger32() : -1; } bool CGUIWindowPVRGuideBase::ShouldNavigateToGridContainer(int iAction) @@ -630,7 +630,7 @@ class CContextMenuFunctions : public CContextButtons void Add(bool (A::*function)(), unsigned int resId) { - CContextButtons::Add(size(), resId); + CContextButtons::Add(static_cast<unsigned int>(size()), resId); m_functions.emplace_back(std::bind(function, m_instance)); } From 0a254a03686bff26309976f24a35fea8af97f56a Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:49:17 +0200 Subject: [PATCH 459/651] [addons] Fix CPPCheck warnings for CStructHdl and DynamicCStructHdl: noExplicitConstructor, noCopyConstructor --- .../kodi-dev-kit/include/kodi/AddonBase.h | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h index 54119f62aaa29..1ff8e0a26aa37 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h @@ -207,14 +207,17 @@ class CStructHdl public: CStructHdl() : m_cStructure(new C_STRUCT()), m_owner(true) {} - CStructHdl(const CPP_CLASS& cppClass) + CStructHdl(const CStructHdl& cppClass) : m_cStructure(new C_STRUCT(*cppClass.m_cStructure)), m_owner(true) { } - CStructHdl(const C_STRUCT* cStructure) : m_cStructure(new C_STRUCT(*cStructure)), m_owner(true) {} + explicit CStructHdl(const C_STRUCT* cStructure) + : m_cStructure(new C_STRUCT(*cStructure)), m_owner(true) + { + } - CStructHdl(C_STRUCT* cStructure) : m_cStructure(cStructure) { assert(cStructure); } + explicit CStructHdl(C_STRUCT* cStructure) : m_cStructure(cStructure) { assert(cStructure); } const CStructHdl& operator=(const CStructHdl& right) { @@ -289,19 +292,22 @@ class DynamicCStructHdl memset(m_cStructure, 0, sizeof(C_STRUCT)); } - DynamicCStructHdl(const CPP_CLASS& cppClass) + DynamicCStructHdl(const DynamicCStructHdl& cppClass) : m_cStructure(new C_STRUCT(*cppClass.m_cStructure)), m_owner(true) { CPP_CLASS::AllocResources(cppClass.m_cStructure, m_cStructure); } - DynamicCStructHdl(const C_STRUCT* cStructure) + explicit DynamicCStructHdl(const C_STRUCT* cStructure) : m_cStructure(new C_STRUCT(*cStructure)), m_owner(true) { CPP_CLASS::AllocResources(cStructure, m_cStructure); } - DynamicCStructHdl(C_STRUCT* cStructure) : m_cStructure(cStructure) { assert(cStructure); } + explicit DynamicCStructHdl(C_STRUCT* cStructure) : m_cStructure(cStructure) + { + assert(cStructure); + } const DynamicCStructHdl& operator=(const DynamicCStructHdl& right) { From 6ba04a3cb69b26b6404a6908270fa8a82bc995e5 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 8 Sep 2024 17:36:24 +0200 Subject: [PATCH 460/651] [favourites] Fix various CPPCheck warnings. --- xbmc/favourites/GUIViewStateFavourites.h | 2 +- xbmc/favourites/GUIWindowFavourites.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/xbmc/favourites/GUIViewStateFavourites.h b/xbmc/favourites/GUIViewStateFavourites.h index aad4444a1f6d5..48f561b808190 100644 --- a/xbmc/favourites/GUIViewStateFavourites.h +++ b/xbmc/favourites/GUIViewStateFavourites.h @@ -15,7 +15,7 @@ class CFileItemList; class CGUIViewStateFavourites : public CGUIViewState { public: - CGUIViewStateFavourites(const CFileItemList& items); + explicit CGUIViewStateFavourites(const CFileItemList& items); ~CGUIViewStateFavourites() override = default; protected: diff --git a/xbmc/favourites/GUIWindowFavourites.cpp b/xbmc/favourites/GUIWindowFavourites.cpp index daae1c3d3493a..3bbaf3ae98d4c 100644 --- a/xbmc/favourites/GUIWindowFavourites.cpp +++ b/xbmc/favourites/GUIWindowFavourites.cpp @@ -161,12 +161,12 @@ bool CGUIWindowFavourites::OnAction(const CAction& action) if (action.GetID() == ACTION_PLAYER_PLAY) { - const auto target{ + const auto targetItem{ CServiceBroker::GetFavouritesService().ResolveFavourite(*(*m_vecItems)[selectedItem])}; - if (!target) + if (!targetItem) return false; - const auto item{std::make_shared<CFileItem>(*target)}; + const auto item{std::make_shared<CFileItem>(*targetItem)}; // video play action setting is for files and folders... if (item->HasVideoInfoTag() || (item->m_bIsFolder && VIDEO::UTILS::IsItemPlayable(*item))) From 9c9fea8f4dc6544564a9ef3081aa5d5a3bc69ca8 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 8 Sep 2024 17:36:53 +0200 Subject: [PATCH 461/651] [guilib] Fix various CPPCheck warnings. --- xbmc/guilib/guiinfo/AddonsGUIInfo.cpp | 1 - xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp | 22 ++++++------ xbmc/guilib/guiinfo/GUIInfo.h | 36 +++++-------------- xbmc/guilib/guiinfo/GUIInfoLabel.h | 6 ++-- xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp | 15 ++++---- xbmc/pvr/dialogs/GUIDialogPVRRadioRDSInfo.cpp | 24 +++++++------ 6 files changed, 44 insertions(+), 60 deletions(-) diff --git a/xbmc/guilib/guiinfo/AddonsGUIInfo.cpp b/xbmc/guilib/guiinfo/AddonsGUIInfo.cpp index 6630fa14d8606..7f8c29e3710f3 100644 --- a/xbmc/guilib/guiinfo/AddonsGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/AddonsGUIInfo.cpp @@ -240,7 +240,6 @@ bool CAddonsGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int context /////////////////////////////////////////////////////////////////////////////////////////////// case SYSTEM_HAS_ADDON: { - ADDON::AddonPtr addon; value = CServiceBroker::GetAddonMgr().IsAddonInstalled(info.GetData3()); return true; } diff --git a/xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp b/xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp index a5ee1335808d5..74483bd2b17ee 100644 --- a/xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp @@ -204,17 +204,19 @@ bool CGUIControlsGUIInfo::GetLabel(std::string& value, const CFileItem *item, in { int count = 0; const CFileItemList& items = window->CurrentDirectory(); - for (const auto& item : items) + for (const auto& i : items) { // Iterate through container and count watched, unwatched and total duration. - if (info.m_info == CONTAINER_TOTALWATCHED && item->HasVideoInfoTag() && item->GetVideoInfoTag()->GetPlayCount() > 0) + if (info.m_info == CONTAINER_TOTALWATCHED && i->HasVideoInfoTag() && + i->GetVideoInfoTag()->GetPlayCount() > 0) count += 1; - else if (info.m_info == CONTAINER_TOTALUNWATCHED && item->HasVideoInfoTag() && item->GetVideoInfoTag()->GetPlayCount() == 0) + else if (info.m_info == CONTAINER_TOTALUNWATCHED && i->HasVideoInfoTag() && + i->GetVideoInfoTag()->GetPlayCount() == 0) count += 1; - else if (info.m_info == CONTAINER_TOTALTIME && item->HasMusicInfoTag()) - count += item->GetMusicInfoTag()->GetDuration(); - else if (info.m_info == CONTAINER_TOTALTIME && item->HasVideoInfoTag()) - count += item->GetVideoInfoTag()->m_streamDetails.GetVideoDuration(); + else if (info.m_info == CONTAINER_TOTALTIME && i->HasMusicInfoTag()) + count += i->GetMusicInfoTag()->GetDuration(); + else if (info.m_info == CONTAINER_TOTALTIME && i->HasVideoInfoTag()) + count += i->GetVideoInfoTag()->m_streamDetails.GetVideoDuration(); } if (info.m_info == CONTAINER_TOTALTIME && count > 0) { @@ -531,9 +533,9 @@ bool CGUIControlsGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int co } if (content.empty()) { - CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow); - if (window) - content = window->CurrentDirectory().GetContent(); + CGUIMediaWindow* mediaWindow = GUIINFO::GetMediaWindow(contextWindow); + if (mediaWindow) + content = mediaWindow->CurrentDirectory().GetContent(); } value = StringUtils::EqualsNoCase(info.GetData3(), content); return true; diff --git a/xbmc/guilib/guiinfo/GUIInfo.h b/xbmc/guilib/guiinfo/GUIInfo.h index c5bffe8704ff7..10604e623e2a5 100644 --- a/xbmc/guilib/guiinfo/GUIInfo.h +++ b/xbmc/guilib/guiinfo/GUIInfo.h @@ -35,49 +35,31 @@ class CGUIInfo } explicit CGUIInfo(int info, uint32_t data1 = 0, int data2 = 0, uint32_t flag = 0) - : m_info(info), - m_data1(data1), - m_data2(data2), - m_data4(0) + : m_info(info), m_data1(data1), m_data2(data2) { if (flag) SetInfoFlag(flag); } CGUIInfo(int info, uint32_t data1, int data2, const std::string& data3) - : m_info(info), m_data1(data1), m_data2(data2), m_data3(data3), m_data4(0) + : m_info(info), m_data1(data1), m_data2(data2), m_data3(data3) { } CGUIInfo(int info, uint32_t data1, const std::string& data3) - : m_info(info), - m_data1(data1), - m_data2(0), - m_data3(data3), - m_data4(0) + : m_info(info), m_data1(data1), m_data3(data3) { } - CGUIInfo(int info, const std::string& data3) - : m_info(info), - m_data1(0), - m_data2(0), - m_data3(data3), - m_data4(0) - { - } + CGUIInfo(int info, const std::string& data3) : m_info(info), m_data3(data3) {} CGUIInfo(int info, const std::string& data3, int data2) - : m_info(info), - m_data1(0), - m_data2(data2), - m_data3(data3), - m_data4(0) + : m_info(info), m_data2(data2), m_data3(data3) { } CGUIInfo(int info, const std::string& data3, const std::string& data5) - : m_info(info), m_data1(0), m_data3(data3), m_data4(0), m_data5(data5) + : m_info(info), m_data3(data3), m_data5(data5) { } @@ -98,10 +80,10 @@ class CGUIInfo private: void SetInfoFlag(uint32_t flag); - uint32_t m_data1; - int m_data2; + uint32_t m_data1{0}; + int m_data2{0}; std::string m_data3; - int m_data4; + int m_data4{0}; std::string m_data5; }; diff --git a/xbmc/guilib/guiinfo/GUIInfoLabel.h b/xbmc/guilib/guiinfo/GUIInfoLabel.h index fd5da50154c04..b2d58f9412c5a 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabel.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabel.h @@ -32,9 +32,9 @@ class CGUIInfoLabel { public: CGUIInfoLabel() = default; - CGUIInfoLabel(const std::string& label, - const std::string& fallback = "", - int context = INFO::DEFAULT_CONTEXT); + explicit CGUIInfoLabel(const std::string& label, + const std::string& fallback = "", + int context = INFO::DEFAULT_CONTEXT); void SetLabel(const std::string& label, const std::string& fallback, diff --git a/xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp b/xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp index 27a892f10680a..a988ff402a358 100644 --- a/xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp @@ -42,12 +42,9 @@ bool CVisualisationGUIInfo::GetLabel(std::string& value, const CFileItem *item, if (msg.GetPointer()) { CGUIVisualisationControl* viz = static_cast<CGUIVisualisationControl*>(msg.GetPointer()); - if (viz) - { - value = viz->GetActivePresetName(); - URIUtils::RemoveExtension(value); - return true; - } + value = viz->GetActivePresetName(); + URIUtils::RemoveExtension(value); + return true; } break; } @@ -86,8 +83,8 @@ bool CVisualisationGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); if (msg.GetPointer()) { - CGUIVisualisationControl *pVis = static_cast<CGUIVisualisationControl*>(msg.GetPointer()); - value = pVis->IsLocked(); + CGUIVisualisationControl* viz = static_cast<CGUIVisualisationControl*>(msg.GetPointer()); + value = viz->IsLocked(); return true; } break; @@ -104,7 +101,7 @@ bool CVisualisationGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int if (msg.GetPointer()) { CGUIVisualisationControl* viz = static_cast<CGUIVisualisationControl*>(msg.GetPointer()); - value = (viz && viz->HasPresets()); + value = viz->HasPresets(); return true; } break; diff --git a/xbmc/pvr/dialogs/GUIDialogPVRRadioRDSInfo.cpp b/xbmc/pvr/dialogs/GUIDialogPVRRadioRDSInfo.cpp index 6d73450e4e6da..48eb10b9c7a26 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRRadioRDSInfo.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRRadioRDSInfo.cpp @@ -81,37 +81,41 @@ bool CGUIDialogPVRRadioRDSInfo::OnMessage(CGUIMessage& message) if (!textbox) return false; + std::string text; switch (spin->GetValue()) { case INFO_NEWS: - textbox->SetInfo(currentRDS->GetInfoNews()); + text = currentRDS->GetInfoNews(); break; case INFO_NEWS_LOCAL: - textbox->SetInfo(currentRDS->GetInfoNewsLocal()); + text = currentRDS->GetInfoNewsLocal(); break; case INFO_SPORT: - textbox->SetInfo(currentRDS->GetInfoSport()); + text = currentRDS->GetInfoSport(); break; case INFO_WEATHER: - textbox->SetInfo(currentRDS->GetInfoWeather()); + text = currentRDS->GetInfoWeather(); break; case INFO_LOTTERY: - textbox->SetInfo(currentRDS->GetInfoLottery()); + text = currentRDS->GetInfoLottery(); break; case INFO_STOCK: - textbox->SetInfo(currentRDS->GetInfoStock()); + text = currentRDS->GetInfoStock(); break; case INFO_OTHER: - textbox->SetInfo(currentRDS->GetInfoOther()); + text = currentRDS->GetInfoOther(); break; case INFO_CINEMA: - textbox->SetInfo(currentRDS->GetInfoCinema()); + text = currentRDS->GetInfoCinema(); break; case INFO_HOROSCOPE: - textbox->SetInfo(currentRDS->GetInfoHoroscope()); + text = currentRDS->GetInfoHoroscope(); break; } + if (!text.empty()) + textbox->SetInfo(KODI::GUILIB::GUIINFO::CGUIInfoLabel{text}); + SET_CONTROL_VISIBLE(CONTROL_INFO_LIST); } } @@ -210,7 +214,7 @@ bool CGUIDialogPVRRadioRDSInfo::InfoControl::Update(const std::string& textboxVa { m_spinControl->SetValue(m_iSpinControlId); m_textboxValue = textboxValue; - m_textbox->SetInfo(textboxValue); + m_textbox->SetInfo(KODI::GUILIB::GUIINFO::CGUIInfoLabel{textboxValue}); return true; } } From bbfbabed73465ccbb41a24f40f4c6ebb52d8a950 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 8 Sep 2024 19:20:40 +0200 Subject: [PATCH 462/651] [pvr] Fix various CPPCheck warnings. --- xbmc/pvr/PVRDescrambleInfo.h | 2 +- xbmc/pvr/PVRManager.cpp | 11 +++++++---- xbmc/pvr/PVRSignalStatus.h | 2 +- xbmc/pvr/addons/PVRClient.cpp | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/xbmc/pvr/PVRDescrambleInfo.h b/xbmc/pvr/PVRDescrambleInfo.h index 83046909c047b..ec8d5dacc6a8f 100644 --- a/xbmc/pvr/PVRDescrambleInfo.h +++ b/xbmc/pvr/PVRDescrambleInfo.h @@ -27,7 +27,7 @@ class CPVRDescrambleInfo m_info.iHops = PVR_DESCRAMBLE_INFO_NOT_AVAILABLE; } - CPVRDescrambleInfo(const PVR_DESCRAMBLE_INFO& info) + explicit CPVRDescrambleInfo(const PVR_DESCRAMBLE_INFO& info) : m_info(info), m_cardSystem(info.strCardSystem ? info.strCardSystem : ""), m_reader(info.strReader ? info.strReader : ""), diff --git a/xbmc/pvr/PVRManager.cpp b/xbmc/pvr/PVRManager.cpp index 742561e7c9f0a..24ee777ad1c73 100644 --- a/xbmc/pvr/PVRManager.cpp +++ b/xbmc/pvr/PVRManager.cpp @@ -717,7 +717,7 @@ bool CPVRManager::UpdateComponents(ManagerState stateToCheck, if (progressHandler) progressHandler->UpdateProgress(g_localizeStrings.Get(19236), 0); // Loading channels and groups - if (!m_providers->Update(newClients) || (stateToCheck != GetState())) + if (!m_providers->Update(newClients)) { CLog::LogF(LOGERROR, "Failed to load PVR providers."); m_knownClients.clear(); // start over @@ -725,7 +725,10 @@ bool CPVRManager::UpdateComponents(ManagerState stateToCheck, return false; } - if (!m_channelGroups->Update(newClients) || (stateToCheck != GetState())) + if (stateToCheck != GetState()) + return false; + + if (!m_channelGroups->Update(newClients)) { CLog::LogF(LOGERROR, "Failed to load PVR channels / groups."); m_knownClients.clear(); // start over @@ -737,7 +740,7 @@ bool CPVRManager::UpdateComponents(ManagerState stateToCheck, if (progressHandler) progressHandler->UpdateProgress(g_localizeStrings.Get(19237), 50); // Loading timers - if (!m_timers->Update(newClients) || (stateToCheck != GetState())) + if (!m_timers->Update(newClients)) { CLog::LogF(LOGERROR, "Failed to load PVR timers."); m_knownClients.clear(); // start over @@ -749,7 +752,7 @@ bool CPVRManager::UpdateComponents(ManagerState stateToCheck, if (progressHandler) progressHandler->UpdateProgress(g_localizeStrings.Get(19238), 75); // Loading recordings - if (!m_recordings->Update(newClients) || (stateToCheck != GetState())) + if (!m_recordings->Update(newClients)) { CLog::LogF(LOGERROR, "Failed to load PVR recordings."); m_knownClients.clear(); // start over diff --git a/xbmc/pvr/PVRSignalStatus.h b/xbmc/pvr/PVRSignalStatus.h index bd17d33757f46..81aac9ea8b996 100644 --- a/xbmc/pvr/PVRSignalStatus.h +++ b/xbmc/pvr/PVRSignalStatus.h @@ -25,7 +25,7 @@ class CPVRSignalStatus { } - CPVRSignalStatus(const PVR_SIGNAL_STATUS& status) + explicit CPVRSignalStatus(const PVR_SIGNAL_STATUS& status) : m_status(status), m_adapterName(status.strAdapterName ? status.strAdapterName : ""), m_adapterStatus(status.strAdapterStatus ? status.strAdapterStatus : ""), diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index b0e2ef1a5297d..8d56392567426 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -1570,7 +1570,7 @@ PVR_ERROR CPVRClient::SignalQuality(int channelUid, CPVRSignalStatus& qualityinf const PVR_ERROR error{ addon->toAddon->GetSignalStatus(addon, channelUid, &info)}; if (error == PVR_ERROR_NO_ERROR) - qualityinfo = info; + qualityinfo = CPVRSignalStatus{info}; addon->toAddon->FreeSignalStatus(addon, &info); return error; @@ -1586,7 +1586,7 @@ PVR_ERROR CPVRClient::GetDescrambleInfo(int channelUid, CPVRDescrambleInfo& desc PVR_DESCRAMBLE_INFO info{}; const PVR_ERROR error{addon->toAddon->GetDescrambleInfo(addon, channelUid, &info)}; if (error == PVR_ERROR_NO_ERROR) - descrambleinfo = info; + descrambleinfo = CPVRDescrambleInfo{info}; addon->toAddon->FreeDescrambleInfo(addon, &info); return error; From 578c96dd85fac19d330c61d31fa19b1de4afa371 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving <spiff@kodi.tv> Date: Tue, 10 Sep 2024 19:24:22 +0200 Subject: [PATCH 463/651] fixed: GetFolderThumb for archives The intention of this code was to look in the folder holding the archive. I have no idea when this broke, likely years ago.. --- xbmc/utils/ArtUtils.cpp | 8 +++++++- xbmc/utils/test/TestArtUtils.cpp | 5 +---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/xbmc/utils/ArtUtils.cpp b/xbmc/utils/ArtUtils.cpp index 06de8f4bdfed8..b1e90855fd981 100644 --- a/xbmc/utils/ArtUtils.cpp +++ b/xbmc/utils/ArtUtils.cpp @@ -158,11 +158,17 @@ std::string GetFolderThumb(const CFileItem& item, const std::string& folderJPG / { std::string strFolder = item.GetPath(); - if (item.IsStack() || URIUtils::IsInRAR(strFolder) || URIUtils::IsInZIP(strFolder)) + if (item.IsStack()) { URIUtils::GetParentPath(item.GetPath(), strFolder); } + if (URIUtils::IsInRAR(strFolder) || URIUtils::IsInZIP(strFolder)) + { + const CURL url(strFolder); + strFolder = URIUtils::GetDirectory(url.GetHostName()); + } + if (item.IsMultiPath()) strFolder = CMultiPathDirectory::GetFirstPath(item.GetPath()); diff --git a/xbmc/utils/test/TestArtUtils.cpp b/xbmc/utils/test/TestArtUtils.cpp index b2d850ce8027f..f373634e55bd5 100644 --- a/xbmc/utils/test/TestArtUtils.cpp +++ b/xbmc/utils/test/TestArtUtils.cpp @@ -138,10 +138,7 @@ const auto folder_thumb_tests = std::array{ "/home/user/bar/folder.jpg"}, FolderTest{"stack:///home/user/cd1/foo-cd1.avi , /home/user/cd2/foo-cd2.avi", "artist.jpg", "/home/user/artist.jpg"}, - FolderTest{"zip://%2fhome%2fuser%2fbar.zip/foo.avi", "cover.png", - "zip://%2fhome%2fuser%2fbar.zip/cover.png"}, - FolderTest{"rar://%2fhome%2fuser%2fbar.rar/foo.avi", "cover.png", - "rar://%2fhome%2fuser%2fbar.rar/cover.png"}, + FolderTest{"zip://%2fhome%2fuser%2fbar.zip/foo.avi", "cover.png", "/home/user/cover.png"}, FolderTest{"multipath://%2fhome%2fuser%2fbar%2f/%2fhome%2fuser%2ffoo%2f", "folder.jpg", "/home/user/bar/folder.jpg"}, }; From 1ccb8c143da754b17abb7e4e86b5b6d4cb9d7e40 Mon Sep 17 00:00:00 2001 From: Vasyl Gello <vasek.gello@gmail.com> Date: Fri, 13 Sep 2024 03:54:59 +0300 Subject: [PATCH 464/651] CRepository: avoid copying stringstream ... as per @neo1973 suggestion in #25638 Signed-off-by: Vasyl Gello <vasek.gello@gmail.com> --- xbmc/addons/Repository.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/addons/Repository.cpp b/xbmc/addons/Repository.cpp index ec9704634e6cb..2704729fa9073 100644 --- a/xbmc/addons/Repository.cpp +++ b/xbmc/addons/Repository.cpp @@ -165,7 +165,7 @@ bool CRepository::FetchChecksum(const std::string& url, ss.write(temp, read); if (read <= -1) return false; - checksum = ss.str(); + checksum = std::move(ss).str(); std::size_t pos = checksum.find_first_of(" \n"); if (pos != std::string::npos) { From d62199f93ca08cf55df381c0e77d35e60d78a215 Mon Sep 17 00:00:00 2001 From: Alfred Wingate <parona@protonmail.com> Date: Sat, 14 Sep 2024 13:46:25 +0300 Subject: [PATCH 465/651] Include missing <cstdint> includes * GCC-15 stopped implicitly including it. Bug: https://bugs.gentoo.org/938531 Signed-off-by: Alfred Wingate <parona@protonmail.com> --- xbmc/ContextMenuItem.h | 1 + xbmc/addons/AddonManager.h | 1 + xbmc/addons/IAddon.h | 1 + xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h | 2 ++ .../VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h | 1 + xbmc/guilib/FFmpegImage.h | 2 ++ xbmc/input/keymaps/remote/IRTranslator.h | 1 + xbmc/messaging/ThreadMessage.h | 1 + xbmc/settings/AdvancedSettings.h | 1 + xbmc/utils/Archive.h | 1 + 10 files changed, 12 insertions(+) diff --git a/xbmc/ContextMenuItem.h b/xbmc/ContextMenuItem.h index aee6d82e713d0..6577de3224597 100644 --- a/xbmc/ContextMenuItem.h +++ b/xbmc/ContextMenuItem.h @@ -8,6 +8,7 @@ #pragma once +#include <cstdint> #include <map> #include <memory> #include <string> diff --git a/xbmc/addons/AddonManager.h b/xbmc/addons/AddonManager.h index fd7ee70f66983..0679ffd81ff46 100644 --- a/xbmc/addons/AddonManager.h +++ b/xbmc/addons/AddonManager.h @@ -11,6 +11,7 @@ #include "threads/CriticalSection.h" #include "utils/EventStream.h" +#include <cstdint> #include <map> #include <memory> #include <mutex> diff --git a/xbmc/addons/IAddon.h b/xbmc/addons/IAddon.h index 0bc383055f80a..d242f60f0e4e2 100644 --- a/xbmc/addons/IAddon.h +++ b/xbmc/addons/IAddon.h @@ -8,6 +8,7 @@ #pragma once +#include <cstdint> #include <map> #include <memory> #include <string> diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h index 8d153ab5c2409..132b71218c701 100644 --- a/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h +++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h @@ -11,6 +11,8 @@ #include "IRetroPlayerStream.h" #include "cores/RetroPlayer/RetroPlayerTypes.h" +#include <cstdint> + extern "C" { #include <libavutil/pixfmt.h> diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h index b2c66a7e73743..4f43fc0475eea 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h +++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h @@ -10,6 +10,7 @@ #include <array> #include <cmath> +#include <cstdint> #include <memory> extern "C" { diff --git a/xbmc/guilib/FFmpegImage.h b/xbmc/guilib/FFmpegImage.h index 0f7cee380c253..8d34def513a59 100644 --- a/xbmc/guilib/FFmpegImage.h +++ b/xbmc/guilib/FFmpegImage.h @@ -9,6 +9,8 @@ #pragma once #include "iimage.h" + +#include <cstdint> #include <memory> extern "C" diff --git a/xbmc/input/keymaps/remote/IRTranslator.h b/xbmc/input/keymaps/remote/IRTranslator.h index 96eddcf153114..862fb7599c0bd 100644 --- a/xbmc/input/keymaps/remote/IRTranslator.h +++ b/xbmc/input/keymaps/remote/IRTranslator.h @@ -8,6 +8,7 @@ #pragma once +#include <cstdint> #include <map> #include <memory> #include <string> diff --git a/xbmc/messaging/ThreadMessage.h b/xbmc/messaging/ThreadMessage.h index 1a277ffcd4edb..8360d30cc1c4b 100644 --- a/xbmc/messaging/ThreadMessage.h +++ b/xbmc/messaging/ThreadMessage.h @@ -8,6 +8,7 @@ #pragma once +#include <cstdint> #include <memory> #include <string> #include <utility> diff --git a/xbmc/settings/AdvancedSettings.h b/xbmc/settings/AdvancedSettings.h index 0f4c5a7871be4..051636cb05fd4 100644 --- a/xbmc/settings/AdvancedSettings.h +++ b/xbmc/settings/AdvancedSettings.h @@ -13,6 +13,7 @@ #include "settings/lib/ISettingsHandler.h" #include "utils/SortUtils.h" +#include <cstdint> #include <set> #include <string> #include <utility> diff --git a/xbmc/utils/Archive.h b/xbmc/utils/Archive.h index a1af0c3cf87fe..341c07d779188 100644 --- a/xbmc/utils/Archive.h +++ b/xbmc/utils/Archive.h @@ -8,6 +8,7 @@ #pragma once +#include <cstdint> #include <cstring> #include <memory> #include <string> From 3490ec436585bd8965b4d90b5da7deba388405c5 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving <spiff@kodi.tv> Date: Tue, 10 Sep 2024 15:31:53 +0200 Subject: [PATCH 466/651] move GetLocalArtBaseFilename to ArtUtils --- xbmc/FileItem.cpp | 48 +------------------------------- xbmc/FileItem.h | 17 ----------- xbmc/utils/ArtUtils.cpp | 40 ++++++++++++++++++++++++++ xbmc/utils/ArtUtils.h | 9 ++++++ xbmc/utils/test/TestArtUtils.cpp | 39 ++++++++++++++++++++++++++ xbmc/video/VideoInfoScanner.cpp | 12 ++++++-- 6 files changed, 99 insertions(+), 66 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 9efa598f827b8..89b15d80a810f 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -1957,59 +1957,13 @@ std::string CFileItem::FindLocalArt(const std::string &artFile, bool useFolder) return ""; } -std::string CFileItem::GetLocalArtBaseFilename() const -{ - bool useFolder = false; - return GetLocalArtBaseFilename(useFolder); -} - -std::string CFileItem::GetLocalArtBaseFilename(bool& useFolder) const -{ - std::string strFile; - if (IsStack()) - { - std::string strPath; - URIUtils::GetParentPath(m_strPath,strPath); - strFile = URIUtils::AddFileToFolder( - strPath, URIUtils::GetFileName(CStackDirectory::GetStackedTitlePath(m_strPath))); - } - - std::string file = strFile.empty() ? m_strPath : strFile; - if (URIUtils::IsInRAR(file) || URIUtils::IsInZIP(file)) - { - std::string strPath = URIUtils::GetDirectory(file); - std::string strParent; - URIUtils::GetParentPath(strPath,strParent); - strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(file)); - } - - if (IsMultiPath()) - strFile = CMultiPathDirectory::GetFirstPath(m_strPath); - - if (IsOpticalMediaFile()) - { // optical media files should be treated like folders - useFolder = true; - strFile = GetLocalMetadataPath(); - } - else if (useFolder && !(m_bIsFolder && !IsFileFolder())) - { - file = strFile.empty() ? m_strPath : strFile; - strFile = URIUtils::GetDirectory(file); - } - - if (strFile.empty()) - strFile = GetDynPath(); - - return strFile; -} - std::string CFileItem::GetLocalArt(const std::string& artFile, bool useFolder) const { // no retrieving of empty art files from folders if (useFolder && artFile.empty()) return ""; - std::string strFile = GetLocalArtBaseFilename(useFolder); + std::string strFile = ART::GetLocalArtBaseFilename(*this, useFolder); if (strFile.empty()) // empty filepath -> nothing to find return ""; diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index afaaf47c6a286..7766982de5f83 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -397,23 +397,6 @@ class CFileItem : CPictureInfoTag* GetPictureInfoTag(); - /*! - \brief Assemble the base filename of local artwork for an item, - accounting for archives, stacks and multi-paths, and BDMV/VIDEO_TS folders. - `useFolder` is set to false - \return the path to the base filename for artwork lookup. - \sa GetLocalArt - */ - std::string GetLocalArtBaseFilename() const; - /*! - \brief Assemble the base filename of local artwork for an item, - accounting for archives, stacks and multi-paths, and BDMV/VIDEO_TS folders. - \param useFolder whether to look in the folder for the art file. Defaults to false. - \return the path to the base filename for artwork lookup. - \sa GetLocalArt - */ - std::string GetLocalArtBaseFilename(bool& useFolder) const; - /*! \brief Assemble the filename of a particular piece of local artwork for an item. No file existence check is typically performed. \param artFile the art file to search for. diff --git a/xbmc/utils/ArtUtils.cpp b/xbmc/utils/ArtUtils.cpp index b1e90855fd981..386a0f1183b61 100644 --- a/xbmc/utils/ArtUtils.cpp +++ b/xbmc/utils/ArtUtils.cpp @@ -178,6 +178,46 @@ std::string GetFolderThumb(const CFileItem& item, const std::string& folderJPG / return URIUtils::AddFileToFolder(strFolder, folderJPG); } +std::string GetLocalArtBaseFilename(const CFileItem& item, bool& useFolder) +{ + std::string strFile; + if (item.IsStack()) + { + std::string strPath; + URIUtils::GetParentPath(item.GetPath(), strPath); + strFile = URIUtils::AddFileToFolder( + strPath, URIUtils::GetFileName(CStackDirectory::GetStackedTitlePath(item.GetPath()))); + } + + std::string file = strFile.empty() ? item.GetPath() : strFile; + if (URIUtils::IsInRAR(file) || URIUtils::IsInZIP(file)) + { + std::string strPath = URIUtils::GetDirectory(file); + std::string strParent; + URIUtils::GetParentPath(strPath, strParent); + strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(file)); + } + + if (item.IsMultiPath()) + strFile = CMultiPathDirectory::GetFirstPath(item.GetPath()); + + if (item.IsOpticalMediaFile()) + { // optical media files should be treated like folders + useFolder = true; + strFile = item.GetLocalMetadataPath(); + } + else if (useFolder && !(item.m_bIsFolder && !item.IsFileFolder())) + { + file = strFile.empty() ? item.GetPath() : strFile; + strFile = URIUtils::GetDirectory(file); + } + + if (strFile.empty()) + strFile = item.GetDynPath(); + + return strFile; +} + std::string GetLocalFanart(const CFileItem& item) { if (VIDEO::IsVideoDb(item)) diff --git a/xbmc/utils/ArtUtils.h b/xbmc/utils/ArtUtils.h index 07671d28dde1c..4063f389ee707 100644 --- a/xbmc/utils/ArtUtils.h +++ b/xbmc/utils/ArtUtils.h @@ -26,6 +26,15 @@ void FillInDefaultIcon(CFileItem& item); */ std::string GetFolderThumb(const CFileItem& item, const std::string& folderJPG = "folder.jpg"); +/*! + \brief Assemble the base filename of local artwork for an item, + accounting for archives, stacks and multi-paths, and BDMV/VIDEO_TS folders. + \param useFolder whether to look in the folder for the art file. Defaults to false. + \return the path to the base filename for artwork lookup. + \sa GetLocalArt + */ +std::string GetLocalArtBaseFilename(const CFileItem& item, bool& useFolder); + /*! \brief Get the local fanart for item if it exists \return path to the local fanart for this item, or empty if none exists diff --git a/xbmc/utils/test/TestArtUtils.cpp b/xbmc/utils/test/TestArtUtils.cpp index f373634e55bd5..59d827e322d32 100644 --- a/xbmc/utils/test/TestArtUtils.cpp +++ b/xbmc/utils/test/TestArtUtils.cpp @@ -47,6 +47,32 @@ std::string unique_path(const std::string& input) return ret; } +struct ArtFilenameTest +{ + std::string path; + std::string result; + bool isFolder = false; + bool result_folder = false; + bool force_use_folder = false; +}; + +class GetLocalArtBaseFilenameTest : public testing::WithParamInterface<ArtFilenameTest>, + public testing::Test +{ +}; + +const auto local_art_filename_tests = std::array{ + ArtFilenameTest{"/home/user/foo.avi", "/home/user/foo.avi"}, + ArtFilenameTest{"stack:///home/user/foo-cd1.avi , /home/user/foo-cd2.avi", + "/home/user/foo.avi"}, + ArtFilenameTest{"zip://%2fhome%2fuser%2fbar.zip/foo.avi", "/home/user/foo.avi"}, + ArtFilenameTest{"multipath://%2fhome%2fuser%2fbar%2f/%2fhome%2fuser%2ffoo%2f", + "/home/user/bar/", true, true}, + ArtFilenameTest{"/home/user/VIDEO_TS/VIDEO_TS.IFO", "/home/user/", false, true}, + ArtFilenameTest{"/home/user/BDMV/index.bdmv", "/home/user/", false, true}, + ArtFilenameTest{"/home/user/foo.avi", "/home/user/", false, true, true}, +}; + struct FanartTest { std::string path; @@ -173,6 +199,19 @@ TEST_P(FolderThumbTest, GetFolderThumb) INSTANTIATE_TEST_SUITE_P(TestArtUtils, FolderThumbTest, testing::ValuesIn(folder_thumb_tests)); +TEST_P(GetLocalArtBaseFilenameTest, GetLocalArtBaseFilename) +{ + CFileItem item(GetParam().path, GetParam().isFolder); + bool useFolder = GetParam().force_use_folder ? true : GetParam().isFolder; + const std::string res = ART::GetLocalArtBaseFilename(item, useFolder); + EXPECT_EQ(res, GetParam().result); + EXPECT_EQ(useFolder, GetParam().result_folder); +} + +INSTANTIATE_TEST_SUITE_P(TestArtUtils, + GetLocalArtBaseFilenameTest, + testing::ValuesIn(local_art_filename_tests)); + TEST_P(GetLocalFanartTest, GetLocalFanart) { std::string path, file_path, uniq; diff --git a/xbmc/video/VideoInfoScanner.cpp b/xbmc/video/VideoInfoScanner.cpp index 9dc3588a5ce76..c02ee60282917 100644 --- a/xbmc/video/VideoInfoScanner.cpp +++ b/xbmc/video/VideoInfoScanner.cpp @@ -38,6 +38,7 @@ #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "tags/VideoInfoTagLoaderFactory.h" +#include "utils/ArtUtils.h" #include "utils/Digest.h" #include "utils/FileExtensionProvider.h" #include "utils/RegExp.h" @@ -1792,14 +1793,21 @@ namespace KODI::VIDEO { if (!pItem->SkipLocalArt()) { + bool useFolder = false; if (bApplyToDir && (content == CONTENT_MOVIES || content == CONTENT_MUSICVIDEOS)) { - std::string filename = pItem->GetLocalArtBaseFilename(); + std::string filename = ART::GetLocalArtBaseFilename(*pItem, useFolder); std::string directory = URIUtils::GetDirectory(filename); if (filename != directory) AddLocalItemArtwork(art, artTypes, directory, addAll, exactName); } - AddLocalItemArtwork(art, artTypes, pItem->GetLocalArtBaseFilename(), addAll, exactName); + + // Reset useFolder to false as GetLocalArtBaseFilename may modify it in + // the previous call. + useFolder = false; + + AddLocalItemArtwork(art, artTypes, ART::GetLocalArtBaseFilename(*pItem, useFolder), addAll, + exactName); } if (moviePartOfSet) From eebca1a5dcc1cee4a9b1cc08e5aa2b8c8e0fbadb Mon Sep 17 00:00:00 2001 From: blunden <blunden2@gmail.com> Date: Sat, 14 Sep 2024 14:51:46 +0200 Subject: [PATCH 467/651] [tools/depends][target] Bump curl 8.10.0 Also remove patch that was merged upstream --- cmake/modules/FindCurl.cmake | 4 ---- .../depends/target/curl/01-win-nghttp2-add-name.patch | 11 ----------- tools/depends/target/curl/CURL-VERSION | 4 ++-- 3 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 tools/depends/target/curl/01-win-nghttp2-add-name.patch diff --git a/cmake/modules/FindCurl.cmake b/cmake/modules/FindCurl.cmake index a26682395c853..3ce4ed5829749 100644 --- a/cmake/modules/FindCurl.cmake +++ b/cmake/modules/FindCurl.cmake @@ -34,10 +34,6 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) set(PLATFORM_LINK_LIBS crypt32.lib) endif() - set(patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/01-win-nghttp2-add-name.patch") - - generate_patchcommand("${patches}") - set(CMAKE_ARGS -DBUILD_CURL_EXE=OFF -DBUILD_SHARED_LIBS=OFF -DBUILD_STATIC_LIBS=ON diff --git a/tools/depends/target/curl/01-win-nghttp2-add-name.patch b/tools/depends/target/curl/01-win-nghttp2-add-name.patch deleted file mode 100644 index e07843010a2c9..0000000000000 --- a/tools/depends/target/curl/01-win-nghttp2-add-name.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/CMake/FindNGHTTP2.cmake -+++ b/CMake/FindNGHTTP2.cmake -@@ -25,7 +25,7 @@ - - find_path(NGHTTP2_INCLUDE_DIR "nghttp2/nghttp2.h") - --find_library(NGHTTP2_LIBRARY NAMES nghttp2) -+find_library(NGHTTP2_LIBRARY NAMES nghttp2 nghttp2_static) - - find_package_handle_standard_args(NGHTTP2 - FOUND_VAR diff --git a/tools/depends/target/curl/CURL-VERSION b/tools/depends/target/curl/CURL-VERSION index f9a42c29392c1..50facb23585c5 100644 --- a/tools/depends/target/curl/CURL-VERSION +++ b/tools/depends/target/curl/CURL-VERSION @@ -1,6 +1,6 @@ LIBNAME=curl -VERSION=8.7.1 +VERSION=8.10.0 ARCHIVE=$(LIBNAME)-$(VERSION).tar.xz -SHA512=5bbde9d5648e9226f5490fa951690aaf159149345f3a315df2ba58b2468f3e59ca32e8a49734338afc861803a4f81caac6d642a4699b72c6310ebfb1f618aad2 +SHA512=055277695ea242fcb0bf26ca6c4867a385cd578cd73ed4c5c4a020233248044c1ecaebcbaeaac47d3ffe07a41300ea5fc86396d7e812137cf75ed3e1b54ca5b2 BYPRODUCT=libcurl.a BYPRODUCT_WIN=libcurl.lib From 56aef0534ebc25fab53dcf95bf723dc181415254 Mon Sep 17 00:00:00 2001 From: Vasyl Gello <vasek.gello@gmail.com> Date: Thu, 12 Sep 2024 16:06:56 +0300 Subject: [PATCH 468/651] CURL: Treat file:// protocol as simple virtual protocol Signed-off-by: Vasyl Gello <vasek.gello@gmail.com> --- xbmc/URL.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xbmc/URL.cpp b/xbmc/URL.cpp index 3102a4d69f10c..8bc0f98c7f595 100644 --- a/xbmc/URL.cpp +++ b/xbmc/URL.cpp @@ -131,13 +131,16 @@ void CURL::Parse(std::string strURL1) // ones that come to mind are iso9660, cdda, musicdb, etc. // they are all local protocols and have no server part, port number, special options, etc. // this removes the need for special handling below. + // clang-format off if ( IsProtocol("stack") || IsProtocol("virtualpath") || IsProtocol("multipath") || IsProtocol("special") || - IsProtocol("resource") + IsProtocol("resource") || + IsProtocol("file") ) + // clang-format on { SetFileName(std::move(strURL).substr(iPos)); return; From bba351556f5323fbd97057c8014c18d098f5e037 Mon Sep 17 00:00:00 2001 From: Vasyl Gello <vasek.gello@gmail.com> Date: Thu, 12 Sep 2024 16:52:24 +0300 Subject: [PATCH 469/651] Allow HTTP response code 0 for file:// protocol Signed-off-by: Vasyl Gello <vasek.gello@gmail.com> --- xbmc/filesystem/CurlFile.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/xbmc/filesystem/CurlFile.cpp b/xbmc/filesystem/CurlFile.cpp index 0269dbfc48da8..58215701b1f4d 100644 --- a/xbmc/filesystem/CurlFile.cpp +++ b/xbmc/filesystem/CurlFile.cpp @@ -1097,7 +1097,15 @@ bool CCurlFile::Open(const CURL& url) m_httpresponse = m_state->Connect(m_bufferSize); - if (m_httpresponse <= 0 || (m_failOnError && m_httpresponse >= 400)) + long hte = 0; + + // Allow HTTP response code 0 for file:// protocol + if (url2.IsProtocol("file")) + { + hte = -1; + } + + if (m_httpresponse <= hte || (m_failOnError && m_httpresponse >= 400)) { std::string error; if (m_httpresponse >= 400 && CServiceBroker::GetLogging().CanLogComponent(LOGCURL)) From c953cfadc4ea40bf472d01ac7f87583c1d6c790d Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:32:58 +0200 Subject: [PATCH 470/651] [Windows] Enable XAudio2 sink in Windows desktop --- xbmc/cores/AudioEngine/CMakeLists.txt | 11 ++++++----- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 2 ++ xbmc/windowing/windows/WinSystemWin32.cpp | 2 ++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt index fba64eda89eea..4a2531753e221 100644 --- a/xbmc/cores/AudioEngine/CMakeLists.txt +++ b/xbmc/cores/AudioEngine/CMakeLists.txt @@ -105,17 +105,18 @@ endif() if(CORE_SYSTEM_NAME MATCHES windows) list(APPEND SOURCES Sinks/AESinkWASAPI.cpp + Sinks/AESinkXAudio.cpp Sinks/windows/AESinkFactoryWin.cpp) list(APPEND HEADERS Sinks/AESinkWASAPI.h + Sinks/AESinkXAudio.h Sinks/windows/AESinkFactoryWin.h) + if(CORE_SYSTEM_NAME STREQUAL windowsstore) - list(APPEND SOURCES Sinks/AESinkXAudio.cpp - Sinks/windows/AESinkFactoryWin10.cpp) - list(APPEND SOURCES Sinks/AESinkXAudio.h) - elseif(CORE_SYSTEM_NAME STREQUAL windows) + list(APPEND SOURCES Sinks/windows/AESinkFactoryWin10.cpp) + else() list(APPEND SOURCES Sinks/AESinkDirectSound.cpp Sinks/windows/AESinkFactoryWin32.cpp) - list(APPEND SOURCES Sinks/AESinkDirectSound.h) + list(APPEND HEADERS Sinks/AESinkDirectSound.h) endif() endif() diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index ab7f46adce5cc..8303218769f60 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -18,7 +18,9 @@ #include "utils/XTimeUtils.h" #include "utils/log.h" +#ifdef TARGET_WINDOWS_STORE #include "platform/win10/AsyncHelpers.h" +#endif #include "platform/win32/CharsetConverter.h" #include <algorithm> diff --git a/xbmc/windowing/windows/WinSystemWin32.cpp b/xbmc/windowing/windows/WinSystemWin32.cpp index 74bacc0f0a887..4f408f9b38441 100644 --- a/xbmc/windowing/windows/WinSystemWin32.cpp +++ b/xbmc/windowing/windows/WinSystemWin32.cpp @@ -16,6 +16,7 @@ #include "cores/AudioEngine/AESinkFactory.h" #include "cores/AudioEngine/Sinks/AESinkDirectSound.h" #include "cores/AudioEngine/Sinks/AESinkWASAPI.h" +#include "cores/AudioEngine/Sinks/AESinkXaudio.h" #include "filesystem/File.h" #include "filesystem/SpecialProtocol.h" #include "messaging/ApplicationMessenger.h" @@ -75,6 +76,7 @@ CWinSystemWin32::CWinSystemWin32() AE::CAESinkFactory::ClearSinks(); CAESinkDirectSound::Register(); CAESinkWASAPI::Register(); + CAESinkXAudio::Register(); CScreenshotSurfaceWindows::Register(); if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bScanIRServer) From 93b7a1f4a070eb0a85768ac1932a4df5d0880db4 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:36:32 +0200 Subject: [PATCH 471/651] XAudio: remove unused method IsUSBDevice() --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 23 ------------------- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h | 4 ++-- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 8303218769f60..4547dc512b32f 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -897,29 +897,6 @@ void CAESinkXAudio::Drain() m_running = false; } -bool CAESinkXAudio::IsUSBDevice() -{ -#if 0 // TODO - IPropertyStore *pProperty = nullptr; - PROPVARIANT varName; - PropVariantInit(&varName); - bool ret = false; - - HRESULT hr = m_pDevice->OpenPropertyStore(STGM_READ, &pProperty); - if (!SUCCEEDED(hr)) - return ret; - hr = pProperty->GetValue(PKEY_Device_EnumeratorName, &varName); - - std::string str = localWideToUtf(varName.pwszVal); - StringUtils::ToUpper(str); - ret = (str == "USB"); - PropVariantClear(&varName); - if (pProperty) - pProperty->Release(); -#endif - return false; -} - bool CAESinkXAudio::AddEndOfStreamPacket() { constexpr unsigned int frames{1}; diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h index 86158ca925b29..103b45939ef6b 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h @@ -111,8 +111,8 @@ class CAESinkXAudio : public IAESink HANDLE m_StreamEndEvent{0}; }; - bool InitializeInternal(std::string deviceId, AEAudioFormat &format); - bool IsUSBDevice(); + bool InitializeInternal(std::string deviceId, AEAudioFormat& format); + /*! * \brief Add a 1 frame long buffer with the end of stream flag to the voice. * \return true for success, false for failure From 5ac4e68df9a5897a5518aa260132ce00b91f5a81 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Fri, 26 Apr 2024 19:56:26 +0200 Subject: [PATCH 472/651] XAudio: modernize logs --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 93 ++++++++----------- 1 file changed, 41 insertions(+), 52 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 4547dc512b32f..8574c20c9da3f 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -36,12 +36,6 @@ using namespace Microsoft::WRL; extern const char *WASAPIErrToStr(HRESULT err); -#define EXIT_ON_FAILURE(hr, reason, ...) \ - if (FAILED(hr)) \ - { \ - CLog::Log(LOGERROR, reason " - {}", __VA_ARGS__, WASAPIErrToStr(hr)); \ - goto failed; \ - } #define XAUDIO_BUFFERS_IN_QUEUE 2 template <class TVoice> @@ -136,7 +130,7 @@ bool CAESinkXAudio::Initialize(AEAudioFormat &format, std::string &device) if (!InitializeInternal(device, format)) { - CLog::Log(LOGINFO, __FUNCTION__": Could not Initialize voices with that format"); + CLog::LogF(LOGINFO, "could not Initialize voices with that format"); goto failed; } @@ -150,7 +144,7 @@ bool CAESinkXAudio::Initialize(AEAudioFormat &format, std::string &device) return true; failed: - CLog::Log(LOGERROR, __FUNCTION__": XAudio initialization failed."); + CLog::LogF(LOGERROR, "XAudio initialization failed"); return true; } @@ -184,7 +178,7 @@ void CAESinkXAudio::Deinitialize() } catch (...) { - CLog::Log(LOGERROR, "{}: Invalidated source voice - Releasing", __FUNCTION__); + CLog::LogF(LOGERROR, "invalidated source voice - Releasing"); } } m_running = false; @@ -351,7 +345,7 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo hr = XAudio2Create(xaudio2.ReleaseAndGetAddressOf(), eflags); if (FAILED(hr)) { - CLog::Log(LOGDEBUG, __FUNCTION__": Failed to activate XAudio for capability testing."); + CLog::LogF(LOGDEBUG, "failed to activate XAudio for capability testing"); goto failed; } @@ -381,9 +375,9 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo if (FAILED(hr)) { - CLog::Log( - LOGINFO, __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.", - CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD_MA), details.strDescription); + CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.", + CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD_MA), + details.strDescription); } else { @@ -410,9 +404,9 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo if (FAILED(hr)) { - CLog::Log(LOGINFO, - __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.", - CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD), details.strDescription); + CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.", + CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD), + details.strDescription); } else { @@ -429,9 +423,9 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format); if (FAILED(hr)) { - CLog::Log( - LOGINFO, __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.", - CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_TRUEHD), details.strDescription); + CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.", + CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_TRUEHD), + details.strDescription); } else { @@ -453,9 +447,8 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo if (FAILED(hr)) { - CLog::Log(LOGINFO, - __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.", - CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_EAC3), details.strDescription); + CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.", + CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_EAC3), details.strDescription); } else { @@ -477,9 +470,8 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format); if (FAILED(hr)) { - CLog::Log(LOGINFO, - __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.", - "STREAM_TYPE_DTS", details.strDescription); + CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.", + "STREAM_TYPE_DTS", details.strDescription); } else { @@ -496,9 +488,8 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format); if (FAILED(hr)) { - CLog::Log(LOGINFO, - __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.", - CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_AC3), details.strDescription); + CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.", + CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_AC3), details.strDescription); } else { @@ -566,9 +557,8 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo else if (wfxex.Format.nSamplesPerSec == 192000 && add192) { deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]); - CLog::Log(LOGINFO, - __FUNCTION__ ": sample rate 192khz on device \"{}\" seems to be not supported.", - details.strDescription); + CLog::LogF(LOGINFO, "sample rate 192khz on device \"{}\" seems to be not supported.", + details.strDescription); } } SafeDestroyVoice(&mSourceVoice); @@ -603,8 +593,7 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo failed: if (FAILED(hr)) - CLog::Log(LOGERROR, __FUNCTION__ ": Failed to enumerate XAudio endpoint devices ({}).", - WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "failed to enumerate XAudio endpoint devices ({})", WASAPIErrToStr(hr)); } /// ------------------- Private utility functions ----------------------------------- @@ -658,10 +647,10 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form { if (!bdefault) { - CLog::Log(LOGINFO, - __FUNCTION__ ": Could not locate the device named \"{}\" in the list of Xaudio " - "endpoint devices. Trying the default device...", - KODI::PLATFORM::WINDOWS::FromW(device)); + CLog::LogF(LOGINFO, + "could not locate the device named \"{}\" in the list of Xaudio endpoint devices. " + "Trying the default device...", + KODI::PLATFORM::WINDOWS::FromW(device)); } // smartphone issue: providing device ID (even default ID) causes E_NOINTERFACE result @@ -670,9 +659,8 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form 0, 0, nullptr, AudioCategory_Media); if (FAILED(hr) || !pMasterVoice) { - CLog::Log(LOGINFO, - __FUNCTION__ ": Could not retrieve the default XAudio audio endpoint ({}).", - WASAPIErrToStr(hr)); + CLog::LogF(LOGINFO, "Could not retrieve the default XAudio audio endpoint ({}).", + WASAPIErrToStr(hr)); return false; } } @@ -686,7 +674,7 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form hr = m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback); if (SUCCEEDED(hr)) { - CLog::Log(LOGINFO, __FUNCTION__": Format is Supported - will attempt to Initialize"); + CLog::LogF(LOGINFO, "Format is Supported - will attempt to Initialize"); goto initialize; } @@ -694,9 +682,9 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form return false; if (CServiceBroker::GetLogging().CanLogComponent(LOGAUDIO)) - CLog::Log(LOGDEBUG, - __FUNCTION__ ": CreateSourceVoice failed ({}) - trying to find a compatible format", - WASAPIErrToStr(hr)); + CLog::LogFC(LOGDEBUG, LOGAUDIO, + "CreateSourceVoice failed ({}) - trying to find a compatible format", + WASAPIErrToStr(hr)); requestedChannels = wfxex.Format.nChannels; @@ -751,7 +739,7 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form } if (FAILED(hr)) - CLog::Log(LOGERROR, __FUNCTION__ ": creating voices failed ({})", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "creating voices failed ({})", WASAPIErrToStr(hr)); } if (closestMatch >= 0) @@ -763,7 +751,8 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form } } - CLog::Log(LOGERROR, __FUNCTION__": Unable to locate a supported output format for the device. Check the speaker settings in the control panel."); + CLog::LogF(LOGERROR, "unable to locate a supported output format for the device. Check the " + "speaker settings in the control panel."); /* We couldn't find anything supported. This should never happen */ /* unless the user set the wrong speaker setting in the control panel */ @@ -806,7 +795,7 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form hr = m_sourceVoice->Start(0, XAUDIO2_COMMIT_NOW); if (FAILED(hr)) { - CLog::Log(LOGERROR, __FUNCTION__ ": Voice start failed : {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "Voice start failed : {}", WASAPIErrToStr(hr)); CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec); CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat)); CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample); @@ -825,7 +814,7 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form m_xAudio2->GetPerformanceData(&perfData); if (!perfData.TotalSourceVoiceCount) { - CLog::Log(LOGERROR, __FUNCTION__ ": GetPerformanceData Failed : {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "GetPerformanceData Failed : {}", WASAPIErrToStr(hr)); return false; } @@ -835,9 +824,9 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form m_dwBufferLen = m_dwChunkSize * 4; m_AvgBytesPerSec = wfxex.Format.nAvgBytesPerSec; - CLog::Log(LOGINFO, __FUNCTION__ ": XAudio Sink Initialized using: {}, {}, {}", - CAEUtil::DataFormatToStr(format.m_dataFormat), wfxex.Format.nSamplesPerSec, - wfxex.Format.nChannels); + CLog::LogF(LOGINFO, "XAudio Sink Initialized using: {}, {}, {}", + CAEUtil::DataFormatToStr(format.m_dataFormat), wfxex.Format.nSamplesPerSec, + wfxex.Format.nChannels); m_sourceVoice->Stop(); @@ -891,7 +880,7 @@ void CAESinkXAudio::Drain() } catch (...) { - CLog::Log(LOGERROR, "{}: Invalidated source voice - Releasing", __FUNCTION__); + CLog::LogF(LOGERROR, "invalidated source voice - Releasing"); } } m_running = false; From c63c26ffb726e78a6dd74d7e4a45c88295f64356 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sat, 27 Apr 2024 09:54:51 +0200 Subject: [PATCH 473/651] XAudio: remove exclusion of code in debug mode --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 8574c20c9da3f..70924d1dde14b 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -244,11 +244,9 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi HRESULT hr = S_OK; DWORD flags = 0; -#ifndef _DEBUG LARGE_INTEGER timerStart; LARGE_INTEGER timerStop; LARGE_INTEGER timerFreq; -#endif XAUDIO2_BUFFER xbuffer = BuildXAudio2Buffer(data, frames, offset); @@ -276,11 +274,9 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi return frames; } -#ifndef _DEBUG /* Get clock time for latency checks */ QueryPerformanceFrequency(&timerFreq); QueryPerformanceCounter(&timerStart); -#endif /* Wait for Audio Driver to tell us it's got a buffer available */ //XAUDIO2_VOICE_STATE state; @@ -300,7 +296,6 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi if (!m_running) return 0; -#ifndef _DEBUG QueryPerformanceCounter(&timerStop); LONGLONG timerDiff = timerStop.QuadPart - timerStart.QuadPart; double timerElapsed = (double) timerDiff * 1000.0 / (double) timerFreq.QuadPart; @@ -311,14 +306,11 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi CLog::LogF(LOGDEBUG, "Possible AQ Loss: Avg. Time Waiting for Audio Driver callback : {}msec", (int)m_avgTimeWaiting); } -#endif hr = m_sourceVoice->SubmitSourceBuffer(&xbuffer); if (FAILED(hr)) { - #ifdef _DEBUG CLog::LogF(LOGERROR, "submiting buffer failed due to {}", WASAPIErrToStr(hr)); -#endif delete xbuffer.pContext; return INT_MAX; } From 55698da23d7e36543241b68e78538ed8c9485e77 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sat, 27 Apr 2024 10:27:48 +0200 Subject: [PATCH 474/651] XAudio: simplifies variables initialization --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 20 +--------- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h | 37 ++++++++++--------- 2 files changed, 20 insertions(+), 37 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 70924d1dde14b..3caae374ee848 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -50,25 +50,7 @@ inline void SafeDestroyVoice(TVoice **ppVoice) /// ----------------- CAESinkXAudio ------------------------ -CAESinkXAudio::CAESinkXAudio() : - m_masterVoice(nullptr), - m_sourceVoice(nullptr), - m_encodedChannels(0), - m_encodedSampleRate(0), - sinkReqFormat(AE_FMT_INVALID), - sinkRetFormat(AE_FMT_INVALID), - m_AvgBytesPerSec(0), - m_dwChunkSize(0), - m_dwFrameSize(0), - m_dwBufferLen(0), - m_sinkFrames(0), - m_framesInBuffers(0), - m_running(false), - m_initialized(false), - m_isSuspended(false), - m_isDirty(false), - m_uiBufferLen(0), - m_avgTimeWaiting(50) +CAESinkXAudio::CAESinkXAudio() { m_channelLayout.Reset(); diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h index 103b45939ef6b..284dde354a803 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h @@ -129,31 +129,32 @@ class CAESinkXAudio : public IAESink XAUDIO2_BUFFER BuildXAudio2Buffer(uint8_t** data, unsigned int frames, unsigned int offset); Microsoft::WRL::ComPtr<IXAudio2> m_xAudio2; - IXAudio2MasteringVoice* m_masterVoice; - IXAudio2SourceVoice* m_sourceVoice; + IXAudio2MasteringVoice* m_masterVoice{nullptr}; + IXAudio2SourceVoice* m_sourceVoice{nullptr}; VoiceCallback m_voiceCallback; AEAudioFormat m_format; - unsigned int m_encodedChannels; - unsigned int m_encodedSampleRate; + unsigned int m_encodedChannels{0}; + unsigned int m_encodedSampleRate{0}; CAEChannelInfo m_channelLayout; std::string m_device; - enum AEDataFormat sinkReqFormat; - enum AEDataFormat sinkRetFormat; + AEDataFormat sinkReqFormat{AE_FMT_INVALID}; + AEDataFormat sinkRetFormat{AE_FMT_INVALID}; - unsigned int m_uiBufferLen; /* xaudio endpoint buffer size, in frames */ - unsigned int m_AvgBytesPerSec; - unsigned int m_dwChunkSize; - unsigned int m_dwFrameSize; - unsigned int m_dwBufferLen; - uint64_t m_sinkFrames; - std::atomic<uint16_t> m_framesInBuffers; + unsigned int m_uiBufferLen{0}; // xaudio endpoint buffer size, in frames + unsigned int m_AvgBytesPerSec{0}; + unsigned int m_dwChunkSize{0}; + unsigned int m_dwFrameSize{0}; + unsigned int m_dwBufferLen{0}; + uint64_t m_sinkFrames{0}; + std::atomic<uint16_t> m_framesInBuffers{0}; - double m_avgTimeWaiting; /* time between next buffer of data from SoftAE and driver call for data */ + // time between next buffer of data from SoftAE and driver call for data + double m_avgTimeWaiting{50.0}; - bool m_running; - bool m_initialized; - bool m_isSuspended; /* sink is in a suspended state - release audio device */ - bool m_isDirty; /* sink output failed - needs re-init or new device */ + bool m_running{false}; + bool m_initialized{false}; + bool m_isSuspended{false}; // sink is in a suspended state - release audio device + bool m_isDirty{false}; // sink output failed - needs re-init or new device }; From 4b4c576c5f0ac7f017a2afe306fa12ea505ddab2 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sat, 27 Apr 2024 11:05:04 +0200 Subject: [PATCH 475/651] XAudio: simplifies Initialize() --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 31 +++++++------------ xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h | 4 --- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 3caae374ee848..817799a4ec371 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -102,32 +102,22 @@ bool CAESinkXAudio::Initialize(AEAudioFormat &format, std::string &device) return false; m_device = device; - bool bdefault = false; - HRESULT hr = S_OK; /* Save requested format */ - /* Clear returned format */ - sinkReqFormat = format.m_dataFormat; - sinkRetFormat = AE_FMT_INVALID; + AEDataFormat reqFormat = format.m_dataFormat; if (!InitializeInternal(device, format)) { - CLog::LogF(LOGINFO, "could not Initialize voices with that format"); - goto failed; + CLog::LogF(LOGINFO, "could not Initialize voices with format {}", + CAEUtil::DataFormatToStr(reqFormat)); + CLog::LogF(LOGERROR, "XAudio initialization failed"); + return false; } - format.m_frames = m_uiBufferLen; - m_format = format; - sinkRetFormat = format.m_dataFormat; - m_initialized = true; - m_isDirty = false; + m_isDirty = false; return true; - -failed: - CLog::LogF(LOGERROR, "XAudio initialization failed"); - return true; } void CAESinkXAudio::Deinitialize() @@ -792,12 +782,15 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form return false; } - m_uiBufferLen = (int)(format.m_sampleRate * 0.02); + const unsigned int bufferLen = static_cast<int>(format.m_sampleRate * 0.02); // 20 ms chunks m_dwFrameSize = wfxex.Format.nBlockAlign; - m_dwChunkSize = m_dwFrameSize * m_uiBufferLen; - m_dwBufferLen = m_dwChunkSize * 4; + m_dwChunkSize = m_dwFrameSize * bufferLen; + m_dwBufferLen = m_dwChunkSize * 4; // 80 ms buffer m_AvgBytesPerSec = wfxex.Format.nAvgBytesPerSec; + format.m_frames = bufferLen; + m_format = format; + CLog::LogF(LOGINFO, "XAudio Sink Initialized using: {}, {}, {}", CAEUtil::DataFormatToStr(format.m_dataFormat), wfxex.Format.nSamplesPerSec, wfxex.Format.nChannels); diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h index 284dde354a803..9295ec629ca60 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h @@ -139,10 +139,6 @@ class CAESinkXAudio : public IAESink CAEChannelInfo m_channelLayout; std::string m_device; - AEDataFormat sinkReqFormat{AE_FMT_INVALID}; - AEDataFormat sinkRetFormat{AE_FMT_INVALID}; - - unsigned int m_uiBufferLen{0}; // xaudio endpoint buffer size, in frames unsigned int m_AvgBytesPerSec{0}; unsigned int m_dwChunkSize{0}; unsigned int m_dwFrameSize{0}; From 78028d4ec98bc1a92249a5ba70013acc7278fe69 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sat, 27 Apr 2024 12:25:01 +0200 Subject: [PATCH 476/651] XAudio: add more debug info --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 817799a4ec371..1f927df412d94 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -797,6 +797,22 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form m_sourceVoice->Stop(); + CLog::LogF(LOGDEBUG, "Initializing XAudio with the following parameters:"); + CLog::Log(LOGDEBUG, " Audio Device : {}", KODI::PLATFORM::WINDOWS::FromW(device)); + CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec); + CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat)); + CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample); + CLog::Log(LOGDEBUG, " Valid Bits/Samp : {}", wfxex.Samples.wValidBitsPerSample); + CLog::Log(LOGDEBUG, " Channel Count : {}", wfxex.Format.nChannels); + CLog::Log(LOGDEBUG, " Block Align : {}", wfxex.Format.nBlockAlign); + CLog::Log(LOGDEBUG, " Avg. Bytes Sec : {}", wfxex.Format.nAvgBytesPerSec); + CLog::Log(LOGDEBUG, " Samples/Block : {}", wfxex.Samples.wSamplesPerBlock); + CLog::Log(LOGDEBUG, " Format cBSize : {}", wfxex.Format.cbSize); + CLog::Log(LOGDEBUG, " Channel Layout : {}", ((std::string)format.m_channelLayout)); + CLog::Log(LOGDEBUG, " Channel Mask : {}", wfxex.dwChannelMask); + CLog::Log(LOGDEBUG, " Frames : {}", format.m_frames); + CLog::Log(LOGDEBUG, " Frame Size : {}", format.m_frameSize); + return true; } From 605a78540eb9c65b0f63eaa5325b6fa97aaa101b Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sat, 27 Apr 2024 19:14:59 +0200 Subject: [PATCH 477/651] XAudio: fix devices enumeration on Windows desktop XAudio want device 'Path' to identify audio devices while WASAPI want device 'GUID' --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 15 ++++++++++----- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h | 1 - .../AudioEngine/Sinks/windows/AESinkFactoryWin.h | 1 + .../Sinks/windows/AESinkFactoryWin10.cpp | 3 +++ .../Sinks/windows/AESinkFactoryWin32.cpp | 1 + 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 1f927df412d94..6afc96b719879 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -101,8 +101,6 @@ bool CAESinkXAudio::Initialize(AEAudioFormat &format, std::string &device) if (m_initialized) return false; - m_device = device; - /* Save requested format */ AEDataFormat reqFormat = format.m_dataFormat; @@ -318,8 +316,15 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo deviceInfo.m_channels.Reset(); deviceInfo.m_dataFormats.clear(); deviceInfo.m_sampleRates.clear(); + deviceChannels.Reset(); + + for (unsigned int c = 0; c < WASAPI_SPEAKER_COUNT; c++) + { + if (details.uiChannelMask & WASAPIChannelOrder[c]) + deviceChannels += AEChannelNames[c]; + } - std::wstring deviceId = KODI::PLATFORM::WINDOWS::ToW(details.strDeviceId); + std::wstring deviceId = KODI::PLATFORM::WINDOWS::ToW(details.strDevicePath); /* Test format DTS-HD-MA */ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); @@ -528,11 +533,11 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo SafeDestroyVoice(&mSourceVoice); SafeDestroyVoice(&mMasterVoice); - deviceInfo.m_deviceName = details.strDeviceId; + deviceInfo.m_deviceName = details.strDevicePath; deviceInfo.m_displayName = details.strWinDevType.append(details.strDescription); deviceInfo.m_displayNameExtra = std::string("XAudio: ").append(details.strDescription); deviceInfo.m_deviceType = details.eDeviceType; - deviceInfo.m_channels = layoutsByChCount[details.nChannels]; + deviceInfo.m_channels = deviceChannels; /* Store the device info */ deviceInfo.m_wantsIECPassthrough = true; diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h index 9295ec629ca60..30c4fe92b2a2b 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h @@ -137,7 +137,6 @@ class CAESinkXAudio : public IAESink unsigned int m_encodedChannels{0}; unsigned int m_encodedSampleRate{0}; CAEChannelInfo m_channelLayout; - std::string m_device; unsigned int m_AvgBytesPerSec{0}; unsigned int m_dwChunkSize{0}; diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h index 61db03f5c472d..d308673b91510 100644 --- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h @@ -175,6 +175,7 @@ static const sampleFormat testFormats[] = { {KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 32 struct RendererDetail { + std::string strDevicePath; std::string strDeviceId; std::string strDescription; std::string strWinDevType; diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp index 23d79e5a9dcf1..2492f02279495 100644 --- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp @@ -101,6 +101,9 @@ std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails() details.strDescription = KODI::PLATFORM::WINDOWS::FromW(devInfo.Name().c_str()); details.strDeviceId = KODI::PLATFORM::WINDOWS::FromW(devInfo.Id().c_str()); + // on Windows UWP device Id is same as Path + details.strDevicePath = details.strDeviceId; + details.bDefault = (devInfo.Id() == defaultId); list.push_back(details); diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp index d5869c5e2e312..e7d81282ae8af 100644 --- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp @@ -147,6 +147,7 @@ std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails() if (wstrDDID.compare(pwszID) == 0) details.bDefault = true; + details.strDevicePath = KODI::PLATFORM::WINDOWS::FromW(pwszID); CoTaskMemFree(pwszID); } From 6e79be32773e7e094690d309cba81cea53ee4f82 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sat, 27 Apr 2024 20:46:24 +0200 Subject: [PATCH 478/651] XAudio: remove updated but not read member variables --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 12 +++--------- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h | 3 --- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 6afc96b719879..fb1be8af2c993 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -52,8 +52,6 @@ inline void SafeDestroyVoice(TVoice **ppVoice) CAESinkXAudio::CAESinkXAudio() { - m_channelLayout.Reset(); - HRESULT hr = XAudio2Create(m_xAudio2.ReleaseAndGetAddressOf(), 0); if (FAILED(hr)) { @@ -729,13 +727,9 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form initialize: - CAESinkFactoryWin::AEChannelsFromSpeakerMask(m_channelLayout, wfxex.dwChannelMask); - format.m_channelLayout = m_channelLayout; - - /* When the stream is raw, the values in the format structure are set to the link */ - /* parameters, so store the encoded stream values here for the IsCompatible function */ - m_encodedChannels = wfxex.Format.nChannels; - m_encodedSampleRate = (format.m_dataFormat == AE_FMT_RAW) ? format.m_streamInfo.m_sampleRate : format.m_sampleRate; + CAEChannelInfo channelLayout; + CAESinkFactoryWin::AEChannelsFromSpeakerMask(channelLayout, wfxex.dwChannelMask); + format.m_channelLayout = channelLayout; /* Set up returned sink format for engine */ if (format.m_dataFormat != AE_FMT_RAW) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h index 30c4fe92b2a2b..da9c2dc85b31d 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h @@ -134,9 +134,6 @@ class CAESinkXAudio : public IAESink VoiceCallback m_voiceCallback; AEAudioFormat m_format; - unsigned int m_encodedChannels{0}; - unsigned int m_encodedSampleRate{0}; - CAEChannelInfo m_channelLayout; unsigned int m_AvgBytesPerSec{0}; unsigned int m_dwChunkSize{0}; From 7ec6731df4d9694a8d62a0386286764bba29a3be Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sun, 28 Apr 2024 19:00:31 +0200 Subject: [PATCH 479/651] XAudio: remove unused static method 'rescale_u64' --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index fb1be8af2c993..592a287115a00 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -157,14 +157,6 @@ void CAESinkXAudio::Deinitialize() m_initialized = false; } -/** - * @brief rescale uint64_t without overflowing on large values - */ -static uint64_t rescale_u64(uint64_t val, uint64_t num, uint64_t den) -{ - return ((val / den) * num) + (((val % den) * num) / den); -} - void CAESinkXAudio::GetDelay(AEDelayStatus& status) { HRESULT hr = S_OK; From 0ecdbe2505b1856438cf456f9363cf5fbc6b829a Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:28:35 +0200 Subject: [PATCH 480/651] XAudio: remove tests for PT formats --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 145 +----------------- 1 file changed, 6 insertions(+), 139 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 592a287115a00..729aae7a9ec23 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -316,151 +316,18 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo std::wstring deviceId = KODI::PLATFORM::WINDOWS::ToW(details.strDevicePath); - /* Test format DTS-HD-MA */ - wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); - wfxex.Format.nSamplesPerSec = 192000; - wfxex.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND; - wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD; - wfxex.Format.wBitsPerSample = 16; - wfxex.Samples.wValidBitsPerSample = 16; - wfxex.Format.nChannels = 8; - wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3); - wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign; - - hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec, - 0, deviceId.c_str(), nullptr, AudioCategory_Media); - hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format); - - if (FAILED(hr)) - { - CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.", - CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD_MA), - details.strDescription); - } - else - { - deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA); - add192 = true; - } - SafeDestroyVoice(&mSourceVoice); - - /* Test format DTS-HD-HR */ - wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); - wfxex.Format.nSamplesPerSec = 192000; - wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1; - wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD; - wfxex.Format.wBitsPerSample = 16; - wfxex.Samples.wValidBitsPerSample = 16; - wfxex.Format.nChannels = 2; - wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3); - wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign; - - hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec, - 0, deviceId.c_str(), nullptr, AudioCategory_Media); - hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format); - - if (FAILED(hr)) - { - CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.", - CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD), - details.strDescription); - } - else - { - deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD); - add192 = true; - } - SafeDestroyVoice(&mSourceVoice); - - /* Test format Dolby TrueHD */ - wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP; - wfxex.Format.nChannels = 8; - wfxex.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND; - - hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format); - if (FAILED(hr)) - { - CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.", - CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_TRUEHD), - details.strDescription); - } - else - { - deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_TRUEHD); - add192 = true; - } - - /* Test format Dolby EAC3 */ - wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS; - wfxex.Format.nChannels = 2; - wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3); - wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign; - - SafeDestroyVoice(&mSourceVoice); - SafeDestroyVoice(&mMasterVoice); - hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec, - 0, deviceId.c_str(), nullptr, AudioCategory_Media); - hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format); - - if (FAILED(hr)) - { - CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.", - CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_EAC3), details.strDescription); - } - else - { - deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3); - add192 = true; - } - - /* Test format DTS */ - wfxex.Format.nSamplesPerSec = 48000; - wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1; - wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS; - wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3); - wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign; - - SafeDestroyVoice(&mSourceVoice); - SafeDestroyVoice(&mMasterVoice); - hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec, - 0, deviceId.c_str(), nullptr, AudioCategory_Media); - hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format); - if (FAILED(hr)) - { - CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.", - "STREAM_TYPE_DTS", details.strDescription); - } - else - { - deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE); - deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048); - deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024); - deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512); - } - SafeDestroyVoice(&mSourceVoice); - - /* Test format Dolby AC3 */ - wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL; - - hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format); - if (FAILED(hr)) - { - CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.", - CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_AC3), details.strDescription); - } - else - { - deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3); - } - /* Test format for PCM format iteration */ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + wfxex.Format.nSamplesPerSec = 48000; + wfxex.Format.nChannels = 2; wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO; wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, + wfxex.Format.nSamplesPerSec, 0, deviceId.c_str(), nullptr, + AudioCategory_Media); + for (int p = AE_FMT_FLOAT; p > AE_FMT_INVALID; p--) { if (p < AE_FMT_FLOAT) From 9f797697e7d9f98f4244c49d5c64646e24b5a401 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:42:54 +0200 Subject: [PATCH 481/651] XAudio: EnumerateDevicesEx() code improvements --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 729aae7a9ec23..3802531296664 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -283,22 +283,21 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force) { - HRESULT hr = S_OK, hr2 = S_OK; + HRESULT hr = S_OK; CAEDeviceInfo deviceInfo; CAEChannelInfo deviceChannels; WAVEFORMATEXTENSIBLE wfxex = {}; - bool add192 = false; - - UINT32 eflags = 0;// XAUDIO2_DEBUG_ENGINE; - + UINT32 eflags = 0; // XAUDIO2_DEBUG_ENGINE; IXAudio2MasteringVoice* mMasterVoice = nullptr; IXAudio2SourceVoice* mSourceVoice = nullptr; Microsoft::WRL::ComPtr<IXAudio2> xaudio2; + hr = XAudio2Create(xaudio2.ReleaseAndGetAddressOf(), eflags); if (FAILED(hr)) { - CLog::LogF(LOGDEBUG, "failed to activate XAudio for capability testing"); - goto failed; + CLog::LogF(LOGERROR, "failed to activate XAudio for capability testing ({})", + WASAPIErrToStr(hr)); + return; } for(RendererDetail& details : CAESinkFactoryWin::GetRendererDetails()) @@ -375,18 +374,15 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo wfxex.Format.nSamplesPerSec = WASAPISampleRates[j]; wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign; - hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec, - 0, deviceId.c_str(), nullptr, AudioCategory_Media); + xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, + wfxex.Format.nSamplesPerSec, 0, deviceId.c_str(), nullptr, + AudioCategory_Media); hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format); + if (SUCCEEDED(hr)) deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]); - else if (wfxex.Format.nSamplesPerSec == 192000 && add192) - { - deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]); - CLog::LogF(LOGINFO, "sample rate 192khz on device \"{}\" seems to be not supported.", - details.strDescription); - } } + SafeDestroyVoice(&mSourceVoice); SafeDestroyVoice(&mMasterVoice); @@ -415,11 +411,6 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo deviceInfoList.push_back(deviceInfo); } } - -failed: - - if (FAILED(hr)) - CLog::LogF(LOGERROR, "failed to enumerate XAudio endpoint devices ({})", WASAPIErrToStr(hr)); } /// ------------------- Private utility functions ----------------------------------- From a3d854cfd968aef4c38fc0cb2c7f7ce79001e1a3 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Wed, 1 May 2024 09:32:25 +0200 Subject: [PATCH 482/651] XAudio: remove unused variables in GetDelay() --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 3802531296664..9ceb54857037e 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -159,10 +159,6 @@ void CAESinkXAudio::Deinitialize() void CAESinkXAudio::GetDelay(AEDelayStatus& status) { - HRESULT hr = S_OK; - uint64_t pos = 0, tick = 0; - int retries = 0; - if (!m_initialized) { status.SetDelay(0.0); @@ -172,8 +168,7 @@ void CAESinkXAudio::GetDelay(AEDelayStatus& status) XAUDIO2_VOICE_STATE state; m_sourceVoice->GetState(&state, 0); - double delay = (double)(m_sinkFrames - state.SamplesPlayed) / m_format.m_sampleRate; - status.SetDelay(delay); + status.SetDelay(static_cast<double>(m_sinkFrames - state.SamplesPlayed) / m_format.m_sampleRate); return; } From 3b8bb020432b67a839a94d9d648e6b08ee2c6ac3 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Wed, 1 May 2024 10:10:50 +0200 Subject: [PATCH 483/651] XAudio: misc code improvements --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 9ceb54857037e..5522db20b6c6c 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -13,9 +13,6 @@ #include "cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h" #include "cores/AudioEngine/Utils/AEDeviceInfo.h" #include "cores/AudioEngine/Utils/AEUtil.h" -#include "utils/StringUtils.h" -#include "utils/TimeUtils.h" -#include "utils/XTimeUtils.h" #include "utils/log.h" #ifdef TARGET_WINDOWS_STORE @@ -34,9 +31,12 @@ using namespace Microsoft::WRL; -extern const char *WASAPIErrToStr(HRESULT err); +namespace +{ +constexpr int XAUDIO_BUFFERS_IN_QUEUE = 2; +} // namespace -#define XAUDIO_BUFFERS_IN_QUEUE 2 +extern const char* WASAPIErrToStr(HRESULT err); template <class TVoice> inline void SafeDestroyVoice(TVoice **ppVoice) @@ -48,8 +48,6 @@ inline void SafeDestroyVoice(TVoice **ppVoice) } } -/// ----------------- CAESinkXAudio ------------------------ - CAESinkXAudio::CAESinkXAudio() { HRESULT hr = XAudio2Create(m_xAudio2.ReleaseAndGetAddressOf(), 0); @@ -57,7 +55,7 @@ CAESinkXAudio::CAESinkXAudio() { CLog::LogF(LOGERROR, "XAudio initialization failed."); } -#ifdef _DEBUG +#ifdef _DEBUG else { XAUDIO2_DEBUG_CONFIGURATION config = {}; @@ -67,7 +65,7 @@ CAESinkXAudio::CAESinkXAudio() config.LogFunctionName = true; m_xAudio2->SetDebugConfiguration(&config, 0); } -#endif // _DEBUG +#endif // _DEBUG } CAESinkXAudio::~CAESinkXAudio() @@ -177,7 +175,7 @@ double CAESinkXAudio::GetCacheTotal() if (!m_initialized) return 0.0; - return XAUDIO_BUFFERS_IN_QUEUE * m_format.m_frames / (double)m_format.m_sampleRate; + return static_cast<double>(XAUDIO_BUFFERS_IN_QUEUE * m_format.m_frames) / m_format.m_sampleRate; } double CAESinkXAudio::GetLatency() @@ -188,7 +186,7 @@ double CAESinkXAudio::GetLatency() XAUDIO2_PERFORMANCE_DATA perfData; m_xAudio2->GetPerformanceData(&perfData); - return perfData.CurrentLatencyInSamples / (double) m_format.m_sampleRate; + return static_cast<double>(perfData.CurrentLatencyInSamples) / m_format.m_sampleRate; } unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset) @@ -197,7 +195,6 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi return 0; HRESULT hr = S_OK; - DWORD flags = 0; LARGE_INTEGER timerStart; LARGE_INTEGER timerStop; @@ -234,8 +231,6 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi QueryPerformanceCounter(&timerStart); /* Wait for Audio Driver to tell us it's got a buffer available */ - //XAUDIO2_VOICE_STATE state; - //while (m_sourceVoice->GetState(&state), state.BuffersQueued >= XAUDIO_BUFFERS_IN_QUEUE) while (m_format.m_frames * XAUDIO_BUFFERS_IN_QUEUE <= m_framesInBuffers.load()) { DWORD eventAudioCallback; @@ -252,8 +247,8 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi return 0; QueryPerformanceCounter(&timerStop); - LONGLONG timerDiff = timerStop.QuadPart - timerStart.QuadPart; - double timerElapsed = (double) timerDiff * 1000.0 / (double) timerFreq.QuadPart; + const LONGLONG timerDiff = timerStop.QuadPart - timerStart.QuadPart; + const double timerElapsed = static_cast<double>(timerDiff) * 1000.0 / timerFreq.QuadPart; m_avgTimeWaiting += (timerElapsed - m_avgTimeWaiting) * 0.5; if (m_avgTimeWaiting < 3.0) @@ -308,7 +303,7 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo deviceChannels += AEChannelNames[c]; } - std::wstring deviceId = KODI::PLATFORM::WINDOWS::ToW(details.strDevicePath); + const std::wstring deviceId = KODI::PLATFORM::WINDOWS::ToW(details.strDevicePath); /* Test format for PCM format iteration */ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); @@ -408,11 +403,9 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo } } -/// ------------------- Private utility functions ----------------------------------- - bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &format) { - std::wstring device = KODI::PLATFORM::WINDOWS::ToW(deviceId); + const std::wstring device = KODI::PLATFORM::WINDOWS::ToW(deviceId); WAVEFORMATEXTENSIBLE wfxex = {}; if ( format.m_dataFormat <= AE_FMT_FLOAT @@ -444,7 +437,7 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; } - bool bdefault = StringUtils::EndsWithNoCase(deviceId, std::string("default")); + const bool bdefault = deviceId.find("default") != std::string::npos; HRESULT hr; IXAudio2MasteringVoice* pMasterVoice = nullptr; From 7101462749e69a19d95d176d05f9015245e1331e Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:55:44 +0200 Subject: [PATCH 484/651] XAudio: remove initialized but unused variables --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 7 +------ xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h | 4 ---- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 5522db20b6c6c..ec56a4369c794 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -619,13 +619,8 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form return false; } - const unsigned int bufferLen = static_cast<int>(format.m_sampleRate * 0.02); // 20 ms chunks - m_dwFrameSize = wfxex.Format.nBlockAlign; - m_dwChunkSize = m_dwFrameSize * bufferLen; - m_dwBufferLen = m_dwChunkSize * 4; // 80 ms buffer - m_AvgBytesPerSec = wfxex.Format.nAvgBytesPerSec; + format.m_frames = static_cast<int>(format.m_sampleRate * 0.02); // 20 ms chunks - format.m_frames = bufferLen; m_format = format; CLog::LogF(LOGINFO, "XAudio Sink Initialized using: {}, {}, {}", diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h index da9c2dc85b31d..1440ed179a0f9 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h @@ -135,10 +135,6 @@ class CAESinkXAudio : public IAESink AEAudioFormat m_format; - unsigned int m_AvgBytesPerSec{0}; - unsigned int m_dwChunkSize{0}; - unsigned int m_dwFrameSize{0}; - unsigned int m_dwBufferLen{0}; uint64_t m_sinkFrames{0}; std::atomic<uint16_t> m_framesInBuffers{0}; From 12a1a851fa4d4b57e2498243d911b3444a443c00 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:04:50 +0200 Subject: [PATCH 485/651] XAudio: query performance counter frequency only at initialization --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 7 ++++--- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index ec56a4369c794..1c6e3a2f36de1 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -66,6 +66,9 @@ CAESinkXAudio::CAESinkXAudio() m_xAudio2->SetDebugConfiguration(&config, 0); } #endif // _DEBUG + + // Get performance counter frequency for latency calculations + QueryPerformanceFrequency(&m_timerFreq); } CAESinkXAudio::~CAESinkXAudio() @@ -198,7 +201,6 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi LARGE_INTEGER timerStart; LARGE_INTEGER timerStop; - LARGE_INTEGER timerFreq; XAUDIO2_BUFFER xbuffer = BuildXAudio2Buffer(data, frames, offset); @@ -227,7 +229,6 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi } /* Get clock time for latency checks */ - QueryPerformanceFrequency(&timerFreq); QueryPerformanceCounter(&timerStart); /* Wait for Audio Driver to tell us it's got a buffer available */ @@ -248,7 +249,7 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi QueryPerformanceCounter(&timerStop); const LONGLONG timerDiff = timerStop.QuadPart - timerStart.QuadPart; - const double timerElapsed = static_cast<double>(timerDiff) * 1000.0 / timerFreq.QuadPart; + const double timerElapsed = static_cast<double>(timerDiff) * 1000.0 / m_timerFreq.QuadPart; m_avgTimeWaiting += (timerElapsed - m_avgTimeWaiting) * 0.5; if (m_avgTimeWaiting < 3.0) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h index 1440ed179a0f9..f398ca9b0e32f 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h @@ -145,4 +145,6 @@ class CAESinkXAudio : public IAESink bool m_initialized{false}; bool m_isSuspended{false}; // sink is in a suspended state - release audio device bool m_isDirty{false}; // sink output failed - needs re-init or new device + + LARGE_INTEGER m_timerFreq{}; // performance counter frequency for latency calculations }; From 11b6b242c7390dedad0dfad883ab8bc530846c1e Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 16 Sep 2024 11:20:36 +0200 Subject: [PATCH 486/651] [PVR] EPG search: Fix channel group matching. --- xbmc/pvr/epg/EpgSearchFilter.cpp | 18 ++++++------------ xbmc/pvr/epg/EpgSearchFilter.h | 3 --- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/xbmc/pvr/epg/EpgSearchFilter.cpp b/xbmc/pvr/epg/EpgSearchFilter.cpp index 0af425fd2a54d..2425c0a1c5915 100644 --- a/xbmc/pvr/epg/EpgSearchFilter.cpp +++ b/xbmc/pvr/epg/EpgSearchFilter.cpp @@ -52,8 +52,6 @@ void CPVREpgSearchFilter::Reset() m_bIgnorePresentTimers = true; m_bIgnorePresentRecordings = true; - m_groupIdMatches.reset(); - m_iDatabaseId = -1; m_title.clear(); m_lastExecutedDateTime.SetValid(false); @@ -192,7 +190,6 @@ void CPVREpgSearchFilter::SetChannelGroupID(int iChannelGroupID) if (m_iChannelGroupID != iChannelGroupID) { m_iChannelGroupID = iChannelGroupID; - m_groupIdMatches.reset(); m_bChanged = true; } } @@ -372,17 +369,14 @@ bool CPVREpgSearchFilter::MatchChannelGroup(const std::shared_ptr<const CPVREpgI { if (m_iChannelGroupID != -1) { - if (!m_groupIdMatches.has_value()) + const std::shared_ptr<const CPVRChannelGroupsContainer> groups{ + CServiceBroker::GetPVRManager().ChannelGroups()}; + if (groups) { - const std::shared_ptr<const CPVRChannelGroup> group = CServiceBroker::GetPVRManager() - .ChannelGroups() - ->Get(m_bIsRadio) - ->GetById(m_iChannelGroupID); - m_groupIdMatches = - group && (group->GetByUniqueID({tag->ClientID(), tag->UniqueChannelID()}) != nullptr); + const std::shared_ptr<const CPVRChannelGroup> group{ + groups->Get(m_bIsRadio)->GetById(m_iChannelGroupID)}; + return group && (group->GetByUniqueID({tag->ClientID(), tag->UniqueChannelID()}) != nullptr); } - - return *m_groupIdMatches; } return true; diff --git a/xbmc/pvr/epg/EpgSearchFilter.h b/xbmc/pvr/epg/EpgSearchFilter.h index cf937b426a3d1..f6cca5cf0c056 100644 --- a/xbmc/pvr/epg/EpgSearchFilter.h +++ b/xbmc/pvr/epg/EpgSearchFilter.h @@ -13,7 +13,6 @@ #include "pvr/epg/EpgSearchData.h" #include <memory> -#include <optional> #include <string> #include <vector> @@ -166,8 +165,6 @@ namespace PVR bool m_bIgnorePresentTimers; /*!< True to ignore currently present timers (future recordings), false if not */ bool m_bIgnorePresentRecordings; /*!< True to ignore currently active recordings, false if not */ - mutable std::optional<bool> m_groupIdMatches; - int m_iDatabaseId = -1; std::string m_title; std::string m_iconPath; From c3ba2817d431da7382b2528d82b10b23bdb0afa6 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 16 Sep 2024 11:59:17 +0200 Subject: [PATCH 487/651] [PVR] EPG search: Fix support for empty search phrase (only "" given). --- xbmc/pvr/epg/EpgDatabase.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xbmc/pvr/epg/EpgDatabase.cpp b/xbmc/pvr/epg/EpgDatabase.cpp index c0550f467ab65..7c17c0390993f 100644 --- a/xbmc/pvr/epg/EpgDatabase.cpp +++ b/xbmc/pvr/epg/EpgDatabase.cpp @@ -562,6 +562,8 @@ class CSearchTermConverter public: explicit CSearchTermConverter(const std::string& strSearchTerm) { Parse(strSearchTerm); } + bool HasSearchTerm() const { return !m_fragments.empty(); } + std::string ToSQL(const std::string& strFieldName) const { std::string result = "("; @@ -754,10 +756,9 @@ std::vector<std::shared_ptr<CPVREpgInfoTag>> CPVREpgDatabase::GetEpgTags( // search term ///////////////////////////////////////////////////////////////////////////////////////////// - if (!searchData.m_strSearchTerm.empty()) + const CSearchTermConverter conv{searchData.m_strSearchTerm}; + if (conv.HasSearchTerm()) { - const CSearchTermConverter conv(searchData.m_strSearchTerm); - // title std::string strWhere = conv.ToSQL("sTitle"); From 51bef9501591cc8a753c91f97b8f554ad73049cb Mon Sep 17 00:00:00 2001 From: sarbes <sarbes@kodi.tv> Date: Tue, 17 Sep 2024 22:21:51 +0200 Subject: [PATCH 488/651] Add single/dual channel texture support to XBT textures (#25714) Add single/dual channel texture support to XBT textures --- .../TexturePacker/src/TexturePacker.cpp | 167 ++++++++++++++---- .../TexturePacker/src/decoder/IDecoder.h | 7 +- xbmc/filesystem/XbtFile.cpp | 37 ++++ xbmc/filesystem/XbtFile.h | 4 + xbmc/guilib/Texture.cpp | 80 ++++++++- xbmc/guilib/Texture.h | 28 +++ xbmc/guilib/TextureBase.cpp | 74 ++++++++ xbmc/guilib/TextureBase.h | 12 +- xbmc/guilib/TextureBundleXBT.cpp | 14 +- xbmc/guilib/TextureGL.cpp | 7 + xbmc/guilib/TextureGL.h | 5 + xbmc/guilib/TextureGLES.cpp | 29 ++- xbmc/guilib/TextureGLES.h | 1 + xbmc/guilib/XBTF.cpp | 24 ++- xbmc/guilib/XBTF.h | 12 +- xbmc/guilib/XBTFReader.cpp | 17 +- 16 files changed, 466 insertions(+), 52 deletions(-) diff --git a/tools/depends/native/TexturePacker/src/TexturePacker.cpp b/tools/depends/native/TexturePacker/src/TexturePacker.cpp index 5c8304c5cb832..9d4f270b23c0e 100644 --- a/tools/depends/native/TexturePacker/src/TexturePacker.cpp +++ b/tools/depends/native/TexturePacker/src/TexturePacker.cpp @@ -56,40 +56,30 @@ namespace { -const char *GetFormatString(unsigned int format) +const char* GetFormatString(KD_TEX_FMT format) { switch (format) { - case XB_FMT_DXT1: - return "DXT1 "; - case XB_FMT_DXT3: - return "DXT3 "; - case XB_FMT_DXT5: - return "DXT5 "; - case XB_FMT_DXT5_YCoCg: - return "YCoCg"; - case XB_FMT_A8R8G8B8: - return "ARGB "; - case XB_FMT_A8: - return "A8 "; - default: - return "?????"; + case KD_TEX_FMT_SDR_R8: + return "R8 "; + case KD_TEX_FMT_SDR_RG8: + return "RG8 "; + case KD_TEX_FMT_SDR_RGBA8: + return "RGBA8"; + case KD_TEX_FMT_SDR_BGRA8: + return "BGRA8"; + default: + return "?????"; } } -bool HasAlpha(unsigned char* argb, unsigned int width, unsigned int height) -{ - unsigned char* p = argb + 3; // offset of alpha - for (unsigned int i = 0; i < 4 * width * height; i += 4) - { - if (p[i] != 0xff) - return true; - } - return false; -} - void Usage() { + puts("Texture Packer Version 3"); + puts(""); + puts("Tool to pack XBT 3 texture files, used in Kodi Piers (v22)."); + puts("Accepts the following file formats as input: PNG (preferred), JPG and GIF."); + puts(""); puts("Usage:"); puts(" -help Show this screen."); puts(" -input <dir> Input directory. Default: current dir"); @@ -122,6 +112,10 @@ class TexturePacker bool CheckDupe(MD5Context* ctx, unsigned int pos); + void ConvertToSingleChannel(RGBAImage& image, uint32_t channel); + void ConvertToDualChannel(RGBAImage& image); + void ReduceChannels(RGBAImage& image); + DecoderManager decoderManager; std::map<std::string, unsigned int> m_hashes; @@ -196,12 +190,13 @@ CXBTFFrame TexturePacker::CreateXBTFFrame(DecodedFrame& decodedFrame, CXBTFWrite const unsigned int delay = decodedFrame.delay; const unsigned int width = decodedFrame.rgbaImage.width; const unsigned int height = decodedFrame.rgbaImage.height; - const unsigned int size = width * height * 4; - const XB_FMT format = XB_FMT_A8R8G8B8; + const uint32_t bpp = decodedFrame.rgbaImage.bbp; + const unsigned int size = width * height * (bpp / 8); + const uint32_t format = static_cast<uint32_t>(decodedFrame.rgbaImage.textureFormat) | + static_cast<uint32_t>(decodedFrame.rgbaImage.textureAlpha) | + static_cast<uint32_t>(decodedFrame.rgbaImage.textureSwizzle); unsigned char* data = (unsigned char*)decodedFrame.rgbaImage.pixels.data(); - const bool hasAlpha = HasAlpha(data, width, height); - CXBTFFrame frame; lzo_uint packedSize = size; @@ -246,7 +241,7 @@ CXBTFFrame TexturePacker::CreateXBTFFrame(DecodedFrame& decodedFrame, CXBTFWrite frame.SetUnpackedSize(size); frame.SetWidth(width); frame.SetHeight(height); - frame.SetFormat(hasAlpha ? format : static_cast<XB_FMT>(format | XB_FMT_OPAQUE)); + frame.SetFormat(format); frame.SetDuration(delay); return frame; } @@ -278,6 +273,111 @@ bool TexturePacker::CheckDupe(MD5Context* ctx, return false; } +void TexturePacker::ConvertToSingleChannel(RGBAImage& image, uint32_t channel) +{ + uint32_t size = (image.width * image.height); + for (uint32_t i = 0; i < size; i++) + { + image.pixels[i] = image.pixels[i * 4 + channel]; + } + + image.textureFormat = KD_TEX_FMT_SDR_R8; + + image.bbp = 8; + image.pitch = 1 * image.width; +} + +void TexturePacker::ConvertToDualChannel(RGBAImage& image) +{ + uint32_t size = (image.width * image.height); + for (uint32_t i = 0; i < size; i++) + { + image.pixels[i * 2] = image.pixels[i * 4]; + image.pixels[i * 2 + 1] = image.pixels[i * 4 + 3]; + } + image.textureFormat = KD_TEX_FMT_SDR_RG8; + image.bbp = 16; + image.pitch = 2 * image.width; +} + +void TexturePacker::ReduceChannels(RGBAImage& image) +{ + if (image.textureFormat != KD_TEX_FMT_SDR_BGRA8) + return; + + uint32_t size = (image.width * image.height); + uint8_t red = image.pixels[0]; + uint8_t green = image.pixels[1]; + uint8_t blue = image.pixels[2]; + uint8_t alpha = image.pixels[3]; + bool uniformRed = true; + bool uniformGreen = true; + bool uniformBlue = true; + bool uniformAlpha = true; + bool isGrey = true; + bool isIntensity = true; + + // Checks each pixel for various properties. + for (uint32_t i = 0; i < size; i++) + { + if (image.pixels[i * 4] != red) + uniformRed = false; + if (image.pixels[i * 4 + 1] != green) + uniformGreen = false; + if (image.pixels[i * 4 + 2] != blue) + uniformBlue = false; + if (image.pixels[i * 4 + 3] != alpha) + uniformAlpha = false; + if (image.pixels[i * 4] != image.pixels[i * 4 + 1] || + image.pixels[i * 4] != image.pixels[i * 4 + 2]) + isGrey = false; + if (image.pixels[i * 4] != image.pixels[i * 4 + 1] || + image.pixels[i * 4] != image.pixels[i * 4 + 2] || + image.pixels[i * 4] != image.pixels[i * 4 + 3]) + isIntensity = false; + } + + if (uniformAlpha && alpha != 0xff) + printf("WARNING: uniform alpha detected! Consider using diffusecolor!\n"); + + bool isWhite = red == 0xff && green == 0xff && blue == 0xff; + if (uniformRed && uniformGreen && uniformBlue && !isWhite) + printf("WARNING: uniform color detected! Consider using diffusecolor!\n"); + + if (isIntensity) + { + // this is an intensity (GL_INTENSITY) texture + ConvertToSingleChannel(image, 0); + image.textureSwizzle = KD_TEX_SWIZ_RRRR; + } + else if (uniformAlpha && alpha == 0xff) + { + // we have a opaque texture, L or RGBX + if (isGrey) + { + ConvertToSingleChannel(image, 1); + image.textureSwizzle = KD_TEX_SWIZ_RRR1; + } + image.textureAlpha = KD_TEX_ALPHA_OPAQUE; + } + else if (uniformRed && uniformGreen && uniformBlue && isWhite) + { + // an alpha only texture + ConvertToSingleChannel(image, 3); + image.textureSwizzle = KD_TEX_SWIZ_111R; + } + else if (isGrey) + { + // a LA texture + ConvertToDualChannel(image); + image.textureSwizzle = KD_TEX_SWIZ_RRRG; + } + else + { + // BGRA + } +} + int TexturePacker::createBundle(const std::string& InputDir, const std::string& OutputFile) { CXBTFWriter writer(OutputFile); @@ -312,6 +412,9 @@ int TexturePacker::createBundle(const std::string& InputDir, const std::string& continue; } + for (unsigned int j = 0; j < frames.frameList.size(); j++) + ReduceChannels(frames.frameList[j].rgbaImage); + printf("%s\n", output.c_str()); bool skip=false; if (m_dupecheck) @@ -342,7 +445,7 @@ int TexturePacker::createBundle(const std::string& InputDir, const std::string& file.GetFrames().push_back(frame); printf(" frame %4i (delay:%4i) %s%c (%d,%d @ %" PRIu64 " bytes)\n", - j, frame.GetDuration(), GetFormatString(frame.GetFormat()), + j, frame.GetDuration(), GetFormatString(frame.GetKDFormat()), frame.HasAlpha() ? ' ' : '*', frame.GetWidth(), frame.GetHeight(), frame.GetUnpackedSize()); } diff --git a/tools/depends/native/TexturePacker/src/decoder/IDecoder.h b/tools/depends/native/TexturePacker/src/decoder/IDecoder.h index 5bc06f2f96b43..63b5dd860773d 100644 --- a/tools/depends/native/TexturePacker/src/decoder/IDecoder.h +++ b/tools/depends/native/TexturePacker/src/decoder/IDecoder.h @@ -20,6 +20,8 @@ #pragma once +#include "guilib/TextureFormats.h" + #include <cstdint> #include <string> #include <vector> @@ -52,8 +54,11 @@ class RGBAImage std::vector<uint8_t> pixels; int width = 0; // width int height = 0; // height - int bbp = 0; // bits per pixel + int bbp = 32; // bits per pixel int pitch = 0; // rowsize in bytes + KD_TEX_FMT textureFormat{KD_TEX_FMT_SDR_BGRA8}; + KD_TEX_ALPHA textureAlpha{KD_TEX_ALPHA_STRAIGHT}; + KD_TEX_SWIZ textureSwizzle{KD_TEX_SWIZ_RGBA}; }; class DecodedFrame diff --git a/xbmc/filesystem/XbtFile.cpp b/xbmc/filesystem/XbtFile.cpp index 3f4061ea0410b..34860dac4358e 100644 --- a/xbmc/filesystem/XbtFile.cpp +++ b/xbmc/filesystem/XbtFile.cpp @@ -12,6 +12,7 @@ #include "filesystem/File.h" #include "filesystem/XbtManager.h" #include "guilib/TextureBundleXBT.h" +#include "guilib/TextureFormats.h" #include "guilib/XBTFReader.h" #include "utils/StringUtils.h" @@ -314,6 +315,42 @@ XB_FMT CXbtFile::GetImageFormat() const return frame.GetFormat(); } +KD_TEX_FMT CXbtFile::GetKDFormat() const +{ + CXBTFFrame frame; + if (!GetFirstFrame(frame)) + return KD_TEX_FMT_UNKNOWN; + + return frame.GetKDFormat(); +} + +KD_TEX_FMT CXbtFile::GetKDFormatType() const +{ + CXBTFFrame frame; + if (!GetFirstFrame(frame)) + return KD_TEX_FMT_UNKNOWN; + + return frame.GetKDFormatType(); +} + +KD_TEX_ALPHA CXbtFile::GetKDAlpha() const +{ + CXBTFFrame frame; + if (!GetFirstFrame(frame)) + return KD_TEX_ALPHA_OPAQUE; + + return frame.GetKDAlpha(); +} + +KD_TEX_SWIZ CXbtFile::GetKDSwizzle() const +{ + CXBTFFrame frame; + if (!GetFirstFrame(frame)) + return KD_TEX_SWIZ_RGBA; + + return frame.GetKDSwizzle(); +} + bool CXbtFile::HasImageAlpha() const { CXBTFFrame frame; diff --git a/xbmc/filesystem/XbtFile.h b/xbmc/filesystem/XbtFile.h index 6da937c907426..ff36fc809d516 100644 --- a/xbmc/filesystem/XbtFile.h +++ b/xbmc/filesystem/XbtFile.h @@ -43,6 +43,10 @@ class CXbtFile : public IFile uint32_t GetImageWidth() const; uint32_t GetImageHeight() const; XB_FMT GetImageFormat() const; + KD_TEX_FMT GetKDFormat() const; + KD_TEX_FMT GetKDFormatType() const; + KD_TEX_ALPHA GetKDAlpha() const; + KD_TEX_SWIZ GetKDSwizzle() const; bool HasImageAlpha() const; private: diff --git a/xbmc/guilib/Texture.cpp b/xbmc/guilib/Texture.cpp index 31a6029636108..b59e3588e55e7 100644 --- a/xbmc/guilib/Texture.cpp +++ b/xbmc/guilib/Texture.cpp @@ -16,8 +16,10 @@ #include "filesystem/ResourceFile.h" #include "filesystem/XbtFile.h" #include "guilib/TextureBase.h" +#include "guilib/TextureFormats.h" #include "guilib/iimage.h" #include "guilib/imagefactory.h" +#include "messaging/ApplicationMessenger.h" #include "utils/URIUtils.h" #include "utils/log.h" #include "windowing/WinSystem.h" @@ -184,9 +186,21 @@ bool CTexture::LoadFromFileInternal(const std::string& texturePath, XFILE::CXbtFile xbtFile; if (!xbtFile.Open(url)) return false; - - return LoadFromMemory(xbtFile.GetImageWidth(), xbtFile.GetImageHeight(), 0, - xbtFile.GetImageFormat(), xbtFile.HasImageAlpha(), buf.data()); + if (xbtFile.GetKDFormatType()) + { + return UploadFromMemory(xbtFile.GetImageWidth(), xbtFile.GetImageHeight(), 0, buf.data(), + xbtFile.GetKDFormat(), xbtFile.GetKDAlpha(), xbtFile.GetKDSwizzle()); + } + else if (xbtFile.GetImageFormat() == XB_FMT_A8R8G8B8) + { + KD_TEX_ALPHA alpha = xbtFile.HasImageAlpha() ? KD_TEX_ALPHA_STRAIGHT : KD_TEX_ALPHA_OPAQUE; + return UploadFromMemory(xbtFile.GetImageWidth(), xbtFile.GetImageHeight(), 0, buf.data(), + KD_TEX_FMT_SDR_BGRA8, alpha, KD_TEX_SWIZ_RGBA); + } + else + { + return false; + } } IImage* pImage; @@ -271,7 +285,7 @@ bool CTexture::LoadIImage(IImage* pImage, if (pImage->Orientation()) m_orientation = pImage->Orientation() - 1; - m_hasAlpha = pImage->hasAlpha(); + m_textureAlpha = pImage->hasAlpha() ? KD_TEX_ALPHA_STRAIGHT : KD_TEX_ALPHA_OPAQUE; m_originalWidth = pImage->originalWidth(); m_originalHeight = pImage->originalHeight(); m_imageWidth = pImage->Width(); @@ -308,11 +322,67 @@ bool CTexture::LoadFromMemory(unsigned int width, m_imageWidth = m_originalWidth = width; m_imageHeight = m_originalHeight = height; m_format = format; - m_hasAlpha = hasAlpha; + m_textureAlpha = hasAlpha ? KD_TEX_ALPHA_STRAIGHT : KD_TEX_ALPHA_OPAQUE; Update(width, height, pitch, format, pixels, false); return true; } +bool CTexture::UploadFromMemory(unsigned int width, + unsigned int height, + unsigned int pitch, + unsigned char* pixels, + KD_TEX_FMT format, + KD_TEX_ALPHA alpha, + KD_TEX_SWIZ swizzle) +{ + m_imageWidth = m_textureWidth = m_originalWidth = width; + m_imageHeight = m_textureHeight = m_originalHeight = height; + m_textureFormat = format; + m_textureAlpha = alpha; + m_textureSwizzle = swizzle; + + if (!SupportsFormat(m_textureFormat, m_textureSwizzle) && !ConvertToLegacy(width, height, pixels)) + { + CLog::LogF( + LOGERROR, + "Failed to upload texture. Format {} and swizzle {} not supported by the texture pipeline.", + m_textureFormat, m_textureSwizzle); + + m_loadedToGPU = true; + return false; + } + + if (CServiceBroker::GetAppMessenger()->IsProcessThread()) + { + if (m_pixels) + { + LoadToGPU(); + } + else + { + // just a borrowed buffer + m_pixels = pixels; + m_bCacheMemory = true; + LoadToGPU(); + m_bCacheMemory = false; + m_pixels = nullptr; + } + } + else if (!m_pixels) + { + size_t size = GetPitch() * GetRows(); + m_pixels = static_cast<unsigned char*>(KODI::MEMORY::AlignedMalloc(size, 32)); + if (m_pixels == nullptr) + { + CLog::LogF(LOGERROR, "Could not allocate {} bytes. Out of memory.", size); + return false; + } + std::memcpy(m_pixels, pixels, size); + } + + return true; +} + bool CTexture::LoadPaletted(unsigned int width, unsigned int height, unsigned int pitch, diff --git a/xbmc/guilib/Texture.h b/xbmc/guilib/Texture.h index 6e5a4ff1a585f..09b9577a7e464 100644 --- a/xbmc/guilib/Texture.h +++ b/xbmc/guilib/Texture.h @@ -74,6 +74,24 @@ class CTexture : public CTextureBase XB_FMT format, bool hasAlpha, const unsigned char* pixels); + /*! \brief Attempts to upload a texture directly from a provided buffer + Unlike LoadFromMemory() which copies the texture into an intermediate buffer, the texture gets uploaded directly to + the GPU if circumstances allow. + \param width the width of the texture. + \param height the height of the texture. + \param pitch the pitch of the texture. + \param pixels pointer to the texture buffer. + \param format the format of the texture. + \param alpha the alpha type of the texture. + \param swizzle the swizzle pattern of the texture. + */ + bool UploadFromMemory(unsigned int width, + unsigned int height, + unsigned int pitch, + unsigned char* pixels, + KD_TEX_FMT format = KD_TEX_FMT_SDR_RGBA8, + KD_TEX_ALPHA alpha = KD_TEX_ALPHA_OPAQUE, + KD_TEX_SWIZ swizzle = KD_TEX_SWIZ_RGBA); bool LoadPaletted(unsigned int width, unsigned int height, unsigned int pitch, @@ -102,6 +120,16 @@ class CTexture : public CTextureBase virtual void SyncGPU(){}; virtual void BindToUnit(unsigned int unit) = 0; + /*! + * \brief Checks if the processing pipeline can handle the texture format/swizzle + \param format the format of the texture. + \return true if the texturing pipeline supports the format + */ + virtual bool SupportsFormat(KD_TEX_FMT textureFormat, KD_TEX_SWIZ textureSwizzle) + { + return !(textureFormat & KD_TEX_FMT_TYPE_MASK) && textureSwizzle == KD_TEX_SWIZ_RGBA; + } + private: // no copy constructor CTexture(const CTexture& copy) = delete; diff --git a/xbmc/guilib/TextureBase.cpp b/xbmc/guilib/TextureBase.cpp index e6154ac624bbd..5f2aff8bf7b66 100644 --- a/xbmc/guilib/TextureBase.cpp +++ b/xbmc/guilib/TextureBase.cpp @@ -398,3 +398,77 @@ void CTextureBase::SetKDFormat(XB_FMT xbFMT) return; } } + +bool CTextureBase::ConvertToLegacy(uint32_t width, uint32_t height, uint8_t* src) +{ + if (m_textureFormat == KD_TEX_FMT_SDR_BGRA8 && m_textureSwizzle == KD_TEX_SWIZ_RGBA) + { + m_format = XB_FMT_A8R8G8B8; + return true; + } + + if (m_textureFormat == KD_TEX_FMT_SDR_R8) + { + if (m_textureSwizzle != KD_TEX_SWIZ_111R && m_textureSwizzle != KD_TEX_SWIZ_RRR1 && + m_textureSwizzle != KD_TEX_SWIZ_RRRR) + return false; + } + else if (m_textureFormat == KD_TEX_FMT_SDR_RG8) + { + if (m_textureSwizzle != KD_TEX_SWIZ_RRRG) + return false; + } + else + { + return false; + } + + size_t size = GetPitch() * GetRows(); + + Allocate(width, height, XB_FMT_A8R8G8B8); + + if (m_textureSwizzle == KD_TEX_SWIZ_111R) + { + for (int32_t i = size - 1; i >= 0; i--) + { + m_pixels[i * 4 + 3] = src[i]; + m_pixels[i * 4 + 2] = 0xff; + m_pixels[i * 4 + 1] = 0xff; + m_pixels[i * 4] = 0xff; + } + } + else if (m_textureSwizzle == KD_TEX_SWIZ_RRR1) + { + for (int32_t i = size - 1; i >= 0; i--) + { + m_pixels[i * 4 + 3] = 0xff; + m_pixels[i * 4 + 2] = src[i]; + m_pixels[i * 4 + 1] = src[i]; + m_pixels[i * 4] = src[i]; + } + } + else if (m_textureSwizzle == KD_TEX_SWIZ_RRRR) + { + for (int32_t i = size - 1; i >= 0; i--) + { + m_pixels[i * 4 + 3] = src[i]; + m_pixels[i * 4 + 2] = src[i]; + m_pixels[i * 4 + 1] = src[i]; + m_pixels[i * 4] = src[i]; + } + } + else if (m_textureSwizzle == KD_TEX_SWIZ_RRRG) + { + for (int32_t i = size / 2 - 1; i >= 0; i--) + { + m_pixels[i * 4 + 3] = src[i * 2 + 1]; + m_pixels[i * 4 + 2] = src[i * 2]; + m_pixels[i * 4 + 1] = src[i * 2]; + m_pixels[i * 4] = src[i * 2]; + } + } + + m_textureFormat = KD_TEX_FMT_SDR_BGRA8; + m_textureSwizzle = KD_TEX_SWIZ_RGBA; + return true; +} diff --git a/xbmc/guilib/TextureBase.h b/xbmc/guilib/TextureBase.h index bbabe2ae469ef..51f8caeb20e31 100644 --- a/xbmc/guilib/TextureBase.h +++ b/xbmc/guilib/TextureBase.h @@ -30,8 +30,11 @@ class CTextureBase CTextureBase() = default; ~CTextureBase() = default; - bool HasAlpha() const { return m_hasAlpha; } - void SetAlpha(bool hasAlpha) { m_hasAlpha = hasAlpha; } + bool HasAlpha() const { return m_textureAlpha != KD_TEX_ALPHA_OPAQUE; } + void SetAlpha(bool hasAlpha) + { + m_textureAlpha = hasAlpha ? KD_TEX_ALPHA_STRAIGHT : KD_TEX_ALPHA_OPAQUE; + } /*! \brief sets mipmapping. do not use in new code. will be replaced with proper scaling. */ void SetMipmapping() { m_mipmapping = true; } @@ -92,6 +95,10 @@ class CTextureBase void SetKDFormat(XB_FMT xbFMT); + /*! \brief Textures might be in a single/dual channel format with L/A/I/LA swizzle. DX and GLES 2.0 don't + handle some/all at the moment. This function can convert the texture into a traditional BGRA format.*/ + bool ConvertToLegacy(uint32_t width, uint32_t height, uint8_t* src); + uint32_t m_imageWidth{0}; uint32_t m_imageHeight{0}; uint32_t m_textureWidth{0}; @@ -110,7 +117,6 @@ class CTextureBase XB_FMT m_format{XB_FMT_UNKNOWN}; // legacy XB format, deprecated int32_t m_orientation{0}; - bool m_hasAlpha{true}; bool m_mipmapping{false}; TEXTURE_SCALING m_scalingMethod{TEXTURE_SCALING::LINEAR}; bool m_bCacheMemory{false}; diff --git a/xbmc/guilib/TextureBundleXBT.cpp b/xbmc/guilib/TextureBundleXBT.cpp index f557d28a26968..e3a6f8e60e27b 100644 --- a/xbmc/guilib/TextureBundleXBT.cpp +++ b/xbmc/guilib/TextureBundleXBT.cpp @@ -16,6 +16,7 @@ #include "commons/ilog.h" #include "filesystem/SpecialProtocol.h" #include "filesystem/XbtManager.h" +#include "guilib/TextureFormats.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/StringUtils.h" @@ -237,9 +238,18 @@ std::unique_ptr<CTexture> CTextureBundleXBT::ConvertFrameToTexture(const std::st // create an xbmc texture std::unique_ptr<CTexture> texture = CTexture::CreateTexture(); - texture->LoadFromMemory(frame.GetWidth(), frame.GetHeight(), 0, frame.GetFormat(), - frame.HasAlpha(), buffer.data()); + if (frame.GetKDFormatType()) + { + texture->UploadFromMemory(frame.GetWidth(), frame.GetHeight(), 0, buffer.data(), + frame.GetKDFormat(), frame.GetKDAlpha(), frame.GetKDSwizzle()); + } + else if (frame.GetFormat() == XB_FMT_A8R8G8B8) + { + KD_TEX_ALPHA alpha = frame.HasAlpha() ? KD_TEX_ALPHA_STRAIGHT : KD_TEX_ALPHA_OPAQUE; + texture->UploadFromMemory(frame.GetWidth(), frame.GetHeight(), 0, buffer.data(), + KD_TEX_FMT_SDR_BGRA8, alpha, KD_TEX_SWIZ_RGBA); + } return texture; } diff --git a/xbmc/guilib/TextureGL.cpp b/xbmc/guilib/TextureGL.cpp index 28da1cc000d56..df4e2304aa263 100644 --- a/xbmc/guilib/TextureGL.cpp +++ b/xbmc/guilib/TextureGL.cpp @@ -234,6 +234,13 @@ void CGLTexture::LoadToGPU() m_textureWidth = maxSize; } + // there might not be any padding for the following formats, so we have to + // read one/two bytes at the time. + if (m_textureFormat == KD_TEX_FMT_SDR_R8) + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + if (m_textureFormat == KD_TEX_FMT_SDR_RG8) + glPixelStorei(GL_UNPACK_ALIGNMENT, 2); + SetSwizzle(); TextureFormat glFormat = GetFormatGL(m_textureFormat); diff --git a/xbmc/guilib/TextureGL.h b/xbmc/guilib/TextureGL.h index e3f68bc421ec2..cc3e41b179fb3 100644 --- a/xbmc/guilib/TextureGL.h +++ b/xbmc/guilib/TextureGL.h @@ -43,6 +43,11 @@ class CGLTexture : public CTexture void SyncGPU() override; void BindToUnit(unsigned int unit) override; + bool SupportsFormat(KD_TEX_FMT textureFormat, KD_TEX_SWIZ textureSwizzle) override + { + return true; + } + protected: void SetSwizzle(); TextureFormat GetFormatGL(KD_TEX_FMT textureFormat); diff --git a/xbmc/guilib/TextureGLES.cpp b/xbmc/guilib/TextureGLES.cpp index bf0785cc77f5e..e475fecf47d13 100644 --- a/xbmc/guilib/TextureGLES.cpp +++ b/xbmc/guilib/TextureGLES.cpp @@ -182,7 +182,9 @@ CGLESTexture::CGLESTexture(unsigned int width, unsigned int height, XB_FMT forma { unsigned int major, minor; CServiceBroker::GetRenderSystem()->GetRenderVersion(major, minor); +#if defined(GL_ES_VERSION_3_0) m_isGLESVersion30orNewer = major >= 3; +#endif } CGLESTexture::~CGLESTexture() @@ -276,6 +278,13 @@ void CGLESTexture::LoadToGPU() } } + // there might not be any padding for the following formats, so we have to + // read one/two bytes at the time. + if (m_textureFormat == KD_TEX_FMT_SDR_R8) + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + if (m_textureFormat == KD_TEX_FMT_SDR_RG8) + glPixelStorei(GL_UNPACK_ALIGNMENT, 2); + TextureFormat glesFormat; if (m_isGLESVersion30orNewer) { @@ -338,6 +347,24 @@ void CGLESTexture::BindToUnit(unsigned int unit) glBindTexture(GL_TEXTURE_2D, m_texture); } +bool CGLESTexture::SupportsFormat(KD_TEX_FMT textureFormat, KD_TEX_SWIZ textureSwizzle) +{ + // GLES 3.0 supports swizzles + if (m_isGLESVersion30orNewer) + return true; + + // GL_LUMINANCE; + if (textureFormat == KD_TEX_FMT_SDR_R8 && textureSwizzle == KD_TEX_SWIZ_RRR1) + return true; + + // GL_LUMINANCE_ALPHA; + if (textureFormat == KD_TEX_FMT_SDR_RG8 && textureSwizzle == KD_TEX_SWIZ_RRRG) + return true; + + // all other GLES 2.0 swizzles would need separate shaders + return textureSwizzle == KD_TEX_SWIZ_RGBA; +} + void CGLESTexture::SetSwizzle(bool swapRB) { #if defined(GL_ES_VERSION_3_0) @@ -428,7 +455,7 @@ TextureFormat CGLESTexture::GetFormatGLES30(KD_TEX_FMT textureFormat) if (textureFormat & KD_TEX_FMT_SDR || textureFormat & KD_TEX_FMT_HDR) { #if defined(GL_ES_VERSION_3_0) - const auto it = TextureMappingGLES30.find(KD_TEX_FMT_SDR_RGBA8); + const auto it = TextureMappingGLES30.find(textureFormat); if (it != TextureMappingGLES30.cend()) glFormat = it->second; #else diff --git a/xbmc/guilib/TextureGLES.h b/xbmc/guilib/TextureGLES.h index 92089506c50e0..fe7af24917af3 100644 --- a/xbmc/guilib/TextureGLES.h +++ b/xbmc/guilib/TextureGLES.h @@ -38,6 +38,7 @@ class CGLESTexture : public CTexture void DestroyTextureObject() override; void LoadToGPU() override; void BindToUnit(unsigned int unit) override; + bool SupportsFormat(KD_TEX_FMT textureFormat, KD_TEX_SWIZ textureSwizzle) override; protected: void SetSwizzle(bool swapRB); diff --git a/xbmc/guilib/XBTF.cpp b/xbmc/guilib/XBTF.cpp index fb89c97889be6..92848bd69ebad 100644 --- a/xbmc/guilib/XBTF.cpp +++ b/xbmc/guilib/XBTF.cpp @@ -72,14 +72,14 @@ void CXBTFFrame::SetUnpackedSize(uint64_t size) m_unpackedSize = size; } -void CXBTFFrame::SetFormat(XB_FMT format) +void CXBTFFrame::SetFormat(uint32_t format) { m_format = format; } XB_FMT CXBTFFrame::GetFormat(bool raw) const { - return raw ? m_format : static_cast<XB_FMT>(m_format & XB_FMT_MASK); + return static_cast<XB_FMT>(raw ? m_format : m_format & XB_FMT_MASK); } uint64_t CXBTFFrame::GetOffset() const @@ -116,6 +116,26 @@ uint64_t CXBTFFrame::GetHeaderSize() const return result; } +KD_TEX_FMT CXBTFFrame::GetKDFormat() const +{ + return static_cast<KD_TEX_FMT>(m_format & KD_TEX_FMT_MASK); +} + +KD_TEX_FMT CXBTFFrame::GetKDFormatType() const +{ + return static_cast<KD_TEX_FMT>(m_format & KD_TEX_FMT_TYPE_MASK); +} + +KD_TEX_ALPHA CXBTFFrame::GetKDAlpha() const +{ + return static_cast<KD_TEX_ALPHA>(m_format & KD_TEX_ALPHA_MASK); +} + +KD_TEX_SWIZ CXBTFFrame::GetKDSwizzle() const +{ + return static_cast<KD_TEX_SWIZ>(m_format & KD_TEX_SWIZ_MASK); +} + CXBTFFile::CXBTFFile() : m_path(), m_frames() diff --git a/xbmc/guilib/XBTF.h b/xbmc/guilib/XBTF.h index 588ba6234dece..af6b77c205078 100644 --- a/xbmc/guilib/XBTF.h +++ b/xbmc/guilib/XBTF.h @@ -16,7 +16,8 @@ #include <stdint.h> static const std::string XBTF_MAGIC = "XBTF"; -static const std::string XBTF_VERSION = "2"; +static const std::string XBTF_VERSION = "3"; +static const char XBTF_VERSION_MIN = '2'; #include "TextureFormats.h" @@ -29,7 +30,7 @@ class CXBTFFrame void SetWidth(uint32_t width); XB_FMT GetFormat(bool raw = false) const; - void SetFormat(XB_FMT format); + void SetFormat(uint32_t format); uint32_t GetHeight() const; void SetHeight(uint32_t height); @@ -51,10 +52,15 @@ class CXBTFFrame bool IsPacked() const; bool HasAlpha() const; + KD_TEX_FMT GetKDFormat() const; + KD_TEX_FMT GetKDFormatType() const; + KD_TEX_ALPHA GetKDAlpha() const; + KD_TEX_SWIZ GetKDSwizzle() const; + private: uint32_t m_width; uint32_t m_height; - XB_FMT m_format; + uint32_t m_format; uint64_t m_packedSize; uint64_t m_unpackedSize; uint64_t m_offset; diff --git a/xbmc/guilib/XBTFReader.cpp b/xbmc/guilib/XBTFReader.cpp index 6d74f46b0d870..b4f561f6151c6 100644 --- a/xbmc/guilib/XBTFReader.cpp +++ b/xbmc/guilib/XBTFReader.cpp @@ -29,6 +29,17 @@ static bool ReadString(FILE* file, char* str, size_t max_length) return (fread(str, max_length, 1, file) == 1); } +static bool ReadChar(FILE* file, char& value) +{ + if (file == nullptr) + return false; + + if (fread(&value, sizeof(char), 1, file) != 1) + return false; + + return true; +} + static bool ReadUInt32(FILE* file, uint32_t& value) { if (file == nullptr) @@ -89,11 +100,11 @@ bool CXBTFReader::Open(const std::string& path) return false; // read the version - char version[1]; - if (!ReadString(m_file, version, sizeof(version))) + char version; + if (!ReadChar(m_file, version)) return false; - if (strncmp(XBTF_VERSION.c_str(), version, sizeof(version)) != 0) + if (version < XBTF_VERSION_MIN) return false; unsigned int nofFiles; From 2e67ce355b99cd63536ad1f84d058f172440ca62 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 20 Sep 2024 13:20:28 +0200 Subject: [PATCH 489/651] [PVR] EPG search dialog: Enhance start/end time matching. --- .../resources/strings.po | 2 + .../skin.estuary/xml/DialogPVRGuideSearch.xml | 36 ++++++++---- xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp | 15 +++++ xbmc/pvr/epg/EpgDatabase.cpp | 58 ++++++++++++++++--- xbmc/pvr/epg/EpgDatabase.h | 2 +- xbmc/pvr/epg/EpgSearchData.h | 8 ++- xbmc/pvr/epg/EpgSearchFilter.cpp | 18 ++++++ xbmc/pvr/epg/EpgSearchFilter.h | 6 ++ 8 files changed, 122 insertions(+), 23 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 96fa299ac1bfc..818ac8db86299 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -3704,6 +3704,7 @@ msgid "Any channel" msgstr "" #. Label of "Start any time" radio button in PVR timer settings dialog +#: addons/skin.estuary/xml/DialogPVRGuideSearch.xml #: xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp msgctxt "#810" msgid "Start any time" @@ -3750,6 +3751,7 @@ msgid "Record only new episodes" msgstr "" #. Label of "End any time" radio button in PVR timer settings dialog +#: addons/skin.estuary/xml/DialogPVRGuideSearch.xml #: xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp msgctxt "#817" msgid "End any time" diff --git a/addons/skin.estuary/xml/DialogPVRGuideSearch.xml b/addons/skin.estuary/xml/DialogPVRGuideSearch.xml index e1e766025e966..8ac09e05d078d 100644 --- a/addons/skin.estuary/xml/DialogPVRGuideSearch.xml +++ b/addons/skin.estuary/xml/DialogPVRGuideSearch.xml @@ -5,12 +5,12 @@ <controls> <control type="group"> <centertop>50%</centertop> - <height>890</height> + <height>960</height> <centerleft>50%</centerleft> <width>1780</width> <include content="DialogBackgroundCommons"> <param name="width" value="1780" /> - <param name="height" value="890" /> + <param name="height" value="960" /> <param name="header_label" value="$LOCALIZE[19142]" /> <param name="header_id" value="2" /> </include> @@ -41,7 +41,7 @@ <left>10</left> <top>210</top> <width>1460</width> - <height>675</height> + <height>740</height> <texture border="40">buttons/dialogbutton-nofo.png</texture> </control> <control type="grouplist" id="5000"> @@ -69,16 +69,23 @@ <control type="edit" id="14"> <description>Start Date</description> <width>710</width> - <onright>16</onright> + <onright>15</onright> <include>DefaultSettingButton</include> <label>$LOCALIZE[19128]</label> </control> - <control type="edit" id="15"> - <description>Stop Date</description> + <control type="radiobutton" id="32"> + <description>Start any time</description> + <width>710</width> + <onright>33</onright> + <include>DefaultSettingButton</include> + <label>$LOCALIZE[810]</label> + </control> + <control type="edit" id="16"> + <description>Start time</description> <width>710</width> <onright>17</onright> <include>DefaultSettingButton</include> - <label>$LOCALIZE[19129]</label> + <label>$LOCALIZE[19126]</label> </control> <control type="radiobutton" id="30"> <description>Ignore finished broadcasts</description> @@ -138,17 +145,24 @@ <include>DefaultSettingButton</include> <label>$LOCALIZE[19131]</label> </control> - <control type="edit" id="16"> - <description>Start time</description> + <control type="edit" id="15"> + <description>Stop Date</description> <width>710</width> <onleft>14</onleft> <include>DefaultSettingButton</include> - <label>$LOCALIZE[19126]</label> + <label>$LOCALIZE[19129]</label> + </control> + <control type="radiobutton" id="33"> + <description>End any time</description> + <width>710</width> + <onleft>32</onleft> + <include>DefaultSettingButton</include> + <label>$LOCALIZE[817]</label> </control> <control type="edit" id="17"> <description>Stop time</description> <width>710</width> - <onleft>15</onleft> + <onleft>16</onleft> <include>DefaultSettingButton</include> <label>$LOCALIZE[19127]</label> </control> diff --git a/xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp b/xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp index 90faa68af2730..b5cb4cf64a78a 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp @@ -51,6 +51,8 @@ using namespace PVR; static constexpr int CONTROL_BTN_SAVE = 29; static constexpr int CONTROL_BTN_IGNORE_FINISHED = 30; static constexpr int CONTROL_BTN_IGNORE_FUTURE = 31; +static constexpr int CONTROL_BTN_START_ANY_TIME = 32; +static constexpr int CONTROL_BTN_END_ANY_TIME = 33; CGUIDialogPVRGuideSearch::CGUIDialogPVRGuideSearch() : CGUIDialog(WINDOW_DIALOG_PVR_GUIDE_SEARCH, "DialogPVRGuideSearch.xml") @@ -225,6 +227,12 @@ bool CGUIDialogPVRGuideSearch::OnMessage(CGUIMessage& message) UpdateChannelSpin(); return true; } + else if (iControl == CONTROL_BTN_START_ANY_TIME || iControl == CONTROL_BTN_END_ANY_TIME) + { + UpdateSearchFilter(); + Update(); + return true; + } } break; } @@ -316,6 +324,9 @@ void CGUIDialogPVRGuideSearch::UpdateSearchFilter() m_searchFilter->SetEndDateTime(end); m_endDateTime = end; } + + m_searchFilter->SetStartAnyTime(IsRadioSelected(CONTROL_BTN_START_ANY_TIME)); + m_searchFilter->SetEndAnyTime(IsRadioSelected(CONTROL_BTN_END_ANY_TIME)); } void CGUIDialogPVRGuideSearch::Update() @@ -340,6 +351,10 @@ void CGUIDialogPVRGuideSearch::Update() m_searchFilter->ShouldIgnoreFinishedBroadcasts()); SET_CONTROL_SELECTED(GetID(), CONTROL_BTN_IGNORE_FUTURE, m_searchFilter->ShouldIgnoreFutureBroadcasts()); + SET_CONTROL_SELECTED(GetID(), CONTROL_BTN_START_ANY_TIME, m_searchFilter->IsStartAnyTime()); + SET_CONTROL_SELECTED(GetID(), CONTROL_BTN_END_ANY_TIME, m_searchFilter->IsEndAnyTime()); + CONTROL_ENABLE_ON_CONDITION(CONTROL_EDIT_START_TIME, !m_searchFilter->IsStartAnyTime()); + CONTROL_ENABLE_ON_CONDITION(CONTROL_EDIT_STOP_TIME, !m_searchFilter->IsEndAnyTime()); // Set start/end datetime fields m_startDateTime = m_searchFilter->GetStartDateTime(); diff --git a/xbmc/pvr/epg/EpgDatabase.cpp b/xbmc/pvr/epg/EpgDatabase.cpp index 7c17c0390993f..f1e6b91e622db 100644 --- a/xbmc/pvr/epg/EpgDatabase.cpp +++ b/xbmc/pvr/epg/EpgDatabase.cpp @@ -130,7 +130,9 @@ void CPVREpgDatabase::CreateTables() "bIgnorePresentTimers bool, " "bIgnorePresentRecordings bool, " "iChannelGroup integer, " - "sIconPath varchar(255)" + "sIconPath varchar(255), " + "bStartAnyTime bool, " + "bEndAnyTime bool" ")"); } @@ -337,6 +339,14 @@ void CPVREpgDatabase::UpdateTables(int iVersion) m_pDS->exec("ALTER TABLE epgtags ADD sParentalRatingIcon varchar(512);"); m_pDS->exec("ALTER TABLE epgtags ADD sParentalRatingSource varchar(128);"); } + + if (iVersion < 19) + { + m_pDS->exec("ALTER TABLE savedsearches ADD bStartAnyTime bool;"); + m_pDS->exec("ALTER TABLE savedsearches ADD bEndAnyTime bool;"); + m_pDS->exec("UPDATE savedsearches SET bStartAnyTime = 1;"); + m_pDS->exec("UPDATE savedsearches SET bEndAnyTime = 1;"); + } } bool CPVREpgDatabase::DeleteEpg() @@ -694,11 +704,25 @@ std::vector<std::shared_ptr<CPVREpgInfoTag>> CPVREpgDatabase::GetEpgTags( // min start datetime ///////////////////////////////////////////////////////////////////////////////////////////// + static constexpr unsigned int ONE_DAY{60 * 60 * 24}; + if (searchData.m_startDateTime.IsValid()) { time_t minStart; searchData.m_startDateTime.GetAsTime(minStart); - filter.AppendWhere(PrepareSQL("iStartTime >= %u", static_cast<unsigned int>(minStart))); + + if (searchData.m_startAnyTime) + { + filter.AppendWhere(PrepareSQL("iStartTime >= %u", static_cast<unsigned int>(minStart))); + } + else + { + const unsigned int startDate{static_cast<unsigned int>(minStart) / ONE_DAY}; + filter.AppendWhere(PrepareSQL("(iStartTime / %u) >= %u", ONE_DAY, startDate)); + + const unsigned int startTime{static_cast<unsigned int>(minStart) % ONE_DAY}; + filter.AppendWhere(PrepareSQL("(iStartTime %% %u) >= %u", ONE_DAY, startTime)); + } } ///////////////////////////////////////////////////////////////////////////////////////////// @@ -709,7 +733,19 @@ std::vector<std::shared_ptr<CPVREpgInfoTag>> CPVREpgDatabase::GetEpgTags( { time_t maxEnd; searchData.m_endDateTime.GetAsTime(maxEnd); - filter.AppendWhere(PrepareSQL("iEndTime <= %u", static_cast<unsigned int>(maxEnd))); + + if (searchData.m_endAnyTime) + { + filter.AppendWhere(PrepareSQL("iEndTime <= %u", static_cast<unsigned int>(maxEnd))); + } + else + { + const unsigned int endDate{static_cast<unsigned int>(maxEnd) / ONE_DAY}; + filter.AppendWhere(PrepareSQL("(iEndTime / %u) <= %u", ONE_DAY, endDate)); + + const unsigned int endTime{static_cast<unsigned int>(maxEnd) % ONE_DAY}; + filter.AppendWhere(PrepareSQL("(iEndTime %% %u) <= %u", ONE_DAY, endTime)); + } } ///////////////////////////////////////////////////////////////////////////////////////////// @@ -1340,6 +1376,8 @@ std::shared_ptr<CPVREpgSearchFilter> CPVREpgDatabase::CreateEpgSearchFilter( newSearch->SetIgnorePresentRecordings(m_pDS->fv("bIgnorePresentRecordings").get_asBool()); newSearch->SetChannelGroupID(m_pDS->fv("iChannelGroup").get_asInt()); newSearch->SetIconPath(m_pDS->fv("sIconPath").get_asString()); + newSearch->SetStartAnyTime(m_pDS->fv("bStartAnyTime").get_asBool()); + newSearch->SetEndAnyTime(m_pDS->fv("bEndAnyTime").get_asBool()); newSearch->SetChanged(false); @@ -1411,9 +1449,9 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch) "iGenreType, bIncludeUnknownGenres, sStartDateTime, sEndDateTime, iMinimumDuration, " "iMaximumDuration, bIsRadio, iClientId, iChannelUid, bRemoveDuplicates, " "bIgnoreFinishedBroadcasts, bIgnoreFutureBroadcasts, bFreeToAirOnly, bIgnorePresentTimers, " - "bIgnorePresentRecordings, iChannelGroup, sIconPath) " + "bIgnorePresentRecordings, iChannelGroup, sIconPath, bStartAnyTime, bEndAnyTime) " "VALUES ('%s', '%s', '%s', %i, %i, %i, %i, '%s', '%s', %i, %i, %i, %i, %i, %i, %i, %i, " - "%i, %i, %i, %i, '%s');", + "%i, %i, %i, %i, '%s', %i, %i);", epgSearch.GetTitle().c_str(), epgSearch.GetLastExecutedDateTime().IsValid() ? epgSearch.GetLastExecutedDateTime().GetAsDBDateTime().c_str() @@ -1433,7 +1471,8 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch) epgSearch.ShouldIgnoreFutureBroadcasts() ? 1 : 0, epgSearch.IsFreeToAirOnly() ? 1 : 0, epgSearch.ShouldIgnorePresentTimers() ? 1 : 0, epgSearch.ShouldIgnorePresentRecordings() ? 1 : 0, epgSearch.GetChannelGroupID(), - epgSearch.GetIconPath().c_str()); + epgSearch.GetIconPath().c_str(), epgSearch.IsStartAnyTime() ? 1 : 0, + epgSearch.IsEndAnyTime() ? 1 : 0); else strQuery = PrepareSQL( "REPLACE INTO savedsearches " @@ -1441,9 +1480,9 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch) "bIsCaseSensitive, iGenreType, bIncludeUnknownGenres, sStartDateTime, sEndDateTime, " "iMinimumDuration, iMaximumDuration, bIsRadio, iClientId, iChannelUid, bRemoveDuplicates, " "bIgnoreFinishedBroadcasts, bIgnoreFutureBroadcasts, bFreeToAirOnly, bIgnorePresentTimers, " - "bIgnorePresentRecordings, iChannelGroup, sIconPath) " + "bIgnorePresentRecordings, iChannelGroup, sIconPath, bStartAnyTime, bEndAnyTime) " "VALUES (%i, '%s', '%s', '%s', %i, %i, %i, %i, '%s', '%s', %i, %i, %i, %i, %i, %i, %i, %i, " - "%i, %i, %i, %i, '%s');", + "%i, %i, %i, %i, '%s', %i, %i);", epgSearch.GetDatabaseId(), epgSearch.GetTitle().c_str(), epgSearch.GetLastExecutedDateTime().IsValid() ? epgSearch.GetLastExecutedDateTime().GetAsDBDateTime().c_str() @@ -1463,7 +1502,8 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch) epgSearch.ShouldIgnoreFutureBroadcasts() ? 1 : 0, epgSearch.IsFreeToAirOnly() ? 1 : 0, epgSearch.ShouldIgnorePresentTimers() ? 1 : 0, epgSearch.ShouldIgnorePresentRecordings() ? 1 : 0, epgSearch.GetChannelGroupID(), - epgSearch.GetIconPath().c_str()); + epgSearch.GetIconPath().c_str(), epgSearch.IsStartAnyTime() ? 1 : 0, + epgSearch.IsEndAnyTime() ? 1 : 0); bool bReturn = ExecuteQuery(strQuery); diff --git a/xbmc/pvr/epg/EpgDatabase.h b/xbmc/pvr/epg/EpgDatabase.h index cf114a105053a..8ea846bf42383 100644 --- a/xbmc/pvr/epg/EpgDatabase.h +++ b/xbmc/pvr/epg/EpgDatabase.h @@ -66,7 +66,7 @@ namespace PVR * @brief Get the minimal database version that is required to operate correctly. * @return The minimal database version. */ - int GetSchemaVersion() const override { return 18; } + int GetSchemaVersion() const override { return 19; } /*! * @brief Get the default sqlite database filename. diff --git a/xbmc/pvr/epg/EpgSearchData.h b/xbmc/pvr/epg/EpgSearchData.h index e5e6ef7057e15..c0ab72069ad51 100644 --- a/xbmc/pvr/epg/EpgSearchData.h +++ b/xbmc/pvr/epg/EpgSearchData.h @@ -25,8 +25,10 @@ struct PVREpgSearchData int m_iGenreType = EPG_SEARCH_UNSET; /*!< The genre type for an entry */ bool m_bIgnoreFinishedBroadcasts; /*!< True to ignore finished broadcasts, false if not */ bool m_bIgnoreFutureBroadcasts; /*!< True to ignore future broadcasts, false if not */ - CDateTime m_startDateTime; /*!< The minimum start time for an entry */ - CDateTime m_endDateTime; /*!< The maximum end time for an entry */ + CDateTime m_startDateTime; /*!< The minimum start date and time for an entry */ + CDateTime m_endDateTime; /*!< The maximum end date and time for an entry */ + bool m_startAnyTime{true}; /*!< Match any start time */ + bool m_endAnyTime{true}; /*!< Match any end time */ void Reset() { @@ -38,6 +40,8 @@ struct PVREpgSearchData m_bIgnoreFutureBroadcasts = false; m_startDateTime.SetValid(false); m_endDateTime.SetValid(false); + m_startAnyTime = true; + m_endAnyTime = true; } }; diff --git a/xbmc/pvr/epg/EpgSearchFilter.cpp b/xbmc/pvr/epg/EpgSearchFilter.cpp index 2425c0a1c5915..30877550c5864 100644 --- a/xbmc/pvr/epg/EpgSearchFilter.cpp +++ b/xbmc/pvr/epg/EpgSearchFilter.cpp @@ -149,6 +149,15 @@ void CPVREpgSearchFilter::SetStartDateTime(const CDateTime& startDateTime) } } +void CPVREpgSearchFilter::SetStartAnyTime(bool startAnyTime) +{ + if (m_searchData.m_startAnyTime != startAnyTime) + { + m_searchData.m_startAnyTime = startAnyTime; + m_bChanged = true; + } +} + void CPVREpgSearchFilter::SetEndDateTime(const CDateTime& endDateTime) { if (m_searchData.m_endDateTime != endDateTime) @@ -158,6 +167,15 @@ void CPVREpgSearchFilter::SetEndDateTime(const CDateTime& endDateTime) } } +void CPVREpgSearchFilter::SetEndAnyTime(bool endAnyTime) +{ + if (m_searchData.m_endAnyTime != endAnyTime) + { + m_searchData.m_endAnyTime = endAnyTime; + m_bChanged = true; + } +} + void CPVREpgSearchFilter::SetIncludeUnknownGenres(bool bIncludeUnknownGenres) { if (m_searchData.m_bIncludeUnknownGenres != bIncludeUnknownGenres) diff --git a/xbmc/pvr/epg/EpgSearchFilter.h b/xbmc/pvr/epg/EpgSearchFilter.h index f6cca5cf0c056..6638befd6770e 100644 --- a/xbmc/pvr/epg/EpgSearchFilter.h +++ b/xbmc/pvr/epg/EpgSearchFilter.h @@ -90,9 +90,15 @@ namespace PVR const CDateTime& GetStartDateTime() const { return m_searchData.m_startDateTime; } void SetStartDateTime(const CDateTime& startDateTime); + bool IsStartAnyTime() const { return m_searchData.m_startAnyTime; } + void SetStartAnyTime(bool startAnyTime); + const CDateTime& GetEndDateTime() const { return m_searchData.m_endDateTime; } void SetEndDateTime(const CDateTime& endDateTime); + bool IsEndAnyTime() const { return m_searchData.m_endAnyTime; } + void SetEndAnyTime(bool endAnyTime); + bool ShouldIncludeUnknownGenres() const { return m_searchData.m_bIncludeUnknownGenres; } void SetIncludeUnknownGenres(bool bIncludeUnknownGenres); From aa3ccbf7decd100e279d82c2077345ed60c5e9f4 Mon Sep 17 00:00:00 2001 From: jjd-uk <jjd-uk@kodi.tv> Date: Sun, 1 Sep 2024 12:05:42 +0100 Subject: [PATCH 490/651] [Estuary] Refactor mediaflags and metadata variables --- .../media/flags/aspectratio/1.00.png | Bin 1076 -> 0 bytes .../media/flags/aspectratio/1.19.png | Bin 1049 -> 0 bytes .../media/flags/aspectratio/1.33.png | Bin 1050 -> 0 bytes .../media/flags/aspectratio/1.37.png | Bin 1115 -> 0 bytes .../media/flags/aspectratio/1.66.png | Bin 1036 -> 0 bytes .../media/flags/aspectratio/1.78.png | Bin 1152 -> 0 bytes .../media/flags/aspectratio/1.85.png | Bin 1228 -> 0 bytes .../media/flags/aspectratio/2.00.png | Bin 1205 -> 0 bytes .../media/flags/aspectratio/2.20.png | Bin 1144 -> 0 bytes .../media/flags/aspectratio/2.35.png | Bin 1284 -> 0 bytes .../media/flags/aspectratio/2.40.png | Bin 1252 -> 0 bytes .../media/flags/aspectratio/2.55.png | Bin 1131 -> 0 bytes .../media/flags/aspectratio/2.76.png | Bin 1193 -> 0 bytes .../media/flags/audiochannel/0.png | Bin 807 -> 0 bytes .../media/flags/audiochannel/1.png | Bin 839 -> 0 bytes .../media/flags/audiochannel/10.png | Bin 864 -> 0 bytes .../media/flags/audiochannel/2.png | Bin 958 -> 0 bytes .../media/flags/audiochannel/3.png | Bin 803 -> 0 bytes .../media/flags/audiochannel/4.png | Bin 928 -> 0 bytes .../media/flags/audiochannel/5.png | Bin 684 -> 0 bytes .../media/flags/audiochannel/6.png | Bin 809 -> 0 bytes .../media/flags/audiochannel/7.png | Bin 856 -> 0 bytes .../media/flags/audiochannel/8.png | Bin 657 -> 0 bytes .../media/flags/audiocodec/aac.png | Bin 1042 -> 0 bytes .../media/flags/audiocodec/aac_latm.png | Bin 1042 -> 0 bytes .../media/flags/audiocodec/ac3.png | Bin 1383 -> 0 bytes .../media/flags/audiocodec/aif.png | Bin 771 -> 0 bytes .../media/flags/audiocodec/aifc.png | Bin 1076 -> 0 bytes .../media/flags/audiocodec/aiff.png | Bin 771 -> 0 bytes .../media/flags/audiocodec/alac.png | Bin 1056 -> 0 bytes .../media/flags/audiocodec/ape.png | Bin 990 -> 0 bytes .../media/flags/audiocodec/avc.png | Bin 1158 -> 0 bytes .../media/flags/audiocodec/cdda.png | Bin 1223 -> 0 bytes .../media/flags/audiocodec/dca.png | Bin 981 -> 0 bytes .../media/flags/audiocodec/dolbydigital.png | Bin 1383 -> 0 bytes .../media/flags/audiocodec/dts.png | Bin 981 -> 0 bytes .../media/flags/audiocodec/dtshd_hra.png | Bin 1549 -> 0 bytes .../media/flags/audiocodec/dtshd_ma.png | Bin 1524 -> 0 bytes .../media/flags/audiocodec/dtsma.png | Bin 1524 -> 0 bytes .../media/flags/audiocodec/eac3.png | Bin 1383 -> 0 bytes .../media/flags/audiocodec/flac.png | Bin 1121 -> 0 bytes .../media/flags/audiocodec/mp1.png | Bin 936 -> 0 bytes .../media/flags/audiocodec/mp2.png | Bin 1105 -> 0 bytes .../media/flags/audiocodec/mp3.png | Bin 1138 -> 0 bytes .../media/flags/audiocodec/mp3float.png | Bin 1138 -> 0 bytes .../media/flags/audiocodec/ogg.png | Bin 1077 -> 0 bytes .../media/flags/audiocodec/opus.png | Bin 1351 -> 0 bytes .../media/flags/audiocodec/pcm.png | Bin 1187 -> 0 bytes .../media/flags/audiocodec/pcm_bluray.png | Bin 1187 -> 0 bytes .../media/flags/audiocodec/pcm_s16le.png | Bin 1187 -> 0 bytes .../media/flags/audiocodec/pcm_s24le.png | Bin 1187 -> 0 bytes .../media/flags/audiocodec/truehd.png | Bin 1579 -> 0 bytes .../media/flags/audiocodec/vorbis.png | Bin 1692 -> 0 bytes .../media/flags/audiocodec/wav.png | Bin 1321 -> 0 bytes .../media/flags/audiocodec/wavpack.png | Bin 1480 -> 0 bytes .../media/flags/audiocodec/wma.png | Bin 1378 -> 0 bytes .../media/flags/audiocodec/wmapro.png | Bin 1378 -> 0 bytes .../media/flags/audiocodec/wmav2.png | Bin 1378 -> 0 bytes addons/skin.estuary/media/flags/rds/rds.png | Bin 1141 -> 0 bytes .../media/flags/videocodec/av1.png | Bin 1025 -> 0 bytes .../media/flags/videocodec/avc1.png | Bin 1262 -> 0 bytes .../media/flags/videocodec/bluray.png | Bin 1313 -> 0 bytes .../media/flags/videocodec/div3.png | Bin 1213 -> 0 bytes .../media/flags/videocodec/divx.png | Bin 1213 -> 0 bytes .../media/flags/videocodec/dvd.png | Bin 1002 -> 0 bytes .../media/flags/videocodec/dx50.png | Bin 1213 -> 0 bytes .../media/flags/videocodec/flv.png | Bin 744 -> 0 bytes .../media/flags/videocodec/h264.png | Bin 1160 -> 0 bytes .../media/flags/videocodec/hddvd.png | Bin 1100 -> 0 bytes .../media/flags/videocodec/hdmv.png | Bin 1313 -> 0 bytes .../media/flags/videocodec/hev1.png | Bin 1261 -> 0 bytes .../media/flags/videocodec/hevc.png | Bin 1261 -> 0 bytes .../media/flags/videocodec/hvc1.png | Bin 1261 -> 0 bytes .../media/flags/videocodec/mp4v.png | Bin 1362 -> 0 bytes .../media/flags/videocodec/mpeg1.png | Bin 1294 -> 0 bytes .../media/flags/videocodec/mpeg1video.png | Bin 1294 -> 0 bytes .../media/flags/videocodec/mpeg2.png | Bin 1429 -> 0 bytes .../media/flags/videocodec/mpeg2video.png | Bin 1429 -> 0 bytes .../media/flags/videocodec/mpeg4.png | Bin 1362 -> 0 bytes .../media/flags/videocodec/theora.png | Bin 1515 -> 0 bytes .../media/flags/videocodec/tv.png | Bin 708 -> 0 bytes .../media/flags/videocodec/vc-1.png | Bin 1045 -> 0 bytes .../media/flags/videocodec/vc1.png | Bin 1045 -> 0 bytes .../media/flags/videocodec/vhs.png | Bin 1097 -> 0 bytes .../media/flags/videocodec/vp8.png | Bin 1193 -> 0 bytes .../media/flags/videocodec/vp9.png | Bin 1143 -> 0 bytes .../media/flags/videocodec/wmv.png | Bin 1394 -> 0 bytes .../media/flags/videocodec/wmv3.png | Bin 1394 -> 0 bytes .../media/flags/videocodec/wvc1.png | Bin 1045 -> 0 bytes .../media/flags/videocodec/xvid.png | Bin 1195 -> 0 bytes .../media/flags/videohdr/dolbyvision.png | Bin 2759 -> 0 bytes .../media/flags/videohdr/hdr10.png | Bin 2910 -> 0 bytes .../skin.estuary/media/flags/videohdr/hlg.png | Bin 2078 -> 0 bytes .../media/flags/videoresolution/1080.png | Bin 1441 -> 0 bytes .../media/flags/videoresolution/3D.png | Bin 938 -> 0 bytes .../media/flags/videoresolution/480.png | Bin 1594 -> 0 bytes .../media/flags/videoresolution/4K.png | Bin 814 -> 0 bytes .../media/flags/videoresolution/540.png | Bin 1541 -> 0 bytes .../media/flags/videoresolution/576.png | Bin 1451 -> 0 bytes .../media/flags/videoresolution/720.png | Bin 1317 -> 0 bytes .../media/flags/videoresolution/8K.png | Bin 1277 -> 0 bytes addons/skin.estuary/xml/DialogSeekBar.xml | 113 +------ addons/skin.estuary/xml/Home.xml | 5 +- addons/skin.estuary/xml/Includes.xml | 284 +++++++++--------- addons/skin.estuary/xml/Variables.xml | 173 ++++++++++- 105 files changed, 320 insertions(+), 255 deletions(-) delete mode 100644 addons/skin.estuary/media/flags/aspectratio/1.00.png delete mode 100644 addons/skin.estuary/media/flags/aspectratio/1.19.png delete mode 100644 addons/skin.estuary/media/flags/aspectratio/1.33.png delete mode 100644 addons/skin.estuary/media/flags/aspectratio/1.37.png delete mode 100644 addons/skin.estuary/media/flags/aspectratio/1.66.png delete mode 100644 addons/skin.estuary/media/flags/aspectratio/1.78.png delete mode 100644 addons/skin.estuary/media/flags/aspectratio/1.85.png delete mode 100644 addons/skin.estuary/media/flags/aspectratio/2.00.png delete mode 100644 addons/skin.estuary/media/flags/aspectratio/2.20.png delete mode 100644 addons/skin.estuary/media/flags/aspectratio/2.35.png delete mode 100644 addons/skin.estuary/media/flags/aspectratio/2.40.png delete mode 100644 addons/skin.estuary/media/flags/aspectratio/2.55.png delete mode 100644 addons/skin.estuary/media/flags/aspectratio/2.76.png delete mode 100644 addons/skin.estuary/media/flags/audiochannel/0.png delete mode 100644 addons/skin.estuary/media/flags/audiochannel/1.png delete mode 100644 addons/skin.estuary/media/flags/audiochannel/10.png delete mode 100644 addons/skin.estuary/media/flags/audiochannel/2.png delete mode 100644 addons/skin.estuary/media/flags/audiochannel/3.png delete mode 100644 addons/skin.estuary/media/flags/audiochannel/4.png delete mode 100644 addons/skin.estuary/media/flags/audiochannel/5.png delete mode 100644 addons/skin.estuary/media/flags/audiochannel/6.png delete mode 100644 addons/skin.estuary/media/flags/audiochannel/7.png delete mode 100644 addons/skin.estuary/media/flags/audiochannel/8.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/aac.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/aac_latm.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/ac3.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/aif.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/aifc.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/aiff.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/alac.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/ape.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/avc.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/cdda.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/dca.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/dolbydigital.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/dts.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/dtshd_hra.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/dtshd_ma.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/dtsma.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/eac3.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/flac.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/mp1.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/mp2.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/mp3.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/mp3float.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/ogg.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/opus.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/pcm.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/pcm_bluray.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/pcm_s16le.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/pcm_s24le.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/truehd.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/vorbis.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/wav.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/wavpack.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/wma.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/wmapro.png delete mode 100644 addons/skin.estuary/media/flags/audiocodec/wmav2.png delete mode 100644 addons/skin.estuary/media/flags/rds/rds.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/av1.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/avc1.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/bluray.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/div3.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/divx.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/dvd.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/dx50.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/flv.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/h264.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/hddvd.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/hdmv.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/hev1.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/hevc.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/hvc1.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/mp4v.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/mpeg1.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/mpeg1video.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/mpeg2.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/mpeg2video.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/mpeg4.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/theora.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/tv.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/vc-1.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/vc1.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/vhs.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/vp8.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/vp9.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/wmv.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/wmv3.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/wvc1.png delete mode 100644 addons/skin.estuary/media/flags/videocodec/xvid.png delete mode 100644 addons/skin.estuary/media/flags/videohdr/dolbyvision.png delete mode 100644 addons/skin.estuary/media/flags/videohdr/hdr10.png delete mode 100644 addons/skin.estuary/media/flags/videohdr/hlg.png delete mode 100644 addons/skin.estuary/media/flags/videoresolution/1080.png delete mode 100644 addons/skin.estuary/media/flags/videoresolution/3D.png delete mode 100644 addons/skin.estuary/media/flags/videoresolution/480.png delete mode 100644 addons/skin.estuary/media/flags/videoresolution/4K.png delete mode 100644 addons/skin.estuary/media/flags/videoresolution/540.png delete mode 100644 addons/skin.estuary/media/flags/videoresolution/576.png delete mode 100644 addons/skin.estuary/media/flags/videoresolution/720.png delete mode 100644 addons/skin.estuary/media/flags/videoresolution/8K.png diff --git a/addons/skin.estuary/media/flags/aspectratio/1.00.png b/addons/skin.estuary/media/flags/aspectratio/1.00.png deleted file mode 100644 index eb41c15f7f38af00319a2331a58d73e11c398e16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1076 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+<+8Ix}&cn1H;CC?mvmFKt5BF zx4R1i>#gV4_X2sG1s;*b3=G`DAk4@xYmNj11M>+_7srr_TW{yQuU%yz;;{aGDP!1! zUI+IFy$S9a?gs>4aac9=El@tek=kP9SYIjsYIQj0f2UbY^A1nX+B0!Ra#8a_Ze@nD zx{rbkMOdH$J1RUgmHqYR9G$)PVm&ekwlJMw1i{G>Yqz9qG!W)dW;a-iVF;=L3hoAR zN9XjP+1VFiWLbMPIpz9Fz3nRm-f}i>l$HAVF?sPhwc=HZM%KydpXEds+3xF`RwuV# zrv2dENi)oY&mVM=TkNz&eTK~Ad0|SHo>m+BBKU+>ACHba8EEVG^ntFHOZAMf9tp#Z zmX#70N@b-(xBp|?U)9BRQEu7PTZwz$x6d>^@~Y3z#-%lbzevKcv-0~Su6?U@7S1Y& z^D38H<g}%Ij&qQu=a;V<)9-AYw{%r%tfubOV(+PmJ6wS}u3P3!+Pl}r>!r@|rOtiR zKCS(_H7`){R<lgdD*k+3|7MfjC7plg>YZ0-UOerSp4*wTTF3LNCpZ^XTK@m4w3_j& zN|uKGwSCvu3aty=T^yq|J3q_6K+df)tXhA2zWTLyYhFL|NYb3M_|oc+uRdGef1CK^ zLlx&+d)K$wU8}j{-vxd=t7=kLA#M5bK;Apy&?`MUCk(5+mT$5Pj8;98rL}ru<};bZ zryW|C1OBfFk~?p||M}KTouZzisGt8<oKJmqX2$0&K`cG9RKHIwcA0z1OF!_0;YJ_j zycbVy=aqFk^+}!*3|AA`uJL3>&N;!Weu7uQqMe0%*PcK3G^w&q&iKxv3CTs5z3$yQ zd%DXj52*d&#^lVoN<pTMPnZ`^NKVQL{r4e9^L)s&4e}DLGK-JyySgl3u|rs7t<Kdg z8)aoe_uow4<@2D=eD7oH)i?KLXKiCSzUs-@Bdb*QY6&i1abNb_fu*)N2H!lQCJT0m zWW5j7$$vgkVd~kd`_3&trL-gZihH|p!jlhmDa+>eetj35e5tKEIM-(Ty^FWxuGX;C z-adTdqwe(GIoch|=FVRFXYVPQn8i2V7(WX2{2VrWt4)+>#L4+>+M>0K(stfVPkd9{ zTXmE_RnW8jV^Vx%f?597i{^^5*7f^$yP3{){Qs@xq^8vG$~8p?IG#MXZ+CERoFeNJ z22|+4hkz2gZ~xys{z1{Ohyj+||1fZiDIb7j5LB{31|1fZ|6yJ#^z8h^Wp%rN8J)q? L)z4*}Q$iB}O!d;c diff --git a/addons/skin.estuary/media/flags/aspectratio/1.19.png b/addons/skin.estuary/media/flags/aspectratio/1.19.png deleted file mode 100644 index 80289ae4663e93a0af0a420b576bad1f8bc2ba96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1049 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+<+8QlDE4H1M98l*Y^T>oCO|{ z#S9GG!XV7ZFl&wk0|WD3PZ!6Kid%2zT&$dJAmUd4{q*(na+WX!=_!2;?j7zM+zpg( zD7OgC5j@55l_Qbmmw@@c9fvH~bac)=Y`x(!pG7@9-09RBp@!M{cbFXbFd@SeRM-$u zSyXrG(507#%0&!!xfIwz(B??SQq}HOhZP+%4%?YdFrvZ+8B|!Fy!chcns+flYo`9I z`RBKEZ`iE<6}E?icJg)ak&h0ZeO1=GYjy3@G~-F%N^GO%eZKr+@v*qh>63!?n$>L& zS`@EkyFI<&nC|hI>Z5U9R^iFInqgVHX78P6yDj~brTdlJYyV_<PTV&C`O;~#G=rD5 z)@hhm<?Pv-{;BB5-n*yPPcQMZ^8S12T*sz>XFFc)dpmQF%*JnBHd9Tmcfa)O+MIvg zs%GB)ij4D5o_c(_{U=oHzwLtTnRi1jy__iTbH!q#asQuvZyotmZ_S=xWP9I2Z$jNY zpq$}8%l-Ag7s>qH%5E7lYxeZa!taxmge_x4`m~pZyg#0~DPzMog?298{&erP)>A(3 z-9BlSQukTSxc>R-X-h8WSI>BE(ROd<@kytq<tdky$$qo3>bCruoafH}^XToh|8KrY zklU>7wWLpbaa-TZg^qpRmPhXxM);mhcXcicQgC0fXQumJ%gc-B1&X9qM9-Kx!Kg?2 zV+%K*(c%MPokdS41*xX5dZOlEX}7OVUyAL(2}7+sf2MLTpR|dyJ}=|=yf7qTLh+8s z87nTx#51X<SnlGL7mteZ(J6meA|L!FY}dcZ?_w%*_XN#4x2$H(OPwb#qWkzc-)R2| zez%b))y3)DMfS47^Y6NPSS;0)kKfUew((i$?b!GF<}A&Ro+X+KPhXUmo}8QVus>+R z<Eo&|UsufVnl3a+=B#(_E}^UUmg(4DVX(C7Q<*jWR>{+=d$z@!`*rh1GCz57<Y&-q zyUo2fN;GG-Oa1VD>Ao|(EOhJU(sy=iS1ob&{HknveNXtsslVgTXJ6PVcJJG%HbKj_ z$gJS=W}Dic9&^6dkhhe*Eh0<!>Zaa3Pi1o^bl0YZ-u^Oi{?d&nMXR0owtBu<w$v<M z|K)s{Ox?UK#hDw=uiYwPwS32qImflwdmQ-G4l4ZcG(gP}XnBEsaqaZlqrOT-43K=F uz+RDXEP<s1Ey075KLcu>0P!Y2*1r{9BDco9+7y_(89ZJ6T-G@yGywqTPSrpF diff --git a/addons/skin.estuary/media/flags/aspectratio/1.33.png b/addons/skin.estuary/media/flags/aspectratio/1.33.png deleted file mode 100644 index 43e079495a8ffc53cb68c9eeb14782b6d9a10a95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1050 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`A9}hthE&{oJLmPxMFt`c z>wo()Z+S57z?1{i9Ju{BeK?&tBMwC{O<SPaqspV2Qm<ouFM*}fRqpwR<Lu@_MJKu+ z9OG!19Y2T3fe#ZhJVAvGOFupOGo^Lu>tyG%j3(L*G9YNsebLKVUrelN;WLI=qUeU8 z8o==6!?E@mMc!7+H%@i=G{2;$##`y5N1)xO$IOp!nSA=98|*&0#49n|u6MS+Ft5y0 zWqapb-lsOTmPM~KcFj3+;>u=egH!Hxdrp2ddYPp=A?DD$%$7;f@4pvKJh1AVvE}K? z2^#W07e;EX{(AZB*IWO*eg|Eg`O9YGtj@$mHBZ{pj&^XaWqMU*^mtZ8?1hUAAbUgm zR`!KColLcMu)03+XyBz&<tt;Zy#Lz&X`+1FCZUs_P4mm&-Y%PaY3ucZ(~i86zukYH zIRENt$C`5w9OIV0u-X1`zXzA4)joZ;9-xl%K80I?JU#~7tlab6<E7H<qL#uEF^>m2 zAMye>rU-SM2)q|^*f^(7V(kjPhwswz5^nI_SsA=*-HL#@8~<efj67;{{NU-ww>Q2D zcvtP_8fLs{?G@SfO1r*&_oVYT)~pirxM-Xe{c@{L({#}+!4v0p{p;ud%KU2an^})~ zWQw1Blh?eLpJuen<?U39_r;RE3huW3D?WOJD6W6X@%)X<zjKqe>VNwwpuD?rQIMx< zY@gKY)t3Lh3YUdVH=Mj+;lCGRTU*>JK6Td~n<?(GCdO^C8_=g+d3$>OvwV})7rR}2 zK0`L$RHFCFk{=R*Ck(CmUX@xDi4>jbaq^90Rd!cAeuU$6JA<9Ai|zzf{?zS7TJcAP zqI(a1-L;?3Pk-;C6_#noTzx%u9v8LFes2dfATjBunDVksL7x}Q>N*m*StaZIqVy@h zIcBg_|5CZ}cd1!?xxDkH@Md@0#jnJF8*qMeS>>I(?#1m_GoL)&^(nya$&zoY?+Iqg zm;UEiVaax1=(5kVnqOO@xJ{N%DgX8N&%4j-*JXY!`?>I$#Fp2Y=M%GnDp&XVKJEW} zxh$|d=FMgv%WtbJ@}}Q<^Yr`N-^*9-xfjcNO>fmdj<i$9cHEZQdVF$~;6vuGt@Bzl z;<ALJY?~i9U;q9%u+;s6oqgRek*!Z{LM;=Riau=j-*8GY2w$3S;Jp0j$n1wIfyE38 v>=0<v$lENq;2C<(0P@ida^Q=Qt!I>T6cAHc?s*ZI=NUX*{an^LB{Ts5;AiOM diff --git a/addons/skin.estuary/media/flags/aspectratio/1.37.png b/addons/skin.estuary/media/flags/aspectratio/1.37.png deleted file mode 100644 index e86c6f7a1d59a9ce9533243158ac97857c37be72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1115 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GlssJ=Ln>~)opZNzm4k?z z{d+IwEe>7^suP6vsLl}Dz@q8s-JzPm8Rev-IL$%s(A4HX+~Ta?p1Ja#Nt*Na;hBOH zN@5O%^*^{6EU~}|%&2hLDes5NMW&tWXPwBrV+}(Q2p-T#(>9IN@oAZHf-xox!w^&h zCL|k}bxqdYv70x%aZ$GQ#(kn0kvpDWdlS0fc9lKz2O}oaZBeB;2d}EBH!bd6ve~Xg zBDQ{lvf`xs8(HH6Tg@igZIKQI3j8c}|75l8;oTJ*FGu>R&B-sl^fIkKE39a8`16Cm zja6F3gSzFm?fhuCPc`H2sp*zg*>?PAp2x3^`tsgpe%-Ij&%=z*o!4%kE_tnb|C!XI z8^iebyUxD&b;bJ|k~{31PkmgoS=9fcX-T=FuQNYa{y(u<-A<nq-p`(+cqR40KZB`L z*Ds#)dUuZ7g>6esCZE`B-kBMEDd*`Wi-~X7+i#w(Y`y!&>$M->8{3N)u49fgN!)v< zFz@YSxB9=1e6eSyi@(ZmpLQ-PW9`;!Rr)dl%eShnzO-ZGmF-DKS*Q2^`*k?y=#q)6 zW4LbQGnMDuTo*mdtLmBl#lycY7d!o$^IGP&qul+tSteT|zXi<8|1tCWyR*NJ*Ujl) zeE$2f+d=bnH?v=l3HfI3zC-lA+v(FhTMM?Fa_Mcpru(?9=<=hSB^HXuB~IRVjeX9x z%P#MU_>FS6?O!9(lurcu2Ai&W+*WcideiaD54-oA_&EPY#^o35a!T)L1hZMXiD}k_ zYv-KWF*VqF!@()43EMv9CpfoOT~(Ugl9!dGGwTTNlNnRg$|S87@)X?54$XAqzpt}Y z*nEQXw{<yJeSgn9*1Pj`p<&?yog$0kUg_ZJhrQ*Pe!RJGY?aETGYy*lsj;$?!@YI6 z*{>M}-zxUJ<DswB{!%Aj(DGcGLG!H@1$s9R&rIKEsA}2eo%`(>ldy=(v;%J{UrLyt z>aIO@TG)P8K*)R9DIsOkr{22J^*+?qw4(59<>y~tUM0=n_IKg0J??qlxj}ENUW5e( z^}fqoy!fWRP7hb^Be$QD0+v<xn|93mBDgg7S@Hf<@4)8b71isOzT&y{z5T?+>{l~m ze}<&pe6{$~jez&IXM)ut-x%{+mZe#IT54v!ciI^fr@d3d8d=lkZF*P1rkpA$9-n<i zj{lU>9=4B3^Ci#RzyD@olINK;w)R=~%dI+dqxJ=tvc3In?Bf?`dG5RxyG^6apWEs) zo^oj_u%kjq&VuGI;o$#AqR&isdBWhp2Z0p~+-k}WPZ%&EOa+3TA#3mT#AUXCp;QjA P&|vU%^>bP0l+XkK2+i|& diff --git a/addons/skin.estuary/media/flags/aspectratio/1.66.png b/addons/skin.estuary/media/flags/aspectratio/1.66.png deleted file mode 100644 index 80ca726bccf5b81d7634a52e98acaf8419a40e87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1036 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`FMGN;hE&{oJLm4qMFt`c z>p%N4Z*ky`U<ziM_F&oq)dbF{rfChT4xGuH&YW)!@A6snFua51+(TFPe3gd+c}{ae z6&cd(GMF6rFd@SeRM@cdQ|2FSZtv;GyXG<PSj$iZf(^opr?%;cr8zmCXS@@}V2K7N zFr&c3QX56S=bC#|PW&@PRZQ9a=8E$kHC79b1#WAeCVnEaa=V=ADYsfN9e1UN3!YZq z^1SD5mNcdJm(I6sk36j^g5{3t$IpLakOdM7;fojIWYa5F5BzC0*?zGb&?u*;9Bvt> z93Fit)teo-IOj_GLQ8jr&Wh&yORnsB=$)qwR#V*<5VTZtv-qX`tM)9c^}4AKvQX1q zqw~`{rR<aJ=gj!l2A`@6G<&r<z_MuRN4~x9KlYzYSQE4Jx8S0G{u5Qb{Eg#k-vrDs z6RkWfc6Wbub^ZJ)?wemnCPhvA5c@;fh1IKDRqU1VnQH~}md~6J$M)}M*_s|<GoYb* zfeXLgs{irj=-K&;^&cLLUSf0bgo>_ykNnN6JbJrbf119ZllrfyqUcK7lvnd2f-I%| zQ?G06U4CN1Vcth=KU+P5oc%ADzFNYuu{_n}(JPf30YCix9sG-g1Kkw=FWdR$Ve0o4 zG1tD|4p6&(bIUyyckTY@|8M@SxT+fVEvH*P^?YH2hV<8sR#7u|%e-3IBlqp+t+@YB zSKK^l_<oLZkWu^I7`bovwme^bu{+TCN%Awv<L`Bzp0t=hNzzT_@1hBZBQ4j7DtD}Y zxEah@l_r;W>P7MjjVA>?calVkEF3?JzLT`Ot<PmSXX@gt$ozyItzWvG_>S+;T7J`9 zZOz1oCf~cA`Z&*L%$RoT$*Imh-oW&w+js1d2;F<*cx;-XbJX4|YHQ}a`8Fk}e)_{N zy{qEi9RBiC`ss~K_ci{R^QYS0|6Z~GM&{%P&zB4BcDpLeSj4mEWpD1Yh>r_&YIa<^ z+*WkyQ1!2W>jF#NFYL9miIz+a`7}r6*`~TGyG2Dt>)75)9Zi~l^3s+oY0B}(LdDc0 zum9Px(ff*y`0q7b#nV>RJkmWbx~*bK>E(5A&AfBB-THEM>C=r{dRJLDz5BY+SZby1 z`QWL{Y6|TCzj2CLIdh$0M1>792xt&F>;KzeN9Bvf3<~TJxTjIHS#ZH~)Z7H-VP?7r bnR-S!p(Uj=_#V#$=5hv4S3j3^P6<r_mX+G? diff --git a/addons/skin.estuary/media/flags/aspectratio/1.78.png b/addons/skin.estuary/media/flags/aspectratio/1.78.png deleted file mode 100644 index 01d70b832d9d6a8a0a4302b6841c96669c37e137..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1152 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>G+&x_!Ln>~)opZNxm4kr8 z@tVwrXeQ0ZDGjO>-WP-xu;?;+cc><C1~p9+@Oof(;%H<{U5H3aafN4zpqO7;db{an zABHzI1)L0)Sl|R^RCw&v@~@#{)BO4+Co)TfK{PzjP1EL$)ahyIIl;)I-5`Ss8BQ>w zz>h}PCOwTVxLg;wF@E;hHuXlC(Ag18h1IXmuihA^-uq>r>#Llm-QRPT?b$y4sHpDZ zGcT9g$L8~X|5^MYP+4sM^vA!1!uM?#iGNm9wff(kgBzX}sEEC9{JXu^ydaWCX!)gr zq*a1l9onZKm4_$&oc87D-^2(_Gjr=0)-QR7H^l!u$^P2f?^`mDp7U$}<A+ZbJpHg~ zaq5=$-;&~ry5g0)KV<i<FlI|n`1>-VZ{fKxn@;N)q9&=L(=XO1^9WAA{(Y0?ThUe1 z{w?@rJaOe3efGkx`$C@cil&@4F`AnDGA!<i#^hDk?>K%<7b=?Zx#g~G=l$fu!yyl6 zT~_#QENB^2`qAvucTe}ouw%T%br;{6PHg@2EY)cH^nX4seJ)`?{k1vgm+*v(nHDOq zl(new=K1(UefqKKE8740tT?x^`q%aC&9`<;Tv4$>lUJEr^~}9lvwl6A_U-20=hrVT z|M|do`sT7}JPF}<VwQJs{8x5a;+2!>+ERGDaON`2_PRS;H*Sm2l<ljm4relbZ6WD= zWTnz4h2zr4dsi|2j#KUP`zP{MBfIqXbGIWmwG<DP&+Ofke(qxV9SeTl_^GX{_nujw zrgH!1bG2!=IEs2S^5zJiUy*c4>(S{zr<+sb?)JP5sueI_<Km_?%S&V7#amP7H|k$` z*<>E^X0iMPR(|`Jxt~Ky*G9=2#qZsGrtWHW_r2f7C)x9BkMV|Y^?GvqgyJ^0kJYyC zx=nR0Z#|Xf{`$FeW3+!_>|)&_ABFoLbdDMrE^@cpRwQ0zq5E7jF4<7qv2Wu>lT{M^ z1+$K^&hC@xlM4B^wRKg<+sh)+6OvU54`)WMvx*E3o_nq8a))B_#Kl!B&#o;zd`2Uu zRnNBZ?smK8@)QwQ1OK`CIk|!>#Oe-Tlv#XX>l)1oN6qIjx^9@Kp})0KC9h-2u?@nW zbKbx82(Z~_FTBl;S;@UU`og2#YXgs#C;e@I<n}xNrk>XA-~8X&Ph3>|>Yba{Eu9{g zwX^?kM{%;%nWXs#gZ5uh3-~<cL)-a3!54q2EUcNTBK<UJeq@?_c1ZWUO&sf8_uP== zn)5&HyXoW`zr`NSFrMb?Z2j9X@BR7gqOM!NZ9*&)7>YjR&u`$;RA5Jk3`H<_U_-{g z^LCQrohKM!+3y0gv6`~O69!D^z=sZJ$oywm`*BK-30t;5uw-HIboFyt=akR{09369 A82|tP diff --git a/addons/skin.estuary/media/flags/aspectratio/1.85.png b/addons/skin.estuary/media/flags/aspectratio/1.85.png deleted file mode 100644 index 479804fde64ec2ae6ff4729fc41daa3657ae9fac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1228 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GCVRR#hE&{oJLh5LDgz0( z`1hAtUvQMLSTXf2P=3K-0HLg!dK8qW2=1`gyRJ4XVC9OA=O30YRg)_EF{$QApBCee zpNCZ$o?w9ve5g=DEC1Z)9GzNQHy6Hy2u84$hUF1!{}^<)HU<b<HYBo6U`B-umZ<QZ z+p<-5lj5%nYF>O8;lFgPP;0NM(BrG?9)Gy-O`%;Sv+}EI&z7rkGgY0}_^|7WR(P}S z%{@Q2;`LwkJ6iiTwM9g~zn|7+8un20y3fZ2-xS<y-{*;z3Yhu?c`i&le&0Fw>&i=k zpX%qF+*){TPlZSIyUt<@(~F1uHrxtduCg}!rnNWw)ms+(OZNSKfB*Bew=X~aSUCT; z_45v^ou`u)_g&d>-P?Do5!>_wAOBtFmEWGpFMR1&P@C+P&$j#CpKEKpBH@$te!-<) znb@uDmLl2tcTVs8q;2&yx-Z!4am<YXqxXT;adCXrr5iu`h~Ey{7L(s1yQ;AJ+nyCN zOA9}W%_`gWDRc5kj=qVHFFB?zT5q*e?A}saeJ%HOm47eCyu4calbx@4SztxF%yid2 zkJnLKBTh?iYqxASlgeB&apSxlaeXIe$~~JoxBOV5`o-&Bb3UqVtK-YRCimn;$mT~D z0c+Og_4ZiTl$vpK*gBtgnYr&=(&5Y)uJ*!@ZT~~c@?I3In4i_+)$`T)A;-6~lASZZ zsWXYpt7w(ko0jgw*!-*Vh2zYN6O)g>I{4<-)FAD~wDX4l_N$dY^Pe}%oOgb&-5$*q zbB~?-EI&!e(|Yyg^izFaF0&#fub;QldfL}>YV)PX>Du>?e_NF<IQiA;PsJ<L*3Rpf z=~Lam>Ve|hhc~^_b=ER2u#r}BS{waly+)6v&w`U1^zUAHdZMTO^Uh;c*PXbRN@eDq z;n^g1H;2zeu*gP#p`dV4ZPez*sZ)=gi}EdG-<DGAYk8)^pp-{&<;AnpkM}WIwmCfU zXgk>@_jGlMNzsp|AC@lt6n{dnA}IcZp+VQ`Db9WB%S!IN-PgBoj)Y~KsJPm^t3JOq z65g5LRXAR}#yciDx9Itzuyd?y=6>;;VzED7NGdgKTEM@vcIQtkTzWq7W99Z^KM#1# zIq&C{IxlF|q+f4-sW{3>p8ULuOaBMUlJoL?`Kx_1RZmU7b?u{aKv7N2|Nbkp+Ed(2 zPQ1F6th<)|>q=4cS?0e5EUj!Oy-MG=ziZ2ubH~GcPjhF@+EVxavh}`~!dvqBCq7Q& z4zgZuXf8K(=jlFwwQ1bG(#w}GU6y{oF>$khe&)Tb&6jqkC+9B=k?;E}E?t}PE~a~m z*VlJHPAi!I?$P=lk-X=T<K~VRO%)qAUK9WS?&h+!W+y#cGJacz_1?Yrh4a?-=XS+i z$Lij#mbt)e`D45PheeV>sJYsK4>{{M-23!-f9<Lr9xia<26lraLnarr!T@9lvPKBI cVftfzYjHDH&M&^3fu$6Kr>mdKI;Vst01Ed+R{#J2 diff --git a/addons/skin.estuary/media/flags/aspectratio/2.00.png b/addons/skin.estuary/media/flags/aspectratio/2.00.png deleted file mode 100644 index cd8ff2569f688b8cf1ed457c030e4585d9924e2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1205 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+<+8Ix}&cn1H;CC?mvmFKt5BF zx4R1i>#gV4_X2sG1s;*b3=G`DAk4@xYmNj^ho`5DV@SoVw{u=sE^-iYSpRz|W7G`Y zgHs&561)vm6F4`r1UG3idS4LQ!lIe@?f5RM7d)RCM8C-VQ#mwO;90@*gAZJ|8|KEx zFgfsHLWU=(up#~F*<YI6-tqmMGnq5O7%V|BL1eM0zrNT!7T;$KvqaGiK{bHk$qq{| zuaz$Ix6Pem^futyF^z21r)L+{)XY7l&0hO=Yp(G`_FS*^TjqMLKC#ItJu>xu;J@6P z(Vns9{<i#~eJkxU%-QF<Pu-fWx%yIMf8f8huL3tupW+<5V&j$6$90!~3Cyw0Nj25Z zxEwvjzB#w*tfRi>@nwO!&(DYVR&V!?TorgV-q!V}$=xq^9QH1d**vW~{golF#_Al+ z)kV{$icP(=L(#ox^`1>C&vtNEXPh*dxv~7JbBLEbm!;C}r^egzo@*`2;_{D7YYUKF zd~4gEMcX$?w#yuz{oLYw@|8{7-rS#GcWO_n=<@m7u5H@4_V_Z_ohO$X)%~7WzH%mu zce4KH$H4Hn@7B;ZUv<PSx5n<RMCb{}Eg36aVrA>6B|iMIeg@0yRPOw+WsI@MFG<LJ z7Y((Xz5QnB`m76QPtES>KC@%}_Jh1re&1LdxM@;tQOLa$;ioeG{7YRBxUbJ{8+(51 z#Wid8D#d;ezGEw4X~v!z`07z75C1*63Fcmp-+fKxySi~!@}h5jXPRWn7n`ig6BG<r zf7{*p%dBNaf2zr&WS`o;Lnox|`zE@0nWpy@2Ka>NzCXVCcGYa_Cp!-QIey9G^?b){ z!%Np{mu#A#o~(1WYtpG5oBy+@zN>v?!`<W6w@ga@qU=7MjQG3WB1Ly%c~et*FPQD+ z%h1}iI?J!ysgH5RN6CwVyerd6uP#{Ps(9Qv%`A-9bl>DE{+XLQcdUE1MdL|DgVa>R zxw6aMirGxd`x^gkwtdzE6qcAOrL^^8VQFY_;nJWek)j^2-n6Tt*VL5Er8;ttuSic> z(En!k>n*|ZIoke`&)uKzJ{OqDtr)3xqwsI#309rHY!m(Xid9i@X&Q6tl~qrbvYja0 zta>bQ`uV+Aj-6rJ?Y8x4uk9tnEUB-TSc<+Za+7|Zr7b;Q`*~5qTJN=6Ui^6^x-#>% z(xVA0wi!G0-P^X5FFmdIq_X!M)w##lJbiITdG=h@)#qzPI1S%==h~&-GrIG)pr`rK zqKG%e%N28M_ANEzuH3F=F#W!+v8Z97|IQofi{3tte%xU_Kk$rs*qUA~&d97_31gAh z4}M#lta{7yso}loO?TadSLt$#x6W%<XXkO_+xLyr?bid|3Csux$$JR$LFUK$&Hc}m wCR#GU6v-WEImj^qoJSGrz$};w1RdA6-+O&u`z4cLP&vfl>FVdQ&MBb@0JOLaT>t<8 diff --git a/addons/skin.estuary/media/flags/aspectratio/2.20.png b/addons/skin.estuary/media/flags/aspectratio/2.20.png deleted file mode 100644 index d0cebe276e113b2b80b0525c456c00a237b00ef9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1144 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>G>^)r^Ln>~)o%6SLk%2_R z<G;HYw4b;wP%Z$%7aS!mR!p*t?gs=*SY#LW9k>|R&FI8)B;dgFhx4_b<UXlA!_E@k z%5dRxcQC^VMoicsg9<l<%-iyQ(}ph}BQ6#(+~rbW2f;H*+lrD!xVaZDc*?L#)PWBb zGCV<rANoW(Pq+r=F8@?&k+r5jh<#~D*uJvoTf3(F{Q4ul%h&bK1-0%q7bZ{teXK*Y zWSZ9;yU42T9_w!R+W3d|ZK%-SRjIG^e&eA>DH>C!zvB6w@>ABd+QTwMd2VK<&}BET zrPm{N1%2Hj$5yj$fljDG`|Yw7OP~I#3;X@<SI<&?)$FD{-@^VCJm-7G_%uS=Z{Dm+ z7oyi33D9~U$y{vSD$`v3)aSCnck7LZllDsAn&NPCwq<{{n(py0%QiK3tlED+s#-rO z{I6_Pm+17T`)+1=<xW*zdNsZ4)XNX1&o<uKan7(K!1Hk2=KpJ!-1A=(;kkI-mD%4S zCQI%7l%+oJ^sO^Z%THc?p%b_2%8O{``x}*1CtBG>&DpUuUgy&Jit3GlOMch?S^CTW z_A0mHgiYcHcWujY=)bQXmSOn7XG&(l?vUy^LiKMWx4St_mMgaDtX$Zq)EAeua&iB< z{{<mQC(f5Ct=ej{>0;TNXFn}|-O1atvia!V=@E@-E|+Y>1mm59ZthS%+`C;q!+g^2 z-k-aZPC6FNv5pdZBGK#OdG}hi&gD6eF3ma^(^@g}bWTd$X3=2t2^xEUoU7K^EaSLV zde;=;D@P3ey7HaBZXdKX@_t3PY0aJuqTI=Pce_M(8io9MeWhS_@?6$4UVT+o|1Efb zyh&y~_Hd2w^JbaFD-@^Z|GJmGrrVWWSmC(H)f*PPF1blJPbkVSmY%gsrtw79%bL$} zuh-<tPe^XcdUEP%PPpltq?NM1S1fP;eCDzJlSNidiT3f?MMt8hPDp;3bwyY%+j^SE z%dkH0Y00a$zj^(7%jCf1lhW?-`8(#%5qH|ucBSy|&a<37`>$oZk_&pZJ~FQ&NAX=p z+A7APipZp#@?SpQxt|zyC)_HVlXbE$tNKk=gUq}u28F)SLDlhAM=!bh*nM~FZC`lk z++)?EA2%-@nzwbE&l8c;+uqzNC`pNkxZ!(hG3WLU_9-u;vV^U#Bqv93)d<#08Ow?N z?lEb7>wi6H_JNgZ(I3~${j#s){@T~l-7#-AYkm0~{j_6Z_Lb-2+TXmqO1yL1j%962 zz4S;UG3JeMq)k(4S<C^GCsu8S=PyrYR#RrT|IK2&_ahr}u0+j^4Kf=d^8Rn~Kj^vW wDFZCm{a_69ach{yJOKkTSfaxR4}Y-lDs;N>eI;84utZ_-boFyt=akR{0F_r83jhEB diff --git a/addons/skin.estuary/media/flags/aspectratio/2.35.png b/addons/skin.estuary/media/flags/aspectratio/2.35.png deleted file mode 100644 index cacb0886925840d3e1eebf8fe13dccd5bed174fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1284 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GPJ6mIhE&{oJLj(BD+d8L zd+!FJ4J;arUJXJESacY@CI~G7@eEWa2<_o#nt3+QZ1#kTzn0HA*9#d|8(ACAytAC2 zv8B?9&jAZ$c!CNW_N%fV`{O7)ff)wt=E(TZ*&ctIy@&w~DzF!UkpepsbbsGtay{ho z@9Q(mtX|zYxv?mBYNmYU@u=A^1dVOC|JiL86(7Ghqgy6dtax$d2W$7WQ%XzL_-M;! zPu~!28Mk%Mez!icp3(~oKJDHqHr4x;@m0lNf2WomthdjUsl6Y#S*-r%K^Ofe8KS0s zeNxt`QZrxso-+09kxQOCXMOo86OE%So&~FS#n>O-ne;hx-x_9FuS1IN$!|<|Uw-xP z*V!DtmDk_yb~~7Svqx!*W6zg+ADQ~TJUgoNB;!QT`l;&YUm1FO_&%>%Z8Pm^^0VCF zqt@aVx0u#0t}&ja<nG-STe-efe1?9nUyfq1#wC%!qxNA7&n-!<oifQ;(fx4kPobUj z-WL6S74n?-;p_b?+~2eX|Fi9%&uFe{=l%UHt7X)@6ETM~8YAbIic~tUOMg1iuTB5B z?t9DZODm<Sb$xPN`r0yY|EX)0T^!rAWlBly-<!oDTh_*DJqq`#`E@2R&Xl7p@y)bP zKdUD!R-cuty)rbga$*;gMefg>gKxa6z4P*AMW4LTRIc0~-`YPfBKjnc_iVpF!>4{q zJFiFXe!-^ddEV4k;>nA>XKL4UZOEEpK6Tf%o%0v&vC`cY>HA;IvZ_;hdG*Jec7ZY( zD|cqPw-$+SKl!%!+M!OqyeATDN!&$WdiKt{y>*$LkTq{!yy@gU(|$)9FK)=~xvp<f zRMK~s<CfZDdEQw8+3Qb5W+fX5?0*~;o2=-*{zj9*<t1tb%ia54?cVgz>32cD=EHlQ zTqiCTem{I~dG7qKb?XHarpVZzx-l(Gc<qG6=B9jUmg&4{mPxa6q^^iReAf}bRpEGj z=;GPcOP=<wI{CnFGMimd%|fYM=E~aZDKqBSi@eXW*65ItEj4@dCS-fomHUitefc`> z`?GfZ?fLR{)rqhhVRaj%lFx4KD|5YdBJj>HWyQZ6vqUb-gnH+1pZev~k53=g+CO;_ z(0g25u}1yaw9P9ySNbN+m$tKSmYG|SwZg69ikjJ~H)hvPNuQ5;=(eM_fA`7|{@)7j z%WqUK(p<j2>dLddn|R8WAG{m&R4njYl;y`Gwf())9~0%Dym0N6n3;C{=}Nh+8_i!Y zSzLB&w#(Nx>+=uo*tT8`boUHCJ9j2;d8FjGpF(#w&a~JbAt1eVWwp$${g+<ff2f~z zcZ2^a!`s*F>OK0rpGe-+vV64I%<S`rkIN4%*=g3uJ|XW(ROMFxyBqk6O8zwlKV2cc zRWegj=J)Na_{al#ro1*Y=Vwdb;do`oSX6Ro*TM6*0_Xe+oV{`K=_P;d<%6HUycWCV zPH$~f=2_w2^DVE)Sw7g^AcGE1G|GSwBLeRC;rk(t$nk%8EE!;B1uD&8i3%T>f9Bs> XFj3yr-8>gqx-odV`njxgN@xNAkOM}f diff --git a/addons/skin.estuary/media/flags/aspectratio/2.40.png b/addons/skin.estuary/media/flags/aspectratio/2.40.png deleted file mode 100644 index 35aff17350da23bf036066a1700495c5077c5fe1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1252 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GR(rZQhE&{oJNtI!BLe}q zdM^dl2|^24G#I@+yf{=<R2?`Mf;b$iEkc|4b1s~irgQSfZk0KoxBNUK_0aN6j~>rH z7J;+|8ElZ@1S2Z^uF8Jw&O%OvpuJxo<N3GOKeb}$KhY=yLZ}8@v{pWAdiBev?}6EG zOI|!Ux4!D<@AY%jIt9;|?)$U%qGP`7zTcagci&woqr5juZqKI6oBP7=R<BySGuo$b zVc0~$81YL}=UMH#Jol90wXnl4u1*%awRPY3fa=~!MLl2h;zGqs76z{_zdy^TLuT!! zWA}oS*DiK@_FHYPN#<PJdwXjHrYV~=trWl3tfsr=&Mcu;nYA}&yYIMGY_R6+UZ*+Y zd&7zj=v1>PukBIDj}p`R9~a1M*;RQ->(_@ha+Af@zKr-C)i}eHD@3+Yms?etW4h*( zjM%B>QCEaMKNsb{_TKDKQB9ne*WXtG^3zv;{W$eoz==Q!2l0QA3*MIORJJ#Hn5QiE zZ|6N}_k_=<lEa=Sr@LQr+%-k~NrvTFo0ku}M7ylaUgYzZ7jm`EK6mNO)72X<9(v;T zL?h>ROh#GnYL?WZ8$XXbPClK!=KfL9sSz)nZf38W+IBEhB0A`tuTIeym)n~jJ(GW) zS>VHWd($x~zwn5Cn`i%x|LF8}P3h9rPJMpXllq^0kLo?@5tWf_muxzvY@Nvo4Uy;T z4$oIuZYB8SMeVHF_7lJC?p^v_a&^%T1NpBKx`Fajr<Fd{y?9pW_4X4Nz3&wSU;HEL zbvt)Q!|^94UT5ABwwlgmUu>f??b^h}?5~b4EMa=R$v3KR*}P+>Ytt>?9ICK-aN?rn zHj5|bi#J|9w?(Gx;l5MHRhxHK?=v~Gal+#8H!UAlcs`rAVy^2Cr=w2_8nve?%RhNh z@%8Y#Nzb+|+UCBJ>uT*Yhra(yj$M1xd1ll3B9q+9rMHU}j{B~dc)!E!?b>5c3w-ly z>{7%pd_LYRW4n6df7`9uf(vCP#69PD|2D*WrWrf?gvE=e@>Z9vQfUq6|GLDI{nWY6 zq}QUB>mB=MhaTon=#;TkRbKu@;rQ8A57~FR@a=XLpS^y^POq1Nj!$+lnx5O4e5K&( z+XZX%?;c+`N!+iBM}&9x+iS~YUHkm5zIZHn?0oT!Ftab`^tOClYx(5G#M*^Y9~YZF zExPgGYCTh8wWRf?NrDQ;MQ;e-v+Q60bA|6t8MAU3Q{#i%RF6LE-6W{suAXJ`@K9LU z6*XP=uYKORC09a<-(P8qefVI*dHaL*W|z;D&yE7hy7zst_#<X%+B7G0`m((;PbQ?4 z>wbEwVVyNu-96ah_5Q2Ay$qIC@&)T;Qr&Zlo=!Mc#r33r>9ws7mb*2`%>5Ab;nv*n zuS@qVHv9K`&LnM(;`io1+mFZ|R-SRKPF4EVLbDT$+nI_O(4Yc)5f~}3!{8qy%LjH2 pd@%S!vWNjDg-W9vbs+y4|DDjS^550nY=ET~gQu&X%Q~loCIAdPN)!M9 diff --git a/addons/skin.estuary/media/flags/aspectratio/2.55.png b/addons/skin.estuary/media/flags/aspectratio/2.55.png deleted file mode 100644 index a592e04fbeeabec2baddfdcaf60310e10aee278b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1131 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>Gj67W&Ln>~)oqe(Nk%53~ zyqAKi1Lr~(4Mwj9ArH;~rYQ_Set?sPqMqXKjR~hCrkO@rrhC5ot5kTbJ>vSM44b;2 zLJjl9pD<v94t%KaM{H5T8GRE*O9m)>@bXE;!(D$P_Dx_$hn5qVK?tT2LRY1)n5i~v zihcan%bwz&E5oAL`{PYdiHRPLT77$YdF}*d*Pg9u_mtIjOMPW{P8%$DE!})|(Vrd} z&OI+zIKIB<R6Lg_-TJj|#h)da+g9lHo%89C@!WiD=dxpd;k<Ky`)Zk(ospZreRFKd z2f6zxJzRztJ~~=FF?f7$v$gK&^VR7KJNM_e|MHON+Opz<<oDj49F{#gTXOeStdo0u z;!4S~zi%(^N##2sE*horUCOMkTju2IPmZ6D9r=^FZ^PAf$vHP(E^&=4)_7vD?)23U z{<fLvrsXDAKL)!-{F}Z-!(Lc+?tHzVoqH7AAGPXyk-C#`EKT-1&$C5^PWO_#Uxhth zu+!LFsogMl_SEtlnh~lUlNZnGOiG`%WyQ>GQWdKW-c8zcu{!smOW&G^=~K<;3bP%O z3jMp+|5kO;?kn#US3C8skv!>MDSj;WM%Me_D?x{v^=_>^Arh*;wcjes;Kaq>X6AX~ zf_hE6L#Nbt-n!;ha=Gu9ZqXN`{5hwi9CH>$=!cu{%y_r+{Mx{;Gaqj)b#v;wSMdJ% zjZ0VBgR5@6)XMx|pdDk9%d^b%lx|NszeQ2X@>P+y%H%G2RaIQsf8o|i)j!TDy-}>+ zB2HY?e%JP4RpId?vYS)wrfqg!82IGpr9~4KKY!K!dybjA<L<aQyYh{GcRBURUA3^X zJaFa8X~9KRziw~KJ}KTRbAN%`y!0jgoa>ers_SxDR)s5<D!pGdD<bM2$c*+=ZMUtb zhVwqD$g=zudRR9$QmkW(#*+#y(>v`I)2<i+b<FeXUDbNS%p)`K#Kp-{e`B1!E8f%C zHQg2{WmIIg>f*hu&6D;oc{}autkcc5tNuzWxQE?IUt~Ca{obq3=Gn+?Su=Il>%9*a zw*_2VQLvf6=*uFvj@8SyKg`VDTG;M$>&24EOFGlGzUw~D=+(LC=JV(k+t0S_T)r}Q zty%1Pr{C@Bs;gGtRDU*SW%c}cmE-9TWN(^T-ippmi>i)O-4&eiQuo;H#k*$JPEh_W z-ZxeGPS)lNM?UYXTj*QhwcWMU^tXb$SXkTs(&)lnQMqbczlW4hoGUSH#Rc;jC3`mh zH*dF6VE?19z>W@!6xczC0S*uFPiC%TKfwrt=M~uzk|->6qyD7)V|bC)D$l9nX$vew O7(8A5T-G@yGywpV>-<&# diff --git a/addons/skin.estuary/media/flags/aspectratio/2.76.png b/addons/skin.estuary/media/flags/aspectratio/2.76.png deleted file mode 100644 index 051e671b6a7a4457f3c68f12ee5e8f0af842ae0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1193 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>G3OrpLLn>~)oqad)k%0i) z{wWS#4MGc8G#I@UR3`{6XbE7N!l3HFxv(YR&|PNss7BT3O><%<&0PM+;z7mn)v=Y8 zzmyxY=1pM61{o|-;e+y%jfVR#usmUa!iIeH<HFb99{<FSrjo&O0y6}mn7g?6*Q~5H zAK(6-eC^ij8$aIPH}S97zUzO<D)X;hG1Hfd{ohx0mCLf|NJLiD&)Iw9N`94Cwpq=1 z@5b46>>QV+kwjMdyGzyQW@Y|gS@fkOV84e)xtg$&d)PILimWxILHD`8L@s8#nf!uv zQP)r1C+fUP$8&weqF<|i{{Jd-`NHqQPduI_=q==J<-Hg;`PzHqYwH4Mh(+DoXO}f) z?Vo3UyiYvR)@^@qbaT~~$nWoi|3os+l@5_UYZUf%b<UYXEi!AJw_SasXjm_@d&AjJ zZSlEJxFlCCoS<A7TlR2%$dtKy<u<D>yWZQdQ_FlxnDa}GyVJy<oY*rV-PZj($E{FV zpq8~}VO6>dj)fU7RxMJQmV5cO?6&@G%ilgJkW`#w{;MiKrXz0q3AysbH<OA4c`U6K zo!OLhSdMSP>sfxYT(|?Hx7c}EUv-=GWzpBWO>Hu++LONDj=k~h(5^qi=cMx(SL?gH z{iMvBWhM3G#h$Zg^HX#0apY#kzW;W5l99>TH>)1~?AtGHz4GRM*FL{bZQ0tN%@_X( z2)qB(%sF)9)z#n2>z-BceX~1pQPS+<`7Lh^@~_xWwaiZX)P7fDJ-<bf$Z@q_W=At> z?``-MqiW?DS2n><s!}pZ@a4t{i-U8VE99r;eQvvZe@1rZwApvu3r+-9d_QcrT-VfB z@RgR`nd-ax@&9J>Y?`-4!9CEl?Agy_OD^iT&$L^rRZ+U%y-z<_dA~sJEc2M>g149p zXD3%~<u58(bgZ<0<L*6)tX!@4N_{JNO^ZZs-Q2J_`0bU9Y36SN7hc%8cE^uD5=AxA zrf&=!)|@XWYPlz9xi6+~Q`V2&Gd3=|CM(W0*IMcQNrR$4=~*{;KUR3enhRN-vz0tI z`=di&Q`VFnExw=I_Wub9Rok}a?CZG`7K^sKm>ybewzBdte_ZJPq~$hjz2_M$xf1Pv zTz>NDsC(|EPi;^5zx`aA?Jr%Nm$z}XJ@XTdb#6T7b3@IuCQmc}v+B_&&bw3fn0vT4 zm*#EeI&3%J>6E*~`trQHD}1BwRjjF8Ws#Ls<{x}c!R4R&%er0}`HPCPvn(GiHhWq5 z*s%Hv>!!RL?3P{g3xYvnnwuAV-+FveQfGAb-w#@)hwV-TwzwYJST@brCd+vL)86E3 zpYQBEnY!HLwUqQX$?G~nrR#fT8sP9mqs$3L3<wkiqKj^PKV%iyVX%hZk^v=~!V3e` g?9X8NK>f40SaYkqM~ALAun1!CboFyt=akR{02K)#L;wH) diff --git a/addons/skin.estuary/media/flags/audiochannel/0.png b/addons/skin.estuary/media/flags/audiochannel/0.png deleted file mode 100644 index a5be90821a49d392a935de7639f8c3191f508466..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 807 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`-g&w>hE&{od*@>4Z3Bt6 z#O3X*dz!*M+&^%|E!0kFT_@nSKzV}TB*7CL22E9m6}=|D6%mRqnin2^<C*n@|E`l# zXNIn5FtM*;QD8s8SkxecL``6R!eHsZ2c>kTwm%7!_I}?#bslq@C|EGy$lX&Xbi}w7 zUsxtYFd~$KBvA}=XEeFG>{j^0#kQfdbvB(+SQ6zEnzQ<Fu*c?^rrPt(f}TG;wUJMM z;r!<T=cj~DkXb$9ouA(tE?K{Cp|Z7qBV<=^%UZ15{^~hT<W64c!=-T<=QsA(HNQQ6 zQO(Bvc>VbasSz{x=CVCwDfGN9oIX*x`J2>Pb~}Gv|MdB4=ai1WS=(Lrt89sWhT0h| zzdqG<#U&SA3a4BApRwoLw@9&rMh{BlKdM*$)3w?+&!Ago^X9%h>vG%cGD1i9mx@_# znxGuIV_(y?P~$)`OQQ#0w%5Jc`>bNy5mR5FT!i%AI7jIsovGXP=j{e6?wYuGo=vxm zB#(Aw>O^JczL{wgl`sFEwy-#VGRRpo-HZdyo#v`Kt5YPR<X-&nLLrcqV)}eVHG8u+ zH%K_e;SbM}_nF5tBmWp!h{PCo%S7^MKkh%YE9GbYY}dXszxwnnCAM4)D41ce+D2@L z@EXa=)las3{+xPA=QhjcjOCN|*4^53{p*z7)%W>-{W)J=quMRg`bAbhT{ZXoxg+a! zTj%v2`9I&auPM%tBT;x(K&x5K`M!14>+|<bb?tj|+l=RP_Jdd7q9dPZtoz<k`8VX> z=j_+{sS_7Zn7(rR$?uO|A9?Nhd*aHcPb1@&=ym8tb>HA!<Cb$TT)Qm%gjT9-@`iKY z71;M_wAcS`*&Suajw?YdV32cL&ta!x%MMLk2F%*#$`^`J5+5)rA|*a>aw{nR!}>2= W*4jDDb}BFfFnGH9xvX<aXaWGyLR|>} diff --git a/addons/skin.estuary/media/flags/audiochannel/1.png b/addons/skin.estuary/media/flags/audiochannel/1.png deleted file mode 100644 index 87f541c99ca60fc6b4ca5a9c0894c9a29a57dd5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 839 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>``8-`5Ln>~)y>qejmVrpa zLvMev1lDpEBPQ9#o(X*kZXC)7I1*VT7=er_f>)+*;SiX>u)O!}`|sWxWC}O@zf--` z<4X`j>b{+<3hXBsiyCB*s0qwZ7%Uz5pwyPB?T;e4y?49&KWW&)1Xd|!7_~Ft=pt2> ziOiR{P<6w#fptqA@m}(M@xERCTCXDhzEwZ^nzz=pImD`H5$kc2-!@-EZNFU3)(STN zu`j0LTx^F-cg4Pq?T%}bN{rNW7wNAxnOL_bsUlfERA<qtxbsOer{8=v5WD2{xle2D zy9sQc?3P8|?|$>gTl%us?57e-wp(A}_&W3c*{>2SEN2~lpdcA@I?rWO&q>GI!8zR5 zS6c3~I2@6h`pD{9_&4KIRWoI_=4~!synBU5U;P5NIHhxs4S#e0Un66Ea9ZoP$d6G) zHHq71@=UJS<+*uo($n%;>76|?ya}@Q_v?R2KA!z*T3uM-oT~N76Qd3}Z<+iv)~tcQ zOK8rd#qyy!E`94ZbW1On|CN@%%O~n3kErE`i9+9ZnH5cVtid~Rv2*6q$rB#SNEbaY zemt!>H@c`~<E0hnRygt%)hy7vIbm_Psr&zwBt>`koAEpmY34`wO$*yKHB9e+bZ$sU zr_9_g*~Ljol4+T#zI}Oyr&sk|Vr#crZT{>@hW6qgqMDZup1SNSX8mfh;b{$Tv&~}n zCi}gqIDNhIx_a2E_YpEV`8PgJTeT*2$MvM+(b<yoRwyf(-Hh6#b?@He;|9yBUx&<( zstk{kwtQ7GGqcb<*Xyuxb*{I~ZSC8=FV|h#@%WC{mE1oWse2bHoIQ1NuE66%Dc4lw zy19BDSO0!y{_leK)0oMz7jI}KeQ2yOw3nK;?!4Wbn%9>$eKeL9pI2P{^uXC75AA?r zl%xhsPVl7mVe5`Rp_5Pg3RyBh(qrR)hw}+6N(9p91m?Mq%~|Ibp0b|xULBZ07(8A5 KT-G@yGywo<J9V1? diff --git a/addons/skin.estuary/media/flags/audiochannel/10.png b/addons/skin.estuary/media/flags/audiochannel/10.png deleted file mode 100644 index 49ed3ec3ab60c55adc4edd4a21a6315bae2d079b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 864 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`)jeGtLn>~)oqMrxv4McY zb@R*yv5dwk4%|CfcC%<OdL2-`q543`hjSrIaML?wzPOuAK7R_^zsT7)Ncb}4mxT+d zNGmhs{VU*Ru*3o<Fr&hnr-v)2iF+STc7DQeiA#YU1lJ^OF$zA~q%NVvJ|PUl3{?FS zl37(Iot{1;F<WK&i?ep87jD1vd3SouY`3u8s>=-XPMKG&4YmK-_5K|9`J4AoOg!m& zY2vmRz5PA%y>AYAYRlhSRk)>(ThLP__~o*R=UZ#tE_n!rTzr@s)p@q`$Ita?=5zYb ze7t^A)ol9mFHb6?J_h$it(<7HW$LT)Zm}!+@qM>iXC;@qtkTn1Qk$W>MEYC&T6v*N zl{Agl%-`MbojiE4E`a~9Xwd4K-1#NWeW9*>^5@Qd`>bRclzHW%r`=NbzOO>7Pdr>y z8e{3VK4nFUchtw(sm3yE-{e<UYaZGaXm_soi*aW1%b25@SNc!Z_Q=d#C9d?G<z~q( z#{Q=nbDMANma;xRRiHyAT~zQCyKT_Qxl)b>teH!{Wn118@>JVu61K=v(IJC%<q5~2 z(8!dmdYwZK4$e2<atAJa^s!yD{Y2Qd$)-mWQ~rtA_-osX9z7|w{)^VlFAIG$6ptJ4 z^^)7U%J$pCH4`R;J@Z@=qS;!=Rm9`+YU{ziDNQo7qShXm6g0s(a8=ggBp|=s=<CNo zZ!gC_>y2AyZJHnOqUyJro?Cao6N^<>rzDj`=}4~IJpI{(!>_ZH12t1?4%uw|)+x() zV&UrEx=(-4S3L4i*>3mTV`|m+&89_bw%=Nzc0J*(*}cynV)|~ZGV?Re-oUQva?s|4 zx%6}U*B8~zS5H;m{OFj?Pu;9JvZ5#dc%Qt>wfsr?yv_TI4%{yKkUn4G#zGxPVnUD% zMF@DH{?woFZ*8+(o-jD@LEsPe9El!=iD>B)l-e+oprwMvKc?BUgd|RX5o!SD90pHU KKbLh*2~7a}PG`>m diff --git a/addons/skin.estuary/media/flags/audiochannel/2.png b/addons/skin.estuary/media/flags/audiochannel/2.png deleted file mode 100644 index c7102b6c2207c4351eefe81b15324fc8f5668039..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 958 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`TRmMILn>~)opZbJm4Sf6 zbnDCmx-X_Bcold(nC9SRpc>=VAmqcjj3tIik8%2fKPum>PVi4W!*cTZ5A~19=}EF> zs;)_?tOw4@$1y6fV?u@^ROql~kHz^-8%iqIoRD#dWPHK^gbfOA+QNGyI#|4)F`VK; zHv?5aLlH~E#2BxQZwsf!T*^PPYehuslZpFnUt5_7y-CbTygTJu#W63o<fn}L7j~BQ zh4)>#_r}0{cIDAseMhu%g_g@`zxGp?UF~rCq^r=^-+@_%n?lp2BEA0>tno1_T{fw7 zL2B}zxyol{#J`HmT=}`^d*^>Yon-IQfA*=WQ<AMOsb8pCVx06;d860)h!Y=K+^=V} zF3x^=edeCtlvysZx{oF+A6uBS=oj<x-K%@&+?v0feXI1<+c&I}-d>&CR@wJlJ+!yH zEN1;wx64H(bN#~CEdG4YqoOmQFRCoJd6)6&nq4v_MREC0u6RvUa_1;|qkhWo`j%kJ z6}!LN6!>2~H8C(O>D9!L&D>W$?48<`GeJ4W%y`qm+P;<UpQg;ybM9O4*=?QO=ir%# z6^>6j5z73|c3!sJo;DfAsZ(Nl?fmTZ+?!+;PCDf%+Pq!TUn0`v^u)s+EA4LnPZMpH zvYfX3*z1(=l8QS<WosS#46YhzWzR{syM9K)fV1ex;$ySj-Anpp)b1IH?3d{ju{G_J zJAdump9YzWD*{(}7TBBqGG1}>fy9f+U7I$^n7T+^EtH*<{5XWuQYn1#{Rh(Ni6=uM zS1zh<zL@9hQN`UXe>XNsEvbZCX#E;R-r~PMWlYWFUpwy4tI)a{Ctm#B=f>>WVp{Gm zZ|1ywFpYJt^*USIP4`5%sj;j$CJ~$beDCM9g5svFJ59rK()jmo)+}l%lzCIL?OeLi z`HtHb+ntYDL{+AUR@DD`^?JwSwXvJtNWI%<XTq1H@!Q)uqQ1|oV{Wohwtn_<ChMQl z_x~JIH_Th|?dP?g&6AY=pOAWfaZSpc^KDlBhPTdbe7C7xenRv5+NRcj`K%L|Q6YmR z0zSx``A;tBw6{_*11$CaVbW$(c6bI#-e}N)4;@Cxysypei}SpAXeKaUGI+ZBxvX<a GXaWEi6Pb?y diff --git a/addons/skin.estuary/media/flags/audiochannel/3.png b/addons/skin.estuary/media/flags/audiochannel/3.png deleted file mode 100644 index 5f9b0cca6d47b105a4ff8c89568185b3a83d3c18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 803 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`UU|AWhE&{od-r1DEeC;y zhpLa+G8}au2zhYsU<q#0I5>qt)d9%VIH;#MWr59+14*YHADlTdX<xkmqzh*k{NB5J zx%Es(Q3qT5J4_RppD<WD@F7tr7>gQY6xgBEu2ZpZ1G&9_A9r}tu!RXM6J;2+GvMeV z<q#$IC89_gfyN+d1nS??pmgenOX;1sMLieAZ`W9D3oHBcF0?dSEo!&u)m5v~gGJ4^ z@A7y%MR%Vy@2{P^Cnk&Bo>5bjCv#ik{*_m1a!aRvTRz#B=kh<Jw2eN$4@bBbrp`V* zd+MaHeOI^n-cOj=xBlFu)1{tSzZGpgwRN8uiq73UW0^7kM!%CwD>JRS&Q9l!%zPSM zu*&qBVSRaCRamu2_~H%)_urDs{Nn2@cKdH!9H}F5-0Z6U?i&*qpM5-KGRJw-=kb%> ze6QSCvbXfk@qPM5cS=84?>+YBM|hlk<hn%p-;YH<C!c<ju~z0YcjogeAE)0uY7&-z z_0*<#)8nyUdSfP<td_f0TRU@4y>R|y5zBeZPdo}2zj?f)S;kl6^5?Fd@6_G>G*efp zT-!BYhTUzWhw`kI=hTlkey+UL{?F!=((%%)KOtR{lvf8XS&_)$-4|uLj4QB6#v=an z)!pt7p8S|CRXLH*^n@dCQ9!fIf0tfK(-Vb{VtP-1)|mW5G&JOIjP5Oc%XOj2BCEHz zr#${LVXn;F5aspS^}oIx?VX-H<-~mXZrQt2lP~_5+WhFp$wzlT7N0U+dTi@0-4hqt z)XZZ(S8Ypwwsdn5PyUXynJqJBZv5*kE-n%O`PTu*&e}Wsby;p*^*Q}kYRk`Cd!;Q` z{8e3EbK!hN>2&VDZ|>VIOm~jNnH2WfP7iMi&6{+B5vt@5I~$v_0BW+sNIeZQFJ$*K Z9?X97@1jlrM_}S-@O1TaS?83{1OVhVZEyep diff --git a/addons/skin.estuary/media/flags/audiochannel/4.png b/addons/skin.estuary/media/flags/audiochannel/4.png deleted file mode 100644 index 67c04c0e0392e35cd56cc06b9ab8b7e87eb0a03e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 928 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`(>+}rLn>~)opW*PA_D=3 z<F;lF+&P?ioH?8khoYFKD5z!tIRQ;m7O2{Io%pB0BgAsiw!fwN=f}o_%sbEdBvoiD zGnCa8a5Gq9ffJZfVNGi8-P4CIJ>95O#E{7fk((p2mM3K61J1S+j7vlv_)sCk6I58x zw|B<vY5p0qUP{k@@woX-s;JxFlY6<tJbKAGt@@3*34X`?9(|CNeDVCLOP>SZ<lA{q zbPDrLPN^!a-Er3OP^Yf*$>`kvNnQ*8^{J_9UOJVt?TMn~o=IWnR^45APkL6&+xx#~ zgiTXCzARYt$>c3nYxK9sNk1tl<UaG1*K*&yEu2>_3bVf4w2F6Pgy`(Z=NU`C9nG)d zVtr{YxVq5IY~k0rn?hU6j;?&W_ryb%g;EEmP1<+IZdLr>t#a$H{NpX!adf30_tYHq zGx;W-OJ7v_X9{v!&Y7W?zwfN|PQM9<)g0%`H0Fo4pD#M{YJ2}0bzPpbtM0uNe|^g< z>BPgBt%Y*~a<^DZTiSSQp8a0;``gV`oyR96x2a?$%=~tZd;T<KhrXYiC+?MMN}GIM z_|&40fqf2stCVi+EnKEu^rLN_-u=22N*r#ot0yE|UUf57I>>V3;W>tH##fJ3SUS~T zo{(R3WbO57s$Axas{D;sJ>#en<}R{0oW6VcUWpC)DJ9EQ)}ERDXmR>#w?Avd{x3g2 zk1_lFy5*DJUcT8`E1TH2c-NK{%Fa3~RgGL;UG}>FB;r!B_ODaN^n`iX=LKYjO^l0B zd!fIv=I*@K?CCRFrBvGIn)z<Btg7(ukeU1SWo7=G9WypO=iJrZ->)WMx$9}#7Xj-a z)119`9=WTuUt8=GEuVi%N?&JkOk%dj98Zn<5YOH3x7@3Y=;8X`bkNQ|sLaZ~*2wPH z+tj^{GOOm~v~1vwH2#?ysxWo_zq9#wA|<P*uDbW|>Q}X|-~ZkyUQ=`H=C6xO5B*Fr zx^UimGIQLE^h;a;#SBFlP=Or=&vZXzJ=Qkc<p~2Ueg0qz<8f;^!H5YPWYFOTi+_xJ X<}UKzrQL1>%y|r+u6{1-oD!M<zvGy? diff --git a/addons/skin.estuary/media/flags/audiochannel/5.png b/addons/skin.estuary/media/flags/audiochannel/5.png deleted file mode 100644 index a7f5f895aca6c171fb61991ca8e4b43fe74857c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 684 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`ialK%Ln>~)y?eIsmV-pY z!~P!W1*}^eyaTucoHQ7{8iWcsm$3vjU1d^j5jw$o_|(Kew+yv!?>_%%vV5Vicky3G zp2@`y+vWWjA34Z$Fc&KD9ci=>U<Yy#ETHg&)yvP7+eSpq>$GNX69p@|pS1Ooakr{) zdO07DHg26Y*0U|*|M4xBNuB!rWtEmjZ`G9Uo!#@7&e>4(XvVR;mc=rQ%+@?f7r%PA za_`hBZkB~>lKwpqTxD_OOpA&A<{LIOEsri~B%d?vy)JlZM%1ZUxg~3h?oBe8m2qTZ zccszo8+=~McZF{rFP?K`V!Kx40ke&A1uelsht0&=rq=9<V{f^9fcy55PoJ_ZHS~lp z>1Rz|V=%2yreaF%%6PT&KhGKZZ#%txBma?$hPN%InJIKE&iB}<YcH<1#Uk(0+fSvI z*20foOwXJCp8aj!giM*2Wj{ae5^$Bdcuy~W%I5i+e4BX-Z8kKR-WE8n{%q~_=llQN zySmgyMW@e0Z9&Il;r%g}K0f(&@V2H{x%}IkG4BoZi?nq6Ui@r$E7pE8tI_-B!{D3u zr@ucG>eprwwE1<i<5!LNIw5zrSJ$$?^A)PBvi|S7CaPMs)Tnvq-rd6P>u&v4HoJRt zVufc@#P1Dy*O$Jjec#Vrx989DihVgt%w?_R|9@KA#`8T*eeV7zQ}(?~-}C8e@myZ* z8O113slW%0USN!U*!snuv#e~=9ZP7`|7-3%%JB|08FVni5(FfU9Si?4Jib)!zGSIO R3NRHic)I$ztaD0e0ssxgG4%id diff --git a/addons/skin.estuary/media/flags/audiochannel/6.png b/addons/skin.estuary/media/flags/audiochannel/6.png deleted file mode 100644 index d35e28d8e43c2a32f3357852d3dede3e3327d8bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 809 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`K6tt~hE&{od*@;3tpJIJ zhy8Nm3zQ#lyyPfhkzsV_05T1jdIa1j2%h3FaQd~_#MfXJSCxtR`N#c1AMY&w@a?qk zg1%D?W%q4ZI+zO;_>MGM2(UkLkO6WU;4Gl9<Ep$U``5io4;vd<7DPCL<S%q@p4u0y z)2ZRhCldzK1U3Pz9cI>#N1rs}|Nba_xa)LQtIb=BJfVL#kI8R(rS;5tri|us!?!!? z>krOyeZK0^i`J7b&&TFOzpt<OtfHkPeQDlfwTCAE<ifV)pIcD7_w0sC9+l3sVoQHi z?29toVsdGo%zJTnv7_v{`M!Pr^JI3fHs9vI)ub$Zy?USi7TLXKy^FqW&;8S}*y2}i zjBj5yzt-_69{$4T8zx=qdwlEU#_r#j*Zz*UQM_d9<BT|$`!4^Ut$k|dX4n<DCf6f) zw#-?Pw2eK^YnUcKX8l~T>gi$bpuOh;>t9{p*xyn3*z}5y*~emw=dzDh?C$NbQIgA+ zzT|o)IZ`%l(~SM6Q@>8xy6@^jy-B}jOxeF`{+ob4{iww&!cJ8G%P6rq?_8~ZW1p2_ zuf*{)1rm?izNCI-_Elpjob&wRwp>@4!2JvK_zD&J{+s5_RP1=1Bdx_(IA>;;v&`FP zTMwTKV{q<K?%(sZeuhs{<lQ)Pwc|Q`fo_tm#WTDnO+Wwn^7nm~yB+3T)tfi{>5{_p z_N}L%zl^gxzv`tyx3iAFaEjFGW87c6kKLJJmN#4Wq+j2jH)a3w>#pu7uR7SJJ$W&| z+@p6_KTk-$V_Us(-_Nhs&dPUrUz+7T-uE@;iA490dqwAWecZ>N&(zh?5qPt1&B|KW z^XoHHdRh9`&wKc1rc}S|>Mggwe>k$^aiyOv-@1;&-1$83lq4Xj37Q-bi7Y<m`}$ff yTMspMh}c1O2}{X?Pf*g1gA6PcL510#n;+yADnIz!-2#{e7(8A5T-G@yGywo&q;;17 diff --git a/addons/skin.estuary/media/flags/audiochannel/7.png b/addons/skin.estuary/media/flags/audiochannel/7.png deleted file mode 100644 index e026a267535e4760269b8d0266ee7ca18e0530f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 856 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`<vm>-Ln>~)y%Sja$U&t2 z<LP-)2bjd7I765`7&sLeRT^598od}eT@NWK{$^<9Y}sV3&pq@0acAer&*rGz-}!mJ zaigE8gYo_trU}eX7%Uz5kf;-kMGZ0v>`>~~DeuSmwh>WsvQwGcM8V2Fh}=CjSEPGd zz)Bf|HAqT<Iv7w4dwf#j$omz3>ub`M7yI0*Js8&&_}J8LQhEN(=aENNOz{rulDRue z>hg`WdFisXhrMn`xV+!8x2JMh!t6;`G+g^UdHinQmPntwSp0Q`sK@Q;W@*dc*76vi z51b>Vn$lNwtD--4nN;lBoZXk*;so9vR|`y@Et_`o<AKcNd7>`AH~QUM`$+fOlMLp) zD!Dnyvr7AFl@8k&Z=CR$(Q<ij?6+Loa`VSNds!~Ar%d}Iet3<-FWV_1zY;%v3+r>- z^l4-7*-YCf<vypfy-o(^Cx~wiGr4Qr?OUT_%fmG}xhy>{NkIDR;xlJ1?vApIJ*KDh zGL~<0GV9i*TF1@5KG%76v9s_@zy0^#UcIh~$rEz;vw2@^3e$C)<epi?Gii@u@71IC zVsk^w92z%0u?TOj)sg<Te$rLJg<B#l?eba9oo`DH^wF+<aP8Miy_b;zD`ccXi~bbH zzA%&NKJ;YASBnp??>4gQPJL`Vaq)GD2=*r$p?$TB+%L;y{F-vSQtZj^&js1CKD(J; z+U$JV-MwX^ovWQc+pPX%p_lQ0SS_Q>H_XvKq_M9rTX*k_BjJ;r!%m7s&hq<}b$sHg zms5rOf4&Nv{VLro)ZDTz@b>ln7Ppt|I{%1s{iV6T?_8_wtKAd3cGVIc{b{dX>#m(~ z>rvv<0|xgdXGDo7*L>Z*Ss-|FI=fZq=ZMKNAHV#6yu$2d>F#+xeW8z|R{V?Cx$w4b zO-TPN&AL73RZlefSk}B1?mB}=Y2XBhloo9azVH9GvS?xmA2f|Uu)lDz$KffG5^!>4 iKu)eeLl#*4V=%mQzOuC@?JzLMFnGH9xvX<aXaWEVM1DH} diff --git a/addons/skin.estuary/media/flags/audiochannel/8.png b/addons/skin.estuary/media/flags/audiochannel/8.png deleted file mode 100644 index b32fc36ab43e4651fc4028405912d9e8052c45f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 657 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`B0OCjLn>~)z4Orbkb^|S z!^1lmv`biJ7ivG?FktFipnQPCps7zm+n}kZ!L`$Nl2i4^_JZ$w{x~+N9e8{_`?Aq3 z_LaM>^BW%s$TT?@aPS>ev|woma)gk%0qgFT+r2t+>66q<nN5*`AgLE>-@>ZGHlA9t z_oU)%QJ5~U5u9*CfZUkO?hXDsmVW>Ad-vx(g?E1c_gH4G`*i7{iPxf6oUYtx`6!|- z_q_44g;w`&tNpoK==^x$lK(9-g*mfsFMSm+#qQy}`p_yJsnmCCx12t4H$jGfsc)~* zb(zvFSrxezJWCC?X=mq^6#nYS_>#NjfksB&m9tkeqT7!*US{i?ojuFOyiBm|`iGRw zld?l+sH`_PODx-C&V62N$u;TaYrXf**t6thpsV!MQcG=ii%ZXrB`#L7uzG&X=;a1W znP+WYM-%f7prOLt-m|<G=S1gg@B7wxSnzyr+2T+4?y$I<m>;_)UGS$g&*#wd@1Z&Q z7k9oam?8bJfa~L{A9t*DHk^=mKXqq?|GU|;Hy?XM=7)&aS;=U2>hH}{-0RY>_uxdS z{nIZ$jvu>nLi)JPEa|`MZ2HOq=lvA_WM28nD*CZ`dsR{H?!cQoHv0_if4`|Zuyn$0 zX`3V0K3hKTcCJ76FnfN;lY3n#F$j)BaBRdD{*5=?`CV-$EK2`6D_biYSt3+`BM*(+ a^_u(U&JM#p4O}M}fWXt$&t;ucLK6U)Iw`0C diff --git a/addons/skin.estuary/media/flags/audiocodec/aac.png b/addons/skin.estuary/media/flags/audiocodec/aac.png deleted file mode 100644 index 55e81409b7a822af15059bcdf6b0f6efd26185c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1042 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`Z+f~ohE&{oJNs_wDhCm_ z{ZkUW4hU^w(Pi{DQ1#&4$fE1$&7rEI+9Kq{`Q1)sBJ(HvK(;%-HH&N`Bd4D|Q{Fan zc|DWHb52VJY;XcIDwOl)>#1}QMhLp89~b_gKl!PA5d#`jU@rnA1$HFZp8n=b)lbVy z|JGey$h3BgQMi!o)E7N6bstt9xHIeL-BoMDT!Y?PcelyZpYzJ!c|QAS`3k<$3u_)) zcbffj=u@=JnEkuv+Oua@*WcRu=dZq%((!;ZUrI~gM!xi2-R<OgDX>!|Gh1z?ow8)n z9gUu{{;9RAx)&ddJbvfx{LJjS>@}v!#{>3c2O3>JpC2~&yvE8nuiuj#*w?Bojr!N4 zal$Y|qW8?_TX`DqgLA8AhrFJ3KvL_%O>a{<m%f~D{wC(;?^aBo9g<&aFn`*-Z%QW= zGnbwBwwBDC?=5h4<0k*Fvm@W#(w;QoaQK^IoA5;r^JSefzi0jK4f%Y{R<MXi%B{Y- zY4V}@+VWBVD!0l;-|~$)VJN`8H@@y)Y!+8(mZaD3klSiykAL2t%syRtcj(G_`r-Ta zEEoN|`7StD`1t1z^{FRMCq7=%+xBf)`0}Q@tC9AX|9+J#;t_Gn^^P=){S#WM);|CB zyc30{N5l^&f8D<$O!>IRlMVhA8{e*x61}r}VzNllmDJ`{HV@<Gn(jZ_YgEMJRvSK1 zw|jc~rF~a#T6MfFm~i;E!L1{uYu?1?Ubj5<J1+H6(VnMfU#=C;<@*(W+jY~4$Fhs~ zb=qqWUu^qh{4OZ>^NAhdR%+Vr|1=y+Q$jP>l&sz+&2oHCgI%QMCX2}Jk~_3)c7@)4 z>)1JMajet)6*6V_)>oIk{k|#UW!T*7%U;`+KiOgV_V&q~o)bw+qraA?RhmB!UG+8M z#gYTJA6R)>=FG{t)tM@Ney7m$SDOM|e+7hmD!RYzwB0L1uv}E;jyRR_)$8IkJ4Ijr zm~8LFdh6-ALqB)SEtpi4l5c)XCn5dx-TRX}qPG?u{Bos2;?nc46F5&zwD;QZ-sD`) z6su72!>@hTt3JOMA`z%`?$o{Rb=Di-Bp#0_S`{~4ZNUbE)*B~PzI~8*((?4iObLnY zGTCCP;lD%PNB@mId*7AM0Suow@I7I`fIvYoa^TysT;_uWBK`L`^I@t6i$IJ+kTZ<_ ZGd6~M$n&>x{Q~B722WQ%mvv4FO#qZ^*dhP` diff --git a/addons/skin.estuary/media/flags/audiocodec/aac_latm.png b/addons/skin.estuary/media/flags/audiocodec/aac_latm.png deleted file mode 100644 index 55e81409b7a822af15059bcdf6b0f6efd26185c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1042 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`Z+f~ohE&{oJNs_wDhCm_ z{ZkUW4hU^w(Pi{DQ1#&4$fE1$&7rEI+9Kq{`Q1)sBJ(HvK(;%-HH&N`Bd4D|Q{Fan zc|DWHb52VJY;XcIDwOl)>#1}QMhLp89~b_gKl!PA5d#`jU@rnA1$HFZp8n=b)lbVy z|JGey$h3BgQMi!o)E7N6bstt9xHIeL-BoMDT!Y?PcelyZpYzJ!c|QAS`3k<$3u_)) zcbffj=u@=JnEkuv+Oua@*WcRu=dZq%((!;ZUrI~gM!xi2-R<OgDX>!|Gh1z?ow8)n z9gUu{{;9RAx)&ddJbvfx{LJjS>@}v!#{>3c2O3>JpC2~&yvE8nuiuj#*w?Bojr!N4 zal$Y|qW8?_TX`DqgLA8AhrFJ3KvL_%O>a{<m%f~D{wC(;?^aBo9g<&aFn`*-Z%QW= zGnbwBwwBDC?=5h4<0k*Fvm@W#(w;QoaQK^IoA5;r^JSefzi0jK4f%Y{R<MXi%B{Y- zY4V}@+VWBVD!0l;-|~$)VJN`8H@@y)Y!+8(mZaD3klSiykAL2t%syRtcj(G_`r-Ta zEEoN|`7StD`1t1z^{FRMCq7=%+xBf)`0}Q@tC9AX|9+J#;t_Gn^^P=){S#WM);|CB zyc30{N5l^&f8D<$O!>IRlMVhA8{e*x61}r}VzNllmDJ`{HV@<Gn(jZ_YgEMJRvSK1 zw|jc~rF~a#T6MfFm~i;E!L1{uYu?1?Ubj5<J1+H6(VnMfU#=C;<@*(W+jY~4$Fhs~ zb=qqWUu^qh{4OZ>^NAhdR%+Vr|1=y+Q$jP>l&sz+&2oHCgI%QMCX2}Jk~_3)c7@)4 z>)1JMajet)6*6V_)>oIk{k|#UW!T*7%U;`+KiOgV_V&q~o)bw+qraA?RhmB!UG+8M z#gYTJA6R)>=FG{t)tM@Ney7m$SDOM|e+7hmD!RYzwB0L1uv}E;jyRR_)$8IkJ4Ijr zm~8LFdh6-ALqB)SEtpi4l5c)XCn5dx-TRX}qPG?u{Bos2;?nc46F5&zwD;QZ-sD`) z6su72!>@hTt3JOMA`z%`?$o{Rb=Di-Bp#0_S`{~4ZNUbE)*B~PzI~8*((?4iObLnY zGTCCP;lD%PNB@mId*7AM0Suow@I7I`fIvYoa^TysT;_uWBK`L`^I@t6i$IJ+kTZ<_ ZGd6~M$n&>x{Q~B722WQ%mvv4FO#qZ^*dhP` diff --git a/addons/skin.estuary/media/flags/audiocodec/ac3.png b/addons/skin.estuary/media/flags/audiocodec/ac3.png deleted file mode 100644 index d01a87739e282a8c475951ff62dd843110a8975b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1383 zcmZXUeK->c9LGmF>1AH>PBX0$<!02eEK**!hRIttN@XsZZciI-PHy6^+Rod|>)1+& zHB+0nB2r|FDBWTgQ}S-CLR<=`KYH%!p3n3Ae&65oe81m6e{^3T&n+8`Hv#~FEohXR z9{`{NT+`GI>(?f)M(P0osC|!ia}A)rm?;`6xQ5h95+_ut;9nhX(YdwW=7NoRE*KiA zfltl`Ls1$;jq@%Gjj<0dYU%0TU6hB_i_YzU*|U-y_-ZX(Fdr4L`u_otzow;ew*Ny2 zX!r7BJ`<57QkAHDh7~M`i4nfFkP?7bE58n`UEj2dTcz7vOyD{%k!P$u(t91RoN61; z&4{;MqI5b<<!7b8vyYvRL(Y{jRcH?!pX1(MzP>V_T62@tLiNJ-kQ9=oLa(N{`E(E; zJBt&iT%m~-{+Yv=%u-5C+0j%!xM%BVh2qJ++Lft_<8$bHO>nl#Nd9wW3_0S5$QMf( zbNAue(TtkLtd!H0-o92^+B^a@!V^h+^d{Xo_whdEb_%a~HAjMWx68v|qL4RARyO~- zBA-KMJwAzNwEe|^ZffYRvXV>3#D-x<DbI*ww%c&g#HKn8su?HjSvf(eou{|eaRxIE zFElG_buK<~*4VR%^RUbXG0iQm8?>34wy;xFZy^88_DbwpEt1wS^#)Yk-#42-y+xLt zgN0bq6qf_W8w?SjSi*PQ$JII7uUjmh<ov#r19_+0#ww4_x2(BF+Hon-rWOWNbnopN z)QJj4oXRjoE3$&}O6IPfzDMDf3lIGw>;>Y&`6#Jz$+TgVAV^(U3j63*HZARNqkRWq zGID3;0KhYTdA%a|k&QE|xxzcOCrq=XS@+8)124PL{iu0LNwGz<uag?nKl@lG*VVte z&Q$8);Z2$By4c5IrtN#3VnQ0C%VNE!oM7&+^kuQ7sHk2T(=;$zIh)BxH7m^{rdWdK z?MCDQuxF)6A|T7e&~<KX1{1rt%S^y|iho3?4E0ybmfZia-w1IdcUMi*jZ3Mzi#q-7 z*6IrhiPTaX!s0PsJ6^z^cwhPs%K5&?)$HRSX>@*cy;5=-pPU9w1B(;MxA*Wo3@O_a z5Fy^xwD)`1K{00{pm7Yc_rsn|;lx3IpKLARbkic0^2oTr*6fdoKbepz$@U{8h#can z43_*9aK8eXpl>#{aGuhdA(}lfw)0K!&u!RA7cwr`hL07b)F$5ni)Y^V*H#LYvT77w zU$Sga$kDOW2n{tDMvSiY1;&@e_$X(`m>n0w2N5r0?LM)+22uE`>drLs?!b#iOM~uK ztRJn(6OCYX{zu0OqJ7i3Qclar=u?N<!bBQKwy@k~!t)EC=!Lna5A|7v@C)mE`cddh zAiD9S5vDz+C~fScL_1{BK{MUim1Jy`0iv4*-h+P%@-Lm0M=+MfvQf!MrF199q|*S0 z)9g0oOb$rUJfR7aBy*Ud6o7~2=En|4@3*t`>$p~i*%?tRI%sWaRhrqlddHkq!NkU` zd)b6CQLAD{Dx8=%55>`FkUH+f!bwo7Wixz<GTygIWSOWpWMgJqMS$|V!^c!KNA&Gz zWCn}??{I1-^<*juP?~BzpIi{nJrLFzs*gRpSyRNWGM;@6^DFd$E9G>QiFec>1{oJ{ z9TNT<F&`Pb2&HnvcB$WO7@F_4U!B?Nb$c8z@*fTWzlMrwxZ->hfhGXIn34=^!{Mrg lZ+xFqUmXALBwG3^AY<1==!1B<VC{hd(C$8NRR>Qb{{#3WhDHDY diff --git a/addons/skin.estuary/media/flags/audiocodec/aif.png b/addons/skin.estuary/media/flags/audiocodec/aif.png deleted file mode 100644 index ce4677858b5a06e4409630737b5d2fe30939d142..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 771 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`PI<aGhE&{od$+srmV<~x z;KV}atVXdrO~Fl33s?i2Vw*A@Ra?X|8ijl~TT)~;imm2%E1U2whBx`|w2c?$e!utb zR9fMg1`GQd76tYbj71GHNYn)8Ck&Pjd{FAl)b<x~{L_{lcY4yWg$b-u?PB!Hl}U!e zeM;<0L{U|v7*upeFtu#W^iAL2d@Jr}o*r+$P4T$-rmRT!PenGVrxUU(Zyn>XIrB)X ztJ`AJ-k-MH=e{qLSj%g<Z^|E~i6YBu+h;$m_&Lq&(y8rNrC;$Cm8gjpO<1hwCja{D z)<sifKJM6PpZcdZ-NrGf=t(2X?C;n2ADOg!Z|3Ph7Os%@`lalaQcOAjmrbc%(0*jn z7MXrW!zk0bf0nB>d?Wgbb1bf0bra>O@02}xO0cgduj%jX4A-V5Pcp<7Pk;TBcjM!o zrLXqNYz4Z-V&xk@Z?T-*uMwA~DeIW8KDJRSYSSaHW!Bo#+HU8IY_4lA{~-|CcG>Rl zKIyGzZH0X&D&Cx{m;W{Yhm_^7DEV6JXR_DB`Rp$3t66roR>$^x<*H0o8JT$3b=js~ zfvcX)s0fn$9^(_&_gd}Ae37M^qDvBWBFcNLQq0AcKAEAAdO^SP<%>6BrJ<Y3R$une z&Q)F<`8vZr&p=1k@|?2F-J%&y-adV)k8iHmGjj~wRkHcHzPq5WOl_y@8xN<x;;$QC zzin<wpOW|O&Qp%d&ZZCk6fS#z(r}|+PW{n~HtzjTy_T7sE1dWJ4)3q*8_wU1jqG;u z37x;7{1)g~l?a#pCpGW9%--|N`+V)U{P_lp?{Og~6{IvWfqC<S|DGEb^LsvFfGYXN q%sp4I136iNlMXytL6eQj@%&$Gue-lZ6kGyK+zg(selF{r5}E*0<4<h> diff --git a/addons/skin.estuary/media/flags/audiocodec/aifc.png b/addons/skin.estuary/media/flags/audiocodec/aifc.png deleted file mode 100644 index ed9a26c6bbd3c018186cfbded3349f127c79bf9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1076 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`e|x$(hE&{oJLh%fA_ob# z_|HoiqZCwU2yJ26#uCIdZGmbAXHXM_V)`i<o?h}K>50)3nYuT5GZ-EvKAX#bBT$dQ zJbw<810N=2c!CNW7Jhp7N0WcrKU=piG6yy=onQpPxHDm1K{`AwbLKHigfUp6!3oSL zaIv_`DShvtTf0`|#ueTa$*~Vw)O+>U0+)%)pI`g=)A!2frwKBz7jF4Fb;*Zd@r{)m z-Y$7i?$`IXbpMo^^ZUNb2)7>F@8oM`)#sspmirp}tk(-R#;bq7TWpzU{U+kJcur~b zj;U|nsek5To+XmCTz~C^#j;FC%Qvy~>95}>(=D}jdbzve+7g@7Qunsl?sJJZa)0?q z>gi<hls>DU33Z$Qm)dQ-#(pg@ujZ6Y-RX_RCkit{`%e14>6x?kP)%{1ypYFjuEp0I z%wKCv(Oauy6#6Qs^h(HolgrO5H!M7NF-gKUD{;=ZZ>zY<7R|_tzhJhtPta0|Z}CLI z|0!pfl2~QWOciU*3Q)}5d7q<abx>Hl-1Z`tXAIM>&bqJQ{y6qm>7&XiB_>}wqAp#Q zF)@1bf;(BcXPJ4u*W)m!%!E+47MZ`?i*Kst&Ir%H(zW2k#m}c+oLc3*OUSc$=3}eq zQxg`iPyF)l{lWFW`hFhX75HgEpW>4j_ZMv1t~Q~1adkgyeE^?v-n}rD;PjN%<sUw- zTch0*_jE>l+ng6KH(hxg{q32U@$)^Y9u*aj-<DWzTblg#(hgoRld8wLxo<q<^qO~n zOcOhvd|bUNUc~FPrBt=l6M=8fJ}&rlYui`D_8FW-CH{M*KTUR9|6=V#D>h5>Pr4Ik z)gD~4z;9uE`=uhIrQ(`SuKe?!DoiL7aJ%mBdi1&-r`g7N4%{3|YS;9<O5CcPvE}f6 zscNpZ%MYE9nHy%&^)%zP*qt-CF7H}UP`=`apIniRq}$u{{aq1~3r%uLtbV7S++ED} zEMoWPz%#X%9-lp3=e+cxer~SQ?5L`qtry)o1DE=iY+{p{ziOo%>!sJx8+S*o*PSC( zoBvdO#yqWk>9YkqW*?A|yW|yK5<N92b9F!0dds}8FRxEt_V2_*3t`u(SAb?{U7l{Y zrXp+eg)L96vUVlx-(~gxzUJNVs&`tKuLR2P6+8EB>$Ons<=nb?CjSHv2fOXG?vT)B zabEhQ`q}>4rbdy2C|Lm|8vru}oAb}f36|=e^O#}E7BIW{xHZf}%`jjdOa+2|aQ-9j Z_WO39=e}^f3@i;8JYD@<);T3K0RXP&<?jFh diff --git a/addons/skin.estuary/media/flags/audiocodec/aiff.png b/addons/skin.estuary/media/flags/audiocodec/aiff.png deleted file mode 100644 index ce4677858b5a06e4409630737b5d2fe30939d142..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 771 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`PI<aGhE&{od$+srmV<~x z;KV}atVXdrO~Fl33s?i2Vw*A@Ra?X|8ijl~TT)~;imm2%E1U2whBx`|w2c?$e!utb zR9fMg1`GQd76tYbj71GHNYn)8Ck&Pjd{FAl)b<x~{L_{lcY4yWg$b-u?PB!Hl}U!e zeM;<0L{U|v7*upeFtu#W^iAL2d@Jr}o*r+$P4T$-rmRT!PenGVrxUU(Zyn>XIrB)X ztJ`AJ-k-MH=e{qLSj%g<Z^|E~i6YBu+h;$m_&Lq&(y8rNrC;$Cm8gjpO<1hwCja{D z)<sifKJM6PpZcdZ-NrGf=t(2X?C;n2ADOg!Z|3Ph7Os%@`lalaQcOAjmrbc%(0*jn z7MXrW!zk0bf0nB>d?Wgbb1bf0bra>O@02}xO0cgduj%jX4A-V5Pcp<7Pk;TBcjM!o zrLXqNYz4Z-V&xk@Z?T-*uMwA~DeIW8KDJRSYSSaHW!Bo#+HU8IY_4lA{~-|CcG>Rl zKIyGzZH0X&D&Cx{m;W{Yhm_^7DEV6JXR_DB`Rp$3t66roR>$^x<*H0o8JT$3b=js~ zfvcX)s0fn$9^(_&_gd}Ae37M^qDvBWBFcNLQq0AcKAEAAdO^SP<%>6BrJ<Y3R$une z&Q)F<`8vZr&p=1k@|?2F-J%&y-adV)k8iHmGjj~wRkHcHzPq5WOl_y@8xN<x;;$QC zzin<wpOW|O&Qp%d&ZZCk6fS#z(r}|+PW{n~HtzjTy_T7sE1dWJ4)3q*8_wU1jqG;u z37x;7{1)g~l?a#pCpGW9%--|N`+V)U{P_lp?{Og~6{IvWfqC<S|DGEb^LsvFfGYXN q%sp4I136iNlMXytL6eQj@%&$Gue-lZ6kGyK+zg(selF{r5}E*0<4<h> diff --git a/addons/skin.estuary/media/flags/audiocodec/alac.png b/addons/skin.estuary/media/flags/audiocodec/alac.png deleted file mode 100644 index a49527cf6a00e2f5dd3b93944bf1c8ed987719fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1056 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+<+8QlDE4H!|mNY8hJn-XMsm# zF$061G6*wPEVVBK3bL1Y`ns||;^Gm}l|66UwTgj(d84O`V@SoVw{zbX&UO%R2$wW3 z&~((DAoN7YgL6xR*95N)FAmiiVj7NGj#C`!R2;j1Sn=^GR`h?I;h_^&a(;I;`?m5{ z29wXd+ZiS>V?qW?RG6@~#>DPa@6y}J?#~!baVfBa;GUy7OP6=IP7Rn~*|3S}1S2YJ zkU@pU0^Wgnh3_xAzAl@-!}Ol67u()V@74$3-6!_>o%uey-aRF|SA`e7wcRy8zICRC z*YTra|HY&CrB#<#AFI4M^RYtp*Y$g4+~yljT75Y3&Z2ws`d#<$+F<6fGBssYpUKoM z`A@v<qR%}RGo7>LvyWx!(t2ZF&Fc}%PCj>$w-ePcw#xE}?2M0D6CMzzbozLX$CLJy zRSvFli)XCevtUKfL}!)h^KGBc)8n$-W4_pNXZ@TdntM+!y~BT`qSvW!=BH!6=Za=O z*&V*UvGKXalMiiyLNWhp|LE(yyT7+GHR+8xm!-`!-o>{L?UxV!BKz4@@i>q1)Q~)- zqTs3>;?ruEPX8d?Dl?BGB-ekQT{p`)-^>RlXBdlgHcOs8ou?f5@~&>V#MJ(x*NJ7_ zPJH=iGeR=mUjJSg99$I?D1JV$_IXa+rael>FQjeRmoHOv{Jj_3j*4ET;xhpuMq4X% zC#@81S2UjHkhC+r#436U`}P%~8~3c<wCk?Vym{WIo-=W3J-Q<NedX7ki|45KO8Hsc z{2IGo=iRC|EB~DOe#<l>_|EBcf4}XQ?{pU|(pYzDUrDR-N?rS>TGM&rHP1fLxSxIV zguh7Y(QYZXx9)R`XKh&QSmSrceD%Z9Zl}JRvjb-*?|xG7Qs5HjZ>}_LVdFPVe-fjw zdu*R)dFa%e1+{9QEbNjwwkx@>nRT)Fc#65s?n%ndJ9;-=+GeD+^=hAcu#{m-^u)!P zr*5enzu{f*A+6}m{C9s}Jz6yDl5O*jI?F@H4qXh7I3YWG!sCm!DSL|rYF+j;T(5f- zvG^XpO`W5?$6gW3lFAK{Q)SA3%H8wo+p%%+hL=_?lHK~#Hl6#pW7WjSyKm>7PCxwX zMWCo$^_@jWih5P$EB-XSE4y#{PgVV|LjAGqy<d8a_kZKOm#_^f1*4{15O0I#ulFq4 z6_ZcMFu;<ze!}q-mJ?{H9HbVm0;ci=<Lt-$eGh(on|Ou46_}qHJYD@<);T3K0RTAw B)>{Ao diff --git a/addons/skin.estuary/media/flags/audiocodec/ape.png b/addons/skin.estuary/media/flags/audiocodec/ape.png deleted file mode 100644 index 94e01abf201bf0a4391b2b493ddf484251129c16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 990 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+<+8QlDE4H!~gdFGy8!&&H|6f zVg?3oVGw3ym^DWND9B#o>Fdh=m|d37O8r>YwPOqn%=w-!jv*Dd-p+YjxY$6z;rdLI z15+PpH%>{IeqgEtcR<r!Ce;?9M`9WWrzCh?`0K$Z_F3`3JWKDY{*Nt&O)M4nVm*&o zD>FRaQ^?6+i3LtzMujy`Yrn+G>G1Z;PGt7bZjb@N2QK+XgN`<-ODM5V2*WS~RsV#? zswa<Z@lMOlRhvFL#rXH^*3;8ZPcKS8<{!JY;7!lEP5IZef96g-?54QWMEcwBXG+J# z_k~US{U-W}g@&oix1x$49V=tz7l^1=et%Ol>EPPEI{$jgbr;7ubw2U#n|yrdnSX7^ zUaq#BrDgi)g3wIGi*Mv)Z{D4DB<AY1*LMq-eJ{@3cqe`TC9ho{_Nq?#q&U?pAhRO# z>DkvyK6S___5Rr?BW)?8<@UJDw%#i+%KPHF<3ft<{Dq6BZNB3F^UUicV>iC=&M7QM z&%cgOy7_NrewA$UlLE6mmsK<V)Y&e6$01Sw#3NK|h3k^AsvLiv-*K!>GJZm<yB6&E zttphR=au~A!%B(VTUm=kJQJhebcEPsO*nit$>go*;=^B4f&;69szoQ~9&49hf8yh; zqb~|<ZwXl2RTnN^pipYNG9>Fn^pc>*BH2|s8&is2zbxU?U-`pOd-dtFpYoz_=5?#S z>ooS^Uw$%o=N$Fzv0~?c?`mh?ylMHj{Jp%-5|oRxx6ji_w>o!LWp!Hd%&D~(1^bE& zGOn(O*i^S<%cPh(-bI3$M-@$#w$3~EEpyV$O`q&+uRQd6*e`Q()wbfIIjnnLtz9#F zf_;nmW~rhl9#yqnFXwq&-}h))yOR5|u8W_2(#<nhPnt7Z&Mj)Dl;qTd8SddzmfiUh z@I>PEroNLOUx>&QS%mj|oBwy#;sDo|^KQS}FLN^E(FRqqBzJ+LlB>?CZ<}9;-3<QU zHF^JkkGJb&tZa28PuJx9ns?%1hI*KoZFXSKF_{?$?-usu=`D%+(o>;)eB#H8F{<-S zb!2r5f4waIzi&SCG~pZHiy2JW6;L2U5i)FeePce0ag2%*J52OIeS&*BO9wb1Ly|L$ hWH`Zy4iA15pL)y8vVW?EH!wFcc)I$ztaD0e0swJbt-t^P diff --git a/addons/skin.estuary/media/flags/audiocodec/avc.png b/addons/skin.estuary/media/flags/audiocodec/avc.png deleted file mode 100644 index 91aa179870e40c6b87fbc188cbe35c4d31158f49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1158 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>Gd_7$pLn>~)oqe(Nk%53~ zyqALN450-q8jM~ZUJXJ{oB>Qz7*riN3pp3IygAM!C*7~|ejdy1JHMZ@+<xdX{o-YX z{WaVR=7~RHzy=-oP+^6hrNJ{!UxeU`IWqcx?pK{QFJeH03hYH-q`-~@-A_lncwDyU z$|=j&!8f12ShDim#oc^cuD>Xnll@|jY4Ea6wP)r#%}#A~?kfx6sm_qNETUC$*{W#Y z-sGv9!#L}iE$e1<ep@<!xs}D#2d~R&E?HFF-@B6C`DwXm@><6yd^?|6DIQmje7*L| zXTRd-XV#_#wyx_b;konCx%)`@_jSBQHeAvC>-Jc69=bN8_g?e&t%vQ`t8}dDtA9T| zUTyK3PhLLGPku}_xTP{>o+(e;rS-R$1o|3YUA6J@GLt{dduM(xxiIC_M~%MiqODSG zmjVucKb&FtaL(7b-FhdtTywb;`FQu1CK-N?#oopr{!4rPHOLBgOZR1NEMDfbJ8acQ zl_HVN_3IW&TG$8P*ke%EW_EeGuv@O{$MyO4{tCw(=5J5gaAlQA=8~36Q>R@#xm*S$ zx6Z9EEW!S`^V6mNHBlP1xgu_F|0XwzSw;!Ft<P>+H2I-=SA@pg%-0%e>w9!tCq7<y z!|ZD5gtrHlwOrcOx-56<)WaDozBkIOT_mApZ7UM&6s57&?ATv}C_kC?vhzZ!^xJtC zn@L5bws9`pGRs%2k+pDQ*dITxqAfZ-w^UQ_rd(E;(#E0@mz{oJ*z%oMjcL`d^+&{4 zFPzmRBRlc%wf0vERi;O|mzqW1Fe#d^HDR%EQOl&(>28k}UI~6y_j1lAc@|4KmkQq? zo|?@8yE<yDgLR7bev>TPqSbTEgLCQi7nMD4B)8V~y{lV)so3({Qr^9Ze@X(@3O_W^ zYOPv+;-XN_Y%Ny%DPlKMmRG9ugsp#3wtVsSqABGQe@_hMb$+>ViPxVlJ@X6I+Q-8W zDJ*Msx1K0-Cgkb+Ut7YTNR@AEy%%ixW{>=@H@80j3AmLrIWcnAhB+rbGF+NI*|pV^ zdue8%vGHYpkN%0<PDM$_RBHl*V&mM1vWJ!>-->Q^Nb)WIYU*i~8EMRUbpABwK6#+5 zpYw{Naq@=2%e%7XKV8slCcZT7)D{iS>&M=<?45Ghjz=~>LA+B*cS_sgjGwD=za*Y8 z`|ez&;l9WB>2*o_J+}g8-P<eiV)3(`=Al}rCZ(s=cQ3Qvm?X{g`j?uo-Se4gQ#FtH zskMvQvE59z6jNi96Ush&f@k{jfIvpG|5tWK-03_RBxc2pmdh9*nG8rG;1?gh3cd-< oFt|&R9X)SC`3Nl#)|Z*~l|TICKmQD_0+uffp00i_>zopr0CQjXt^fc4 diff --git a/addons/skin.estuary/media/flags/audiocodec/cdda.png b/addons/skin.estuary/media/flags/audiocodec/cdda.png deleted file mode 100644 index 3f257dd5676669fe8ef3b6961c1432f2919a2400..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1223 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>G`aE46Ln>~)o%6PEv4McY zb@R*yv5ZEs9YP+QOITt#%Ybkp%kGvPEJ01dOy8CL_8fBRSRc`MKiO!f#fBR%Hb#lO z4C`gs@$;}M!xJpffe#h_SZiY$pPKaY@kXU$hPzw}>>zmO=w&Z|QSSXMZD$xuv>Rkl zA;Sqq6!<Y&Bq>F|_g0o+kj3}hs%={(Jgd)J_=`v{?lUhhygPS(tg`t%pUY=g8s7VU z?`(8%)p;v_k?Pe~Cw@J<KHI_ixUT!fIaSgt@-_*c{dIH8O&7~wi(csF%T7P8e|%Np z^2yxWXD968^V_Gtsd&%#W|?)nmaqAGYIDe={Y=4&GPVUp*@u6Uaoqke|9OV-@oR<C z^Q14&I2OUVt5!bFK=$sG+LD{atjk<>=5#*3tWu)=cw6afr+wQl6(n6%`ptN@N;l5X zsc(bEzf&g_j?d7|pE~y)_k6S8pWk0@Jey^H?PMYUdEuv<_FAr1eIl{(>NVH%dsJ6% zyfWc|b#ZQK_~R!px*tut>k=~M-a?tBrT=oiu$$S2I=p@P|9NN8UB;`^e%;>j^oQ`% z3D;YDWyGycx942(f7%o%^tH3>`TF&Lh21>bCwSc}eEEFYB>v?SirqD*FkStby=n@- zmixuRhtp5UP8aOS`C@xVerZUu+Iyu>>%M4ANapzUNlwge`nye^S{J2comi7qD}GXK z&UePAC-PL>cLhvwdOG9o8rfUU*JR@N=-*mdGcWO_M(3-LvR7Z{ZdP*alh3oN-)rFh z?1O8j_~PsT{T`i_?YF)8Jo%$W>#Lf+u<(+O|D8`aKJ`c!Tx#8=XTEy1dqVihE}6#f z4?oow8g<Lu4U9RO@#6<aQH56Tmv_eQFGC(?pZ9t@UE#Qa<)ovRbFY1L07mZJCrcmu zi>JPE&8#UpV{9PWCbz==oLs6$v$DGY`_zWht)JbNowF*RQZnIIWsmF2;F9+*oLA** z25B;-rS~l@v9mrS>iWE?ey;oatN)I_xTSRHr_4^%XsyT$JNI{UC!5-y^7JiyWA`uA zH!I1`J>YoI$|rkbUs$F}m!$XiUNSY(n!8xmv!yvP&`;QB?N_b4<#A>6LQ~o9eO_r= z|GG5g-$z%@^Bn5WO){5pRy}XK#ohjC-piIWht1R0-Z-gYSrZuab@tbW%5R!?y*Q@s ziSTEszS;fL?`o@rWu&HUj=;-bI~M<uwfS>ZeyK#jt1v<P3vPR4jQAsXZdUA?S{Iid z!S}K&Xm!t}|Caaj!lvB&8g=u?&Ua_i?2Fdsyp3%$QT4JbcsPkOPO4QVJazudGYZ-6 zx<ZGxZ5LeTa+5#i(~Ocecas%M4c@w5oqgc%tv5|sJ(__%t6UGc#eMzp;+cCkSHAWA zewh!Ci$2`9J1G12A@2lc7-X<ShYu8A#Pi)On0P`4o@vb!jvnTifT0qo6J`v8mh1b^ Y)OWw=&$a%0zygZF)78&qol`;+0H3Nn=Kufz diff --git a/addons/skin.estuary/media/flags/audiocodec/dca.png b/addons/skin.estuary/media/flags/audiocodec/dca.png deleted file mode 100644 index 1dc52ec67f103240afb8c1bcc020aa91abd90317..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 981 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`=Xkm}hE&{oJNIGfA_oyS z`)^AagP5irn6^MQgL4UshNIU3p)D-Bj$RXlk~rV=e|^z&AnDPJ)CY0@G!7NCyspk~ zQ{>EI-150QnBfE?CTx&Fg$7~wqOKclEU98~;Zs=4Py~Vv;(Mpg(-L!Y@_oW^iA#YU z6++Y?NS>x$>5<x@S2pHf+Ff3(DYaH-YE<-`DO!@PvTM^1xG=}dJ&sV<{jK)(y5<_O zXJ1zqpT4rX`1+@xA(y<?IJqC~i}v06zQ^lQl=|6PyUcuLp$7|`l^-0Rrr^GIi}AH7 zr=-@lc&%Swt|dJ`+$|_7`nCBaCGCYS{{z~$d|JG4qixUf@<#iYnfg@)w^#hzlUKg# z`8C1(*PhzVYS(?FAJ;BiI=M`^b$WQZ?wT1>v&$;~JLaeUsEK&Fct_l|=TCN=3@{Bk z@6_1jHp{uM@mT+z6<XK)w~I?Qy_I<WA-;c=s%lMqpLF_@4|6vQT0be7kku#iZ?kdT z<MU!mr<%?aRBHdf@o_>=WsCo*LbISVg>x<@N15OAYCriPAylFIt$UF@tK~bdQ}0E0 ze4pyRFzV<Gk(twErUqZ!G;M1q*VU+MyBs@D%X56n?NheDQuJKur@Q^z#KY2-F%w>w zKi9}uwD|qU<fx(@^1HOQUhs*`&fd7~#6$jv-ithz#=M`UnSI4%<Nc?a6Av#{+PwSs zW+%S-4(nZ8-&tfNPF=b2{Nm-8Cne_{>HU=Iesb+k<r5D*?{xIW|IohK^SH&=lh647 z{xZH}l9n~znw^0*`@;H8Tv+1N_wal08Nt(MI&(79Z4Lh~{L(7pcg0}7{!Pmc>CSIa zuQ!**b^NSgD&pC9%|bG33g5Gq`)LdFq`wC!<*pZ8`l-6^<&LKjT345s{`At7nbQ4C z=8DVKOWsPIQ(vWg=`=d;eYw*{`}g#>%x`T!1q4UDm$#5Qf2zuP>C~`uyH<7ITU2&+ z2A@lmq>A)flj1r5i&;-u9g8&H+y5|g+Je0ErpLsX|FdPTmxw?8d*RjV6)6*HGw)uw zU1_rEV)5L+)0yMc*z3P>s>Kx}(j}aQNWMIM|LRu;7EkJsf#;3?jSc5G1S}aa;RI%M csCHbwTzcAZ{>>*Y0kbQEr>mdKI;Vst03$54E&u=k diff --git a/addons/skin.estuary/media/flags/audiocodec/dolbydigital.png b/addons/skin.estuary/media/flags/audiocodec/dolbydigital.png deleted file mode 100644 index d01a87739e282a8c475951ff62dd843110a8975b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1383 zcmZXUeK->c9LGmF>1AH>PBX0$<!02eEK**!hRIttN@XsZZciI-PHy6^+Rod|>)1+& zHB+0nB2r|FDBWTgQ}S-CLR<=`KYH%!p3n3Ae&65oe81m6e{^3T&n+8`Hv#~FEohXR z9{`{NT+`GI>(?f)M(P0osC|!ia}A)rm?;`6xQ5h95+_ut;9nhX(YdwW=7NoRE*KiA zfltl`Ls1$;jq@%Gjj<0dYU%0TU6hB_i_YzU*|U-y_-ZX(Fdr4L`u_otzow;ew*Ny2 zX!r7BJ`<57QkAHDh7~M`i4nfFkP?7bE58n`UEj2dTcz7vOyD{%k!P$u(t91RoN61; z&4{;MqI5b<<!7b8vyYvRL(Y{jRcH?!pX1(MzP>V_T62@tLiNJ-kQ9=oLa(N{`E(E; zJBt&iT%m~-{+Yv=%u-5C+0j%!xM%BVh2qJ++Lft_<8$bHO>nl#Nd9wW3_0S5$QMf( zbNAue(TtkLtd!H0-o92^+B^a@!V^h+^d{Xo_whdEb_%a~HAjMWx68v|qL4RARyO~- zBA-KMJwAzNwEe|^ZffYRvXV>3#D-x<DbI*ww%c&g#HKn8su?HjSvf(eou{|eaRxIE zFElG_buK<~*4VR%^RUbXG0iQm8?>34wy;xFZy^88_DbwpEt1wS^#)Yk-#42-y+xLt zgN0bq6qf_W8w?SjSi*PQ$JII7uUjmh<ov#r19_+0#ww4_x2(BF+Hon-rWOWNbnopN z)QJj4oXRjoE3$&}O6IPfzDMDf3lIGw>;>Y&`6#Jz$+TgVAV^(U3j63*HZARNqkRWq zGID3;0KhYTdA%a|k&QE|xxzcOCrq=XS@+8)124PL{iu0LNwGz<uag?nKl@lG*VVte z&Q$8);Z2$By4c5IrtN#3VnQ0C%VNE!oM7&+^kuQ7sHk2T(=;$zIh)BxH7m^{rdWdK z?MCDQuxF)6A|T7e&~<KX1{1rt%S^y|iho3?4E0ybmfZia-w1IdcUMi*jZ3Mzi#q-7 z*6IrhiPTaX!s0PsJ6^z^cwhPs%K5&?)$HRSX>@*cy;5=-pPU9w1B(;MxA*Wo3@O_a z5Fy^xwD)`1K{00{pm7Yc_rsn|;lx3IpKLARbkic0^2oTr*6fdoKbepz$@U{8h#can z43_*9aK8eXpl>#{aGuhdA(}lfw)0K!&u!RA7cwr`hL07b)F$5ni)Y^V*H#LYvT77w zU$Sga$kDOW2n{tDMvSiY1;&@e_$X(`m>n0w2N5r0?LM)+22uE`>drLs?!b#iOM~uK ztRJn(6OCYX{zu0OqJ7i3Qclar=u?N<!bBQKwy@k~!t)EC=!Lna5A|7v@C)mE`cddh zAiD9S5vDz+C~fScL_1{BK{MUim1Jy`0iv4*-h+P%@-Lm0M=+MfvQf!MrF199q|*S0 z)9g0oOb$rUJfR7aBy*Ud6o7~2=En|4@3*t`>$p~i*%?tRI%sWaRhrqlddHkq!NkU` zd)b6CQLAD{Dx8=%55>`FkUH+f!bwo7Wixz<GTygIWSOWpWMgJqMS$|V!^c!KNA&Gz zWCn}??{I1-^<*juP?~BzpIi{nJrLFzs*gRpSyRNWGM;@6^DFd$E9G>QiFec>1{oJ{ z9TNT<F&`Pb2&HnvcB$WO7@F_4U!B?Nb$c8z@*fTWzlMrwxZ->hfhGXIn34=^!{Mrg lZ+xFqUmXALBwG3^AY<1==!1B<VC{hd(C$8NRR>Qb{{#3WhDHDY diff --git a/addons/skin.estuary/media/flags/audiocodec/dts.png b/addons/skin.estuary/media/flags/audiocodec/dts.png deleted file mode 100644 index 1dc52ec67f103240afb8c1bcc020aa91abd90317..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 981 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`=Xkm}hE&{oJNIGfA_oyS z`)^AagP5irn6^MQgL4UshNIU3p)D-Bj$RXlk~rV=e|^z&AnDPJ)CY0@G!7NCyspk~ zQ{>EI-150QnBfE?CTx&Fg$7~wqOKclEU98~;Zs=4Py~Vv;(Mpg(-L!Y@_oW^iA#YU z6++Y?NS>x$>5<x@S2pHf+Ff3(DYaH-YE<-`DO!@PvTM^1xG=}dJ&sV<{jK)(y5<_O zXJ1zqpT4rX`1+@xA(y<?IJqC~i}v06zQ^lQl=|6PyUcuLp$7|`l^-0Rrr^GIi}AH7 zr=-@lc&%Swt|dJ`+$|_7`nCBaCGCYS{{z~$d|JG4qixUf@<#iYnfg@)w^#hzlUKg# z`8C1(*PhzVYS(?FAJ;BiI=M`^b$WQZ?wT1>v&$;~JLaeUsEK&Fct_l|=TCN=3@{Bk z@6_1jHp{uM@mT+z6<XK)w~I?Qy_I<WA-;c=s%lMqpLF_@4|6vQT0be7kku#iZ?kdT z<MU!mr<%?aRBHdf@o_>=WsCo*LbISVg>x<@N15OAYCriPAylFIt$UF@tK~bdQ}0E0 ze4pyRFzV<Gk(twErUqZ!G;M1q*VU+MyBs@D%X56n?NheDQuJKur@Q^z#KY2-F%w>w zKi9}uwD|qU<fx(@^1HOQUhs*`&fd7~#6$jv-ithz#=M`UnSI4%<Nc?a6Av#{+PwSs zW+%S-4(nZ8-&tfNPF=b2{Nm-8Cne_{>HU=Iesb+k<r5D*?{xIW|IohK^SH&=lh647 z{xZH}l9n~znw^0*`@;H8Tv+1N_wal08Nt(MI&(79Z4Lh~{L(7pcg0}7{!Pmc>CSIa zuQ!**b^NSgD&pC9%|bG33g5Gq`)LdFq`wC!<*pZ8`l-6^<&LKjT345s{`At7nbQ4C z=8DVKOWsPIQ(vWg=`=d;eYw*{`}g#>%x`T!1q4UDm$#5Qf2zuP>C~`uyH<7ITU2&+ z2A@lmq>A)flj1r5i&;-u9g8&H+y5|g+Je0ErpLsX|FdPTmxw?8d*RjV6)6*HGw)uw zU1_rEV)5L+)0yMc*z3P>s>Kx}(j}aQNWMIM|LRu;7EkJsf#;3?jSc5G1S}aa;RI%M csCHbwTzcAZ{>>*Y0kbQEr>mdKI;Vst03$54E&u=k diff --git a/addons/skin.estuary/media/flags/audiocodec/dtshd_hra.png b/addons/skin.estuary/media/flags/audiocodec/dtshd_hra.png deleted file mode 100644 index 53ffb9002ba685ed661b48f6f2bef529cf6866f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1549 zcmZwHX*d)J9LMod%8V<wl$M&D<A{wMTOwna+%ed3<jQR|B5539U2BN2Es`Vbkh5VN znQ>+sxf$aak{QuB8rL{VYoFbxeYV{<|L6NW|L6I={3KbL!}z#QaRUGVJ`-a@YXE?Q z|FFY=oQD^S8#Mv|j*Xfa>f3}(uH>y`6~RTIX{|aO&cHjsmjV|!c^~j|7(^ciNdH>2 zfHWuQ!LC~BOh(7M7aaTk<Ig=I1tPgfd|=j#3jM_V6Pg@_l5r>jvc5dmzr#Qq$dJyA zwdDUA>s4r97e>eMY)@)>Yz^}4Ur&;(5hPwI`z1&N%;kzV_;!Y@|Lu%{Hf}T?76%CJ zB@k|}Er5L_yRQ}aTS-UjweNDqD-XOAZo$JT%&B9S2?M6%*28Sy5O76AA0d+V)KEJH zDPD;@z6D^1FGmr6a^UsO3Y<01cT!tKhdaJ6X%^)IscQ*KX|y$IC~^pLH&$Tg1e3*Q zKdV;N{d}1{f$k4m=25{!ugS-FXi1A!(}Aa9wT3&U>6d0yp=BZp)lwW(IH>{Cr>9qE zD;Y+{>$<D<bLH@FG@T~+*CU6x$Zr)s?#IthoWa*KD}aqv>q+iMSfe!fpm&;|pB(mz zl9`uBs~uiwo(IOdJKQK0KiAoOxuV8y_4ku5cp}k``lO3mEac@%)aneZ4nWKASKg6& z{kbmn(frV~XEAfaJ%RQ{KTO`&>qE$@&5RAdw2@L>viBy-|Ezs;Q%r|d7k`U}L<HS$ zZs2@LuV}lQOcu_lmsK!%vsK{|c`ivyeyaaIOj}V<x0Ek!^*G}KC;$XX#Yf$s*+Vjq z8bcqYY!Ph~k5d-tHM}`t7PMP?!uNW5ithd*$wGn|4_iYUY8PFT&_x%YO^Ig$;Drh7 zRMb@kA53fBuVtv=do!ky)*g-|jWu0^d|Y~{ZZM6TneOhtq8{k+9mx0mY0ECL;(E9) zWxZ>^M?!~TOSbAqD2K((b7~ZskQdSm-)b!%ganD*29Jj75HeCUpWff_aOfplnVr;* zwx7ZQ1PTUCS5z&ch1G8_U{S28jG={Kzfvk|J{S^!lmov<99)r9>l|{Fy_uF5wFp;R zl$V>&Go$Q2F<C%d5pNAHRdr%eJL40s3VWZ)`$#02^ea2j?EJ|4&7W4&5+g+o0!A%I z`fygfK~QoI(T*OZ<4;pXkv<>k>yUC$;`tCdK0u_(-M{BN6w%`6&QB5gzIkY0Y5O+_ ziUbsxXZl2nK~A<f3Y@cfh3M}MX4gYI7pS-5;Y)S2<Pr7fPu8s+1riI3jxs4-o_>2e zcuCd@+JZeUM6c{tz5WB+MQr$GcB)S?s`1@-B(MEUdxsO-55dlx(>J=<8i8U$%Sf_& z1l&ismsRUM9faHJTheyJ#GU)Gak8HNS(Z-2j?+~nw9zY%BUZ!7Ia<=fG7Z<k;o-^f zmF}jV?U7fr-A0hW9_aQp7-Vmtxyql<rvTTn;Z65_{-D4p;uJa*a;B0B??xq$4vtYY z^MiBil|@pZZ*Wh`8=ZT`0<aB(go(UPM5IfG`5<;j3*2Fw+crUtV^8iN5Fy7V%}dlt zY9k`kLZ}_`z}<BaB~A}fn13o*UelbdTjMylH_Xi?6BV>NJH^bil!B(<@QMiu06WGy zgWU0HuKBg^Rm@vTx)#t<o+o(xaZdTh>n}Fj;m?>$ZS89@prw!4QUVCO<<=Y!K+NLf zHDM4D3gKqup0OFjW7z}OB{QRSR4%SzOjcayitLDQI&D#PDl!@l3bZ>0;awaq!t5_< zp+MxT-LnVC-D(F#nT_O3iFx-jv9a+q1cH+XQ)ad0-(b1fFRSkc$j`_u0gvZ~eA(RW zwkM%8dA29Hy=Jhj<=L&U+KR2DMCuW`lbWN~0p7yHQ~Mtm{}%6C(AJr1)tnU*#wn6< tU$s<fON|xmApJ+xKb`&GnNK<g0C8A*h@Np`$szLq6R5dimBG!JKL9@O@S*?! diff --git a/addons/skin.estuary/media/flags/audiocodec/dtshd_ma.png b/addons/skin.estuary/media/flags/audiocodec/dtshd_ma.png deleted file mode 100644 index f20256e591d444055e747501352b634ca8bcec2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1524 zcmZY9dpOg39Ki9PI0;3$h3s@0MM$U^V`40KbH;KxIXJoHI=9J|OGz%njD<FO+-7be zw_=oXj0lrygv!dpMn$#9Wp$kAoX&YVJ+J5U{e1rT{{2pOc0}xvRFMP#V2{l?xC;P? zNN?ZRU1HlWz2%uT0Q}Tx13!zxPAyZGW6F*9n)kn}5NQ*0*>PukG?R5iZUX8rW%>WY z!q}VoJCfnKJ-rOGgltk$*4RaVwZ|t^L>kv*Xh5#$j{wN`UeZ?mg*sAp3;iifkqQNK z>Azsp$1=(@QV&{Kasig)A@T14nD1f7jV@Hm%L-PgCW3d}OO9Ga@_#5IG}UC@6WHaS zYbQ#Y{5<hpeU|*j5>J8{cD*Sa!<&saD47a89GcDWR3!Bv-!jm_{QV|v?0(#CmJE1v zy@+Ldq?NBOF&lPxI=Qwbzr2DprHfEahtVU4K0Nl@QVfs#(naMPzcSMk<2<2-Wa&sZ z^IPQTatxy7!n<Py`L^={t$pc)YqBPyTPK^i&8tf*dLT+n9&d$4Q%KmW>)`{7Yg0-H z0~rP=6#p`BK)V(~ijs@xk2{812B|lzJqRobs?3X<pQ<&P9F7}Mzu(=-Op9E0UA-b) z7jT`@p|;aF6mHNo(eNsBteZSDSr*PUa?*z4?f7fet1J-YGnpAli8||HQ`yj`Z=U(# zYow(|jD6q{M3Z|~#sPZGBXW+GZRsW1I(^9jjU-iXuP_|nG6<{!aU#-pc$AVto?^UE z{9RPP@mruZbS1X|hf^cui`DXaBK8kSIhBS#8@P3aO3C8fn5H}*grEqn8D8;BVr$;3 zw@9nSy91F(aK`VLxDpiladVH&$5Ef2Xh*zjx%F`qNIKJIkvQxwt#`DgD(>ymj+FLh z#=};$CN+vD&p}7VW7^2ozR}~FRip&P$`KueYUBKuB{w^8jI)*7QekImKz&)+2sGHA zV;0hk_klPjyLx(v#_169MYud^!%T7(U3P+|G)(Yq*);3)MsDCRUMben#KD^W(%LPf zlf6neyUx^1Fg?O4T}y<z0Dpf!(32sLZ0FMtdl-iG7>+nHf^K{VRZlCoMN&^mTc?;- zhv$x<vpwy-KwQh&i$S5fdSqUOslhNJK)(*P&`0fPTB|XPzIShN_iCs*Z-{#%Oy0`T zWk5H+JxeL?&}3PI#voa3e%`tri#JCa=P-Saug>0w9XRUD<ILE*fEj$~q%YS^;Y(W& za(bpx1D{|F$t&>*Jr~<dvWU5GWUSS|k@2S1i9afFk!Jk9ou)#(Tq<KdIi%&_@FqQs z(;n_tD*B@N;N=N0JL+>HSP(pUe}RqPG-wC2nPCgDPr82%nYrx#Vri$&%FglO2xE|e zplG&S1K-w?=gC_`E1$|lQBO2LX`l3Y4>}y^ZLlG&`ifaziA6=hGp%HTjTY<N-PpPl z%XV7cm)LC4($12IAziAQuU|Jtxnj8Gv|rGdWkrb)&c@<vWy39Y-C_|7XGR2{adU6D zT4y#Q{<8^-$68AwS{7qYZg9Y9iEtxvZCj9VOB=(j511<hFz_Wfn<^C4NR{8lz$YX7 z@7X}#jLua><1i4PtWTIAb&|zXYYPYyk?T7~F$}EeIGP8Sgs9kIACar}uji&}b8p5C z6cRKZ8JcbilUiQ;FHe+tbKe(13ODNb&rL&#^VPmpV#N|0k!z~Q*7HYhqZ@1MgdZuX z9qJqO^QEsgg<*3r2W~Y2T5lGn;d%bxzTjftUeW7^!TqsZa1OGu$OQqL3y>X(5jI!{ z2s4An3pih)+6Bt(AJ534Dfq<Oj3xVC{0!LyOHIqfy;!IcmNlixwWdCB>Khx`YIhoy z`d_yGiP*QcLB#~Njn_$VF7S5)w<`!p?F5^1fggSQ@!u{4eHF=>(T3fE6}D~H9I&x+ Kgg03Hmhvar)73cu diff --git a/addons/skin.estuary/media/flags/audiocodec/dtsma.png b/addons/skin.estuary/media/flags/audiocodec/dtsma.png deleted file mode 100644 index f20256e591d444055e747501352b634ca8bcec2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1524 zcmZY9dpOg39Ki9PI0;3$h3s@0MM$U^V`40KbH;KxIXJoHI=9J|OGz%njD<FO+-7be zw_=oXj0lrygv!dpMn$#9Wp$kAoX&YVJ+J5U{e1rT{{2pOc0}xvRFMP#V2{l?xC;P? zNN?ZRU1HlWz2%uT0Q}Tx13!zxPAyZGW6F*9n)kn}5NQ*0*>PukG?R5iZUX8rW%>WY z!q}VoJCfnKJ-rOGgltk$*4RaVwZ|t^L>kv*Xh5#$j{wN`UeZ?mg*sAp3;iifkqQNK z>Azsp$1=(@QV&{Kasig)A@T14nD1f7jV@Hm%L-PgCW3d}OO9Ga@_#5IG}UC@6WHaS zYbQ#Y{5<hpeU|*j5>J8{cD*Sa!<&saD47a89GcDWR3!Bv-!jm_{QV|v?0(#CmJE1v zy@+Ldq?NBOF&lPxI=Qwbzr2DprHfEahtVU4K0Nl@QVfs#(naMPzcSMk<2<2-Wa&sZ z^IPQTatxy7!n<Py`L^={t$pc)YqBPyTPK^i&8tf*dLT+n9&d$4Q%KmW>)`{7Yg0-H z0~rP=6#p`BK)V(~ijs@xk2{812B|lzJqRobs?3X<pQ<&P9F7}Mzu(=-Op9E0UA-b) z7jT`@p|;aF6mHNo(eNsBteZSDSr*PUa?*z4?f7fet1J-YGnpAli8||HQ`yj`Z=U(# zYow(|jD6q{M3Z|~#sPZGBXW+GZRsW1I(^9jjU-iXuP_|nG6<{!aU#-pc$AVto?^UE z{9RPP@mruZbS1X|hf^cui`DXaBK8kSIhBS#8@P3aO3C8fn5H}*grEqn8D8;BVr$;3 zw@9nSy91F(aK`VLxDpiladVH&$5Ef2Xh*zjx%F`qNIKJIkvQxwt#`DgD(>ymj+FLh z#=};$CN+vD&p}7VW7^2ozR}~FRip&P$`KueYUBKuB{w^8jI)*7QekImKz&)+2sGHA zV;0hk_klPjyLx(v#_169MYud^!%T7(U3P+|G)(Yq*);3)MsDCRUMben#KD^W(%LPf zlf6neyUx^1Fg?O4T}y<z0Dpf!(32sLZ0FMtdl-iG7>+nHf^K{VRZlCoMN&^mTc?;- zhv$x<vpwy-KwQh&i$S5fdSqUOslhNJK)(*P&`0fPTB|XPzIShN_iCs*Z-{#%Oy0`T zWk5H+JxeL?&}3PI#voa3e%`tri#JCa=P-Saug>0w9XRUD<ILE*fEj$~q%YS^;Y(W& za(bpx1D{|F$t&>*Jr~<dvWU5GWUSS|k@2S1i9afFk!Jk9ou)#(Tq<KdIi%&_@FqQs z(;n_tD*B@N;N=N0JL+>HSP(pUe}RqPG-wC2nPCgDPr82%nYrx#Vri$&%FglO2xE|e zplG&S1K-w?=gC_`E1$|lQBO2LX`l3Y4>}y^ZLlG&`ifaziA6=hGp%HTjTY<N-PpPl z%XV7cm)LC4($12IAziAQuU|Jtxnj8Gv|rGdWkrb)&c@<vWy39Y-C_|7XGR2{adU6D zT4y#Q{<8^-$68AwS{7qYZg9Y9iEtxvZCj9VOB=(j511<hFz_Wfn<^C4NR{8lz$YX7 z@7X}#jLua><1i4PtWTIAb&|zXYYPYyk?T7~F$}EeIGP8Sgs9kIACar}uji&}b8p5C z6cRKZ8JcbilUiQ;FHe+tbKe(13ODNb&rL&#^VPmpV#N|0k!z~Q*7HYhqZ@1MgdZuX z9qJqO^QEsgg<*3r2W~Y2T5lGn;d%bxzTjftUeW7^!TqsZa1OGu$OQqL3y>X(5jI!{ z2s4An3pih)+6Bt(AJ534Dfq<Oj3xVC{0!LyOHIqfy;!IcmNlixwWdCB>Khx`YIhoy z`d_yGiP*QcLB#~Njn_$VF7S5)w<`!p?F5^1fggSQ@!u{4eHF=>(T3fE6}D~H9I&x+ Kgg03Hmhvar)73cu diff --git a/addons/skin.estuary/media/flags/audiocodec/eac3.png b/addons/skin.estuary/media/flags/audiocodec/eac3.png deleted file mode 100644 index d01a87739e282a8c475951ff62dd843110a8975b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1383 zcmZXUeK->c9LGmF>1AH>PBX0$<!02eEK**!hRIttN@XsZZciI-PHy6^+Rod|>)1+& zHB+0nB2r|FDBWTgQ}S-CLR<=`KYH%!p3n3Ae&65oe81m6e{^3T&n+8`Hv#~FEohXR z9{`{NT+`GI>(?f)M(P0osC|!ia}A)rm?;`6xQ5h95+_ut;9nhX(YdwW=7NoRE*KiA zfltl`Ls1$;jq@%Gjj<0dYU%0TU6hB_i_YzU*|U-y_-ZX(Fdr4L`u_otzow;ew*Ny2 zX!r7BJ`<57QkAHDh7~M`i4nfFkP?7bE58n`UEj2dTcz7vOyD{%k!P$u(t91RoN61; z&4{;MqI5b<<!7b8vyYvRL(Y{jRcH?!pX1(MzP>V_T62@tLiNJ-kQ9=oLa(N{`E(E; zJBt&iT%m~-{+Yv=%u-5C+0j%!xM%BVh2qJ++Lft_<8$bHO>nl#Nd9wW3_0S5$QMf( zbNAue(TtkLtd!H0-o92^+B^a@!V^h+^d{Xo_whdEb_%a~HAjMWx68v|qL4RARyO~- zBA-KMJwAzNwEe|^ZffYRvXV>3#D-x<DbI*ww%c&g#HKn8su?HjSvf(eou{|eaRxIE zFElG_buK<~*4VR%^RUbXG0iQm8?>34wy;xFZy^88_DbwpEt1wS^#)Yk-#42-y+xLt zgN0bq6qf_W8w?SjSi*PQ$JII7uUjmh<ov#r19_+0#ww4_x2(BF+Hon-rWOWNbnopN z)QJj4oXRjoE3$&}O6IPfzDMDf3lIGw>;>Y&`6#Jz$+TgVAV^(U3j63*HZARNqkRWq zGID3;0KhYTdA%a|k&QE|xxzcOCrq=XS@+8)124PL{iu0LNwGz<uag?nKl@lG*VVte z&Q$8);Z2$By4c5IrtN#3VnQ0C%VNE!oM7&+^kuQ7sHk2T(=;$zIh)BxH7m^{rdWdK z?MCDQuxF)6A|T7e&~<KX1{1rt%S^y|iho3?4E0ybmfZia-w1IdcUMi*jZ3Mzi#q-7 z*6IrhiPTaX!s0PsJ6^z^cwhPs%K5&?)$HRSX>@*cy;5=-pPU9w1B(;MxA*Wo3@O_a z5Fy^xwD)`1K{00{pm7Yc_rsn|;lx3IpKLARbkic0^2oTr*6fdoKbepz$@U{8h#can z43_*9aK8eXpl>#{aGuhdA(}lfw)0K!&u!RA7cwr`hL07b)F$5ni)Y^V*H#LYvT77w zU$Sga$kDOW2n{tDMvSiY1;&@e_$X(`m>n0w2N5r0?LM)+22uE`>drLs?!b#iOM~uK ztRJn(6OCYX{zu0OqJ7i3Qclar=u?N<!bBQKwy@k~!t)EC=!Lna5A|7v@C)mE`cddh zAiD9S5vDz+C~fScL_1{BK{MUim1Jy`0iv4*-h+P%@-Lm0M=+MfvQf!MrF199q|*S0 z)9g0oOb$rUJfR7aBy*Ud6o7~2=En|4@3*t`>$p~i*%?tRI%sWaRhrqlddHkq!NkU` zd)b6CQLAD{Dx8=%55>`FkUH+f!bwo7Wixz<GTygIWSOWpWMgJqMS$|V!^c!KNA&Gz zWCn}??{I1-^<*juP?~BzpIi{nJrLFzs*gRpSyRNWGM;@6^DFd$E9G>QiFec>1{oJ{ z9TNT<F&`Pb2&HnvcB$WO7@F_4U!B?Nb$c8z@*fTWzlMrwxZ->hfhGXIn34=^!{Mrg lZ+xFqUmXALBwG3^AY<1==!1B<VC{hd(C$8NRR>Qb{{#3WhDHDY diff --git a/addons/skin.estuary/media/flags/audiocodec/flac.png b/addons/skin.estuary/media/flags/audiocodec/flac.png deleted file mode 100644 index f173541ebd9c15b687601ef19b9e0f1feb38333d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1121 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+<+8QlDE4H!|mNY8hJn-XMsm# zF$061G6*wPEVVBK3bL1Y`ns||;^Gm}Rp&GemSA9D{^aT67*cWT?d-ROiwy)EuA65% z=)UkO@H(JcpgKXQfO7}S?v?;1y@PrOr!c5akbiC=B<AEdhvnh-8M7_a?QA|Qid}nT zkuZaK{SMX!8BEA<f)N!KKRx+#zm86=jo23%hX}?e3_#eR;ijz|sdJ{KZyqy;Ho76` zMtp28X^ehqqxS!b>1X}9ic@2>Uhkf(GTlVWdd~M7E@~TFgDm?`+LxZ4T-bKm*ZtYN z{2Mi&Zr6N|E!`p<*5=_Ny+6~lsk+CF%YXMIC%;WS55;U;zhC&a>HC&#<-7gt8B*@O z-WSZms-VkWIZ?LgPJG{4{uo!!GSzOW(ERGsDJT41ZI0QsJzsW-wB>Q$i-!NNCD%kA zz7g4V@89~^q^;Vo*IfNl%=e@qpS@k?-x8CTOXsdE*1sG2yv)~0D%<GSM1ytAlP|5E z`g6^al6Rq}z5bnyns@$So0WUXl4FPONc0!(@JdV<(YCBQ7Z>&1rEf(*8_Vv``^#BF z4%|q49NNs4`Q&e#(Z5qixdd(=xL{)FbSh2HoXIjq@lo=Pm~M~U7ZdxwNk5g%);(cp zIL*x{>RVlx&Roxxn_d|-$$a*AyY0O#@f+L9V2vjg9VsT8CtZAfex<VJ)nhM~x%S;( zEV1V0lk%75S3(0e+H1exQf<3@zerWz!UaWJ*p|2}$E5TZ8BaKTPF5&=|3!bV>HUS) z&rDMPf4wwWTk`LO#XDN&Y$;!R%`&f3UhQ7b+MO~ROSk0pW}2pM-6N$dbn)1c+q_5h zQ)~9^3%{8V6eX=|%w>5(@gA4kjeThoJSQaAy_hYReM!4!Yq88}o5a?|`VrNubRKzl zMQiE@Wq7h*E1EFDZT1ASW1sdt+}jp({z#PJG7pdESMygK<Z9o2D^cI^p0w_*C{ed_ zhKm<m>N{a*!n%E1RGe+=w##?B{+{@#d~(I!Z>Nu+^YGegI;VDHinWFC9Us2ui@RRF zZj||Jm!LCKhn;=P(xu0Pv{tH^uUfU!B<#S<I@7O;LP7Gks<giqT+Zh1eazab_F<PL zXC}{#hl(izuV(MNfBJsIb(!y<WR7|TSM`0)cKgpM*|O^5_iH87`M)W+zPH`M`CVqC z$iZvpf=XW)+C?SL`!2DiZ}rrxCwRPcrEhk`ES)oPt#^FMyLU%!H19BucAIBx$!;)( z6`U(TIRg$I_+W5`*kkR3R(V3U><mQ^ctC$cf&tSPVshUJM*ic{34bPNb8LJ27+4@M Nc)I$ztaD0e0stOj@FoBN diff --git a/addons/skin.estuary/media/flags/audiocodec/mp1.png b/addons/skin.estuary/media/flags/audiocodec/mp1.png deleted file mode 100644 index d3065f1b95d893301425d818fb1c0e5090258b62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 936 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`^F3W0Ln>~)o%^<Qk%Nds zyRF%QX%5^uoXc2(o2E6WI&cO!1vO0(@V+3lghi};@t%Vh89&b9IlT8zAWP?&ueCf+ zB(88BIHT{!sKAa18H!M0!+M>b?427+>R4R(6xKrI4yf!B^%mux7a(leu!ZRaBPwi= zL521nwXt?=Z>M+LF5dnAW~`r9=Cryc*Y{33c{|ZYTIlH$rO2=MvRA$px2^Cuu9S`H zo6x;h*!_I$`@(OEYnSZZF7rD!Zgq2%T}AEnoQHzk{C3V)cswsA?|yo?aq5(_MT%FZ za7i=XJF@zh_QrJAcJ|Dr>-?`3)W`A7-TQ9&)I6t%%-?c6?%l7}&sK4XDs0-|ZV`W9 zWWnE`A@x_{x<jlswg(<w9A>tW@9&PP#X;{M-F)TN>!JCiz`RUF`RT?&ky76~GV9l- z>^rh~Z->E&hdlk4+<r{5Y!B)d6E`iJ`z2Vh-8v+x>GeK~g^9nP_b*we6qb~`Y^mIn zf`&7<R=FjqEvc9&lPh(z#{Sj{UDNjJA{pJwiJgH<)1L*WuwGg=>B0RVjaC`G<9#<S zTsn2ESevcXVq+j*|3}9@rxK3st?7D~4$a8lKF53Igk-B6*EtuyPAK&-TQ%9kH82{e zFL+^&QN6UaQ}W(Inb7>2j6!BhH7RSSz-jBY?)aFqbmEH1X6m!fDzDVw*V_|&ZAqJV z*4w9>=05y%`@?U`Uon2S3m=|6I&bN#EnAAtnN*)NJQp5eWGek<;$or0+p;co`sgqH z$k&tjX7-h@(O1lj6CPenKk2f@LrV76p%UXA2d<`Co%80Be`@i1qIA@aqyomsbDEWB z6xaT_zMy7CUc#}W8C>Uon#?<q=*uyG?v6l%6DJB2Z&!vHo;Y!_Ol)R_&mD(8zo~(B z%cqpH%?R6ed;T874K94<9$UDD-v05@y7ue@qYK}HXEXc`TeUb{QTu+c_=dgO+v-22 zljn4B_w0Y768_0J?q8B*`X#P_V$>uH<{?t{j@G|^8)i**dBWhp2Z28rnPv$ppe1xP fT`>J;`u5jd%)bA0*ZB*;9LV75>gTe~DWM4fZRL<a diff --git a/addons/skin.estuary/media/flags/audiocodec/mp2.png b/addons/skin.estuary/media/flags/audiocodec/mp2.png deleted file mode 100644 index ed4e21eefedca95b960d01865e93230d591ee2d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1105 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GBs^UlLn>~)z56!um4QtA zLwV%~9H}f4jBW=6Q$W-N!4eiLrXB_58-h0SJ97SKI(t@CXg;g7`x!R%@@x~%2a<m} zPTXgZVc}z9Z)`r`_&}jRz=8wJ6@=1I;eaR$fyoP4;L^6TeT@I}>bJ?mMW6~Bm}MR? zu%l}J@+htLmFc4$<$I^*_m|jIufEHbtCAbFzj)csWwWNem){z?j@d%4dUM=Y$%-o> z)}P}n`tEOwTKVBnMD{ydrR$5n1s^E9KD+da#)lPicczygR1~cWjr)Bg!kTw+AH(sx zdDFIBFsr(gad_hF%a&I*eXC77ck<kp@{F9)kTkuxpoO0P?)@qSB2ty@)!X$`8!f`N z{yZ9!UHm3c`19p&QqmILpWgBpFFa|=e0*_%iGTWL^S#&Yg!-JNveE@Y=5N2hwQkB& z!<SF5m<Y;DZ-4h|hhV$&sV|}XYA-A??9N;_tL<0c0q(xp4-4AAq<OEEmhV3NP_H>Y z<ci<_GsbV%9P8V`YHqQvXL04Gx3xb`Z=Tn2)bP4{uaoIf-7k+UlVbhSep|l&@!6v5 z)0X-6jAeIL?6VVVk9|AmntP<Bk<6acSF_eI9(T^Wc52R#ncqcj-aHg`x#*o>ck-Ir zCy!>HKGmFm?SPNz>dr?ycOLTCea>Rm^r_$89PQzJ*<5hrY0780+~d7d&Dw%=`nKNW zlV;7yzInfU=^LP2?}}$(Ra<Y^<W4O;ns}V?|Ci1GzW(M`ma=%)bl6YyuJG+YKH)Oo z9&Ne!>QR2i@tGgm*$x(#%9Oo&p0_mo@v-+aF7dYCealn1=Eyn!UzU=-9PPW6A}iMY z+P`^XgiNyU){-($J%7EokGH&2HhfUQ!1upoV^hFe<CX^*uZ?)W7vA#hpZ{Sp51)0S z4et}6pQp|H-@N$3qepKPjk}+2P}R4{>pOgxeeTPI4LAOtTxY@ei|5Abg3K?aO0yLB zz8g)F)|%$X(YL3p-kxn<%5=9IdRi9mS{DDD!&}~Tt9Gl<q~CM;|6TUrasL~+XkY8| zwFNfvy-y0R%vZl<b8-8@k3A-n4nNEb|2Z$Vid*Kqr?R6=nf00SMeF(>-}&r)@S-({ z`@L)0>&G|jHG5SGdA=@Oxx~r%{JSSx3vXzCD8A?+<X(OK=*1Y>pVnE|4vU*LK5nzP zH#dCS*6YVJwJh>F9`Al?y6J(3jQg4Y8LWM~Lv3yLzpQz(Z5wk??K6QN5wDCMJ^%JG zzx2cOT1ZBN=eq-pd<DSVgOnEov@A4w;kojQj70~WgQQ9TZUV%>zfb=$rk$^lpXV$T R04ypPJYD@<);T3K0RTv(3F!a; diff --git a/addons/skin.estuary/media/flags/audiocodec/mp3.png b/addons/skin.estuary/media/flags/audiocodec/mp3.png deleted file mode 100644 index 258d161f5a51b19d6e22d4188ff70fff59318a35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1138 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GEInNuLn>~)y?rrplYvC* z!^a$DEV7R77X(Wn)GH3Frk()z70Octf5or((8}Ucr={_J=ccV0hWsa|3smn{Qkf%X z!GR4jvZKP6VthwR0+`@RPn>@E;^WS}xpfB}51>LuzJdm3nFk6eFym;ZtV#WY9satL zjyumSK7Y%1&Dtfo54HJCF1rXnpRt+mLB_W&&z8<kShV=(N}ImuvM6o7LpL(SY#yyy zs#IW79+l4PEmvw1UM4CNv}Wh^8+w-Z-fs_FyplQf?~l#BGy0W{7=A^i#-+ddAn{WB z^6oi%laE<7F4mOU*mm&Uy|dfdR14P_e?96WUHm38`E%a4(DEp?{_tBNFK!<QwC3Ay z`B&%5%%8rMGC}d~y-erNZ#i#!>eGyEn?AkZ=!<yl6MX-*x!hqz-Y;`)>@sU=xA=2i z^tjygeNVr*Onuwox0n9jSiM7vuUzU+p=?Cm75y2;xsz@!{;{9=xcuV}(zp4z?t3bS z9ZzhZaN1zvH?@bW)KxkA-2AltX4ziZ`1`NhukZbjHf)|-QnzK-zVZyaN#A6m*B<=% z=W^72-QPJ|H(XX($t-s?QGM-Gwe#_wg5GB@p2+;_b91^_SLN120mn~S{7GDD@$J36 zm(SLNAK5M5s9fE@D0lzHYu5s*M2;WR$>n{2rZ-0FtxSkj<KeRV|B8ZlrPOIkt$wt@ zHFr&a)Bm;WThr1)RrZT99TzUR5p*#(;{VK57H=L*xi9=fKHYkCFW<ur-GT3x7`-*m ztMxLKsV=_0HRIsh@2wdnZ0**{JJ)KqteWUv*kf|>RHUkA)`ttnPsZMCD{MJ^RK&^Z z-SmTjQy%S0D0Q{0==`_X*dpg(#ZB3?-@PTTe%f0k>3Zku6p6pt`SF@Y-<#MAlMg06 z=MF8LQ&REam;B3!q_1K!{s$lODNkIOBst}vVCuThtEwGy?_WKZy20<^hwnK%ShK#~ z^xFF6>*ZICOM@*o?RIe%KA-+*!+W#*hkjrB9)2+YohH4@Pe%FdmY|E*l@DB0l6hAY znlazC*5b`AuN^PnZn$*J_t@OSiL-UKFodR`pU9W=@b|&8e=d%<m}JT<b+)|T?I*L1 z;ak7t3+>IHnEH+te%X56C1_3N0`2wN6}H}<b~i3-$==F7*A3GiXxyGt`e4Iuvn3wZ zvOWu|xPMz(bt>;!f0q}?=vK~ux%TZ|Hj8;p&Qci<YZrfGdVJwh{>go&eN!_VR=K}3 zntb}p%;2S;3ZJYw=e9QO<286L17*zvkPO8lgAVUGE|ErL(n?;LmWF0*P@n)6PAETT Zzwtq{eD)@;^}w=(!PC{xWt~$(696c}67&E7 diff --git a/addons/skin.estuary/media/flags/audiocodec/mp3float.png b/addons/skin.estuary/media/flags/audiocodec/mp3float.png deleted file mode 100644 index 258d161f5a51b19d6e22d4188ff70fff59318a35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1138 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GEInNuLn>~)y?rrplYvC* z!^a$DEV7R77X(Wn)GH3Frk()z70Octf5or((8}Ucr={_J=ccV0hWsa|3smn{Qkf%X z!GR4jvZKP6VthwR0+`@RPn>@E;^WS}xpfB}51>LuzJdm3nFk6eFym;ZtV#WY9satL zjyumSK7Y%1&Dtfo54HJCF1rXnpRt+mLB_W&&z8<kShV=(N}ImuvM6o7LpL(SY#yyy zs#IW79+l4PEmvw1UM4CNv}Wh^8+w-Z-fs_FyplQf?~l#BGy0W{7=A^i#-+ddAn{WB z^6oi%laE<7F4mOU*mm&Uy|dfdR14P_e?96WUHm38`E%a4(DEp?{_tBNFK!<QwC3Ay z`B&%5%%8rMGC}d~y-erNZ#i#!>eGyEn?AkZ=!<yl6MX-*x!hqz-Y;`)>@sU=xA=2i z^tjygeNVr*Onuwox0n9jSiM7vuUzU+p=?Cm75y2;xsz@!{;{9=xcuV}(zp4z?t3bS z9ZzhZaN1zvH?@bW)KxkA-2AltX4ziZ`1`NhukZbjHf)|-QnzK-zVZyaN#A6m*B<=% z=W^72-QPJ|H(XX($t-s?QGM-Gwe#_wg5GB@p2+;_b91^_SLN120mn~S{7GDD@$J36 zm(SLNAK5M5s9fE@D0lzHYu5s*M2;WR$>n{2rZ-0FtxSkj<KeRV|B8ZlrPOIkt$wt@ zHFr&a)Bm;WThr1)RrZT99TzUR5p*#(;{VK57H=L*xi9=fKHYkCFW<ur-GT3x7`-*m ztMxLKsV=_0HRIsh@2wdnZ0**{JJ)KqteWUv*kf|>RHUkA)`ttnPsZMCD{MJ^RK&^Z z-SmTjQy%S0D0Q{0==`_X*dpg(#ZB3?-@PTTe%f0k>3Zku6p6pt`SF@Y-<#MAlMg06 z=MF8LQ&REam;B3!q_1K!{s$lODNkIOBst}vVCuThtEwGy?_WKZy20<^hwnK%ShK#~ z^xFF6>*ZICOM@*o?RIe%KA-+*!+W#*hkjrB9)2+YohH4@Pe%FdmY|E*l@DB0l6hAY znlazC*5b`AuN^PnZn$*J_t@OSiL-UKFodR`pU9W=@b|&8e=d%<m}JT<b+)|T?I*L1 z;ak7t3+>IHnEH+te%X56C1_3N0`2wN6}H}<b~i3-$==F7*A3GiXxyGt`e4Iuvn3wZ zvOWu|xPMz(bt>;!f0q}?=vK~ux%TZ|Hj8;p&Qci<YZrfGdVJwh{>go&eN!_VR=K}3 zntb}p%;2S;3ZJYw=e9QO<286L17*zvkPO8lgAVUGE|ErL(n?;LmWF0*P@n)6PAETT Zzwtq{eD)@;^}w=(!PC{xWt~$(696c}67&E7 diff --git a/addons/skin.estuary/media/flags/audiocodec/ogg.png b/addons/skin.estuary/media/flags/audiocodec/ogg.png deleted file mode 100644 index 208200a63e5a253fb29f6609136a2255000f5930..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1077 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`|9HAMhE&{oJLh5LA_JNB z|MxDlMl^~>D4H{A7bq7fzu+iov1*cabiW{2(sE~dwy(+rhbE@l`!z2Yf6C5^o5bL0 z9>s9y&m(PyCs?2ZA1aK{&Oa9(8S(4$jSIyLhq)BkK~U#W)=}f`)~O3#S~euIPGClb z43@|+L3EGqo_}6kfs1;=^EL@jy!&8DjMvE}+v2QG#^rtbx20w0!q}rOY0+7=SMS!m zNOHIo`F-Qnn@>2`Ys@Q)TP2gaUZULbkMAOzv%g$T6Pb!sbfRTX-Mn1iqMlhhLnCop zQO=g?H9L*>Uwz&iviADDr%FmnQ=jm^zw>1K95$7?DgXa3kJ@xC|L0_zhg*xg{*}eW zoZpzsD|@QS{&MN+?<blj_3iq0Ci~M9P9AN}?oF@Ct}gu?Jo8_Lz=`Qs%$0vkTI)Bj zLB{j+%~yiI&K*AXOZ(KlC9BiZCcV6M@ykcf6B9Rtgq^LKzDj<YN3EyUF1@U2!r$dp zk6&CdeSi14wHM<`)p8fV-W0n;YV{=L?YllsnC(^S&MekES<buW5Yv;0OPi-&+NH9G zRc51k-MTfi-#Jgdwf$|faK(pdBA@w=rC$&ff6V8ecBfcko=Cj-lNnQzT$*dF`t=vh z-saF(IZ^Vvg7Z#`^WO|TKbAW7EiaH(b&t7J0(4Z#xxz}PN!h>Z{AK{%zSF|$>y0IU zQaMi;{(O^sLOnkas6JB0J4&tMmz{Ta;Pg4-?m^!x|6f0`qUx2A8Kb3|bl9c?oe7Di zE3{X>*l};idZ*C7Wxf6h5uSI7?`Sgj2cBE(f6l*WZTF<9n^Y|AdhaRu^j(@EIAi;z z?{1fN6kFLbS_<92dqX-s`^3*Iv)OxXAJ|se3CuUB7yEj`Zu_(+Kf_nt&s3WCLF@Ud z2|rlool=`O>0QstXV;8Hb9?O4uY3K<_R-{D{7PuH!}*CjQm#GPlY73Zdv$r@g^RD+ zHwiwktzqh}og}vVCjYskP2YZfURFF|Q+VSw+0%^Y=lV*|Z;+Y%YT;MM>-W8TS6@5U zvaFo1xaLPmu1Ueu8&4(xoi@qer@yiK%pYyv*)L?X&nA7}v$kaYnl0x%!<}1lmxlck z*s^icECXOD2t2uQGg*YWeO2Dgb-Va(R0+&K5O<R^fS><SnPBAA!@<`sKX7gM$a#KG z$lKiKrY9JMPafP~+Z1XO&4`?<Q1g-lUxeVl*l)A+JLfUOvd9D0oE;L3RcN^mqz^N5 dHpm~h*W7<BNUKHZC$KnR@O1TaS?83{1OSO|^5y^l diff --git a/addons/skin.estuary/media/flags/audiocodec/opus.png b/addons/skin.estuary/media/flags/audiocodec/opus.png deleted file mode 100644 index df856a6d57653e665bc2ab5b65738cf320c38674..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1351 zcmZvcdpMH|9LL{WPK%Qok=Wxnq@Cl^R5-mX-KKX-uF1+}xt55|Hr5!Da<}1OE9Y2p z$uaknxr~sUMmX+rZMCS4h+<k!%hA(6C(n64&+q&E{`r3Y{3JQrLtB-!lmGzOio;@@ z0brBL`cH)^tS5&%bOHbrRdAT2F4XaPHl6spspb(JO1Tf-0Si^Il-qTkaW^#vs)7>N zuL_ER&%uka*Nm^<1X3`&7l<Q`TBc_sB3E}8uH+>F-H`<_0Q?`)<-f(+D2?xseRfqs z)g=LcE9q|jLRo&@%MKr5X5V%&=7Qq4g>?Cis{*#MYX6UcS@U{_;XO-QZ!AKi^ANYb zk8W{fm}LgFn>iV*5fsh~`1?;M$&F#im*yT=%sk|U)Y?s2l{&5<88mIf$}&#bi24t1 zMT`hS0^;Lc&0mhb93z>%R~pkJ!P>)01z|TbrihAk9GEYZsx=IXLCbAA<gAIqKJzr+ zF)x|R%4%Nv)oTR|%KKhSkfSP;X;ZzwDws}>p-vdKE#<0w*6B~dF;{l96ZD~c4EoS9 z*emjH%xw3uAj@T4#D_-|{{WBpacMnxZsj=&;$SCa@rB@n`6ubV0#;c5u@JN&t5Ymz zmr+n&q>Zd3dC1|=7dzn;Jf*Z|<Zyn|yW-MrGm6{W42Oo3<vHxm$+n$I?>%XJFcL`) zELkx&>hD37OS_Wr&VzkJ1&ES|9l~=AZ4`rmrTuk=!@4}ctY8^Rz{muI4le7Ww8UJy zmhXL_aX%}(BFKdGxI?(j{K_c3q`App_EZoMm_{o8bB^0AhLfgEMGD86MAvw35t=ij zM-qQvV>a{DJA^kv?<2@+U1vG>OYxvZQ@5`XEpejeC)El>9dZr5niJvb!+9-lRoNmm z9`rk%|IE<&JM`t|SV8A<D6!}CTFGNNTSR-VDG7i(-J?(GwwL7;5X4HHvb!lPX^2Ms z(><%+!5YfhMSD7k4p84kWR=T_1P8XtYg-hwI9&fJ^$wS)d8Q9Py_gww3M7q>+E^*H zoO6-bEz9gO8vcN;BL;KpW@L=;DfQrSsmxOl%}qv2F3=T>p!&?Hz~F8l4+NG@nqfcl z6&X$yCPKDgQCn2jm_27Q4Dq`0R=YZeLE(map!CR;YiVj)X>NmW=gh+e0ZXlNO#R6V zuZnk!*5em>BK&lX!5&G$NsEGg6E>}C6$t#j*lMQ*1EpS@^Fa}OdFKYL14|3X8I31k zLwk#@Lj01oqLZU5F`K>V+K;Gw>Ga2jQEU&Abv$ZIuH`b8+0QSnv*)-C*V<s+O{NZB zCFy$84`vU<#GmXVV|{}|sFx1y9e!Gqm_)`V)V94)Ev&C@F|D9Ip)a|jSk<?^&=G^B zGA|FRu4R{oUK(jP`;`mxQ~dP8wCHm;)iWnWa!1vlPxJRojzg$P0u<qEqS&2Haa-Km z7qYFD(O3BW<$nF{(%UlGq18J%_qKevoK$<?4<<Ce3EC>~#l%??g4hBqV|xC&t0txG zq4O-wNIy)>V~3A~kG^qzTHV=fGMg#+XbWjP`h+yE!^95oUd;IMMAA0fA*bGLbe=L< zpS#BS`Aw=TW2RQgKYg~D;akxJjiu@2-KGf!JcnB&2)T&G5Yl3=b*mwS-Sl6{Y&3=S zzPE8hTjMa(z!8<NNC6#jDv+^3LSK3ttpb2Ay54CYO+||TIkVkoU626I#vW5~%q#X^ DbBuPK diff --git a/addons/skin.estuary/media/flags/audiocodec/pcm.png b/addons/skin.estuary/media/flags/audiocodec/pcm.png deleted file mode 100644 index 0c7a5bdeee7257fee4a6b3a7d0d0a8d5501db195..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1187 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+<+8QlDE4H!|mNY8hJn-XMsm# zF$061G6*wPEVVBK3bL1Y`ns||;^Gm}HRyVD?LGqoi;1U;V@SoVw{vcnu5u7@=r3Q& z7{sL67|k@z!OK9kN7aEd$SJ64N`lt}p#?2F<qfTv4?92AOT2SiUinkRnQu01C%m{D zX4}tUa^S;+3{TKtd}U$XsotgMk9VD5Tp|k55aF`jDBx(5dWI7FgfIq6G&q471wM9s zaV}id>&l6>DvMI`*Vc-to_t_uzuxNF+>L23j?Xlkcyn*t&6f+xxmTs%^;+FntG2k` zWZO0F^jbC9(|@NpzxFyVo2|Y}D(3f!#q(<A+~r@^-7H$O#d=Om#94zIe}g;<>*Tix zow^nA%iGH8ZH>phLbq9`cXg!Z|NX1>H0S)1>N(=FmLb|&GHc^2jCa17qv?^kbn4b; z{DtXTR<T{Wc7F9v*Gs3Ip9;vnaDM)2i($n4-YdcpTTkpWpStqk;yYWeIrg!iR<tsi zCU4b!c-7)HJpQFlS0zrB`3lELnome(E4pz~xqvV1s+PsuCrhH3UwXM;|K@zckl9km z{cZyL&x}cYE2}p!r@L>lu&kM*xVHEE!^%^yoz(76&Z)irb8auoxy6ZXC1FK>w1lL+ zH}BpyPjYS6n?G(EFMrnd$$UOEXPI7f;p5lm9-ivC*>&Y7@6!-Y%QVL;lavkYG~eB; zOM8>~IN<ZAn`LE}<NWp96L|b;rRHP=Sr#o$3+(gGoxb<^X}hWaPEM36RLWPKRWwN| zA^QmD)fq<py&S>H$LIXm^2qJ?bZg%k`cF=G$Tn`ir!hS?|A(r(iq|T))4%>3{k!Io zyz=6qV|7y&*~L`6cD7u;>3jM6%cV=z-rPJt(R0Jjvn6|?_uskoW3GQ|WZ;tJI|Qpl z&26SUj^6eu>k3QRQb9kB2d)h+N!oiwgul2LKS;gkeqW|#lcH;MpTnZvYUkhHDC`vV z;!@VC{qfN{dY8=26B$Wnv+qxSYj1r%Yld#lzBeoN=U?uMUUch)#xmvdCH=Shs~%TA zS<zb}TxM5S5d9{jK1k{KCWGvWC5PrM%?PpVo^Mp|X33>p_D1c_r)?j@du2`^mf5!C z`Od8#eT!WC>sk)ZQPZ#Ol0L8Eo@qLxQ)bTgS2<4#viy(RKb$o8)TO9<f2~r_e^K~4 zeW|-j^-byNXN3ZTB01A@QiD!Mg>9O2opbh+$_Ed5kNx#uA7&mid!qF@_UmC{Av5;% zuHfeS`TBegYv$8gmi_#duU~2zMV`L3o25|Ldyd_Hlm6{|iJCK0F6}$1FMV@EoO52r zPJ?Mbrr7#~7kuk8{{M}01$rg|@e$eR+0hSk6J||zdBOn8u@wy3vji227%-s%J36$H b{m;O>Lc!=`*daAwk;35V>gTe~DWM4f@8tu= diff --git a/addons/skin.estuary/media/flags/audiocodec/pcm_bluray.png b/addons/skin.estuary/media/flags/audiocodec/pcm_bluray.png deleted file mode 100644 index 30b4f8b1380f3b0bb751e8d312a760e3a3e4e48d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1187 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+<+8QlDE4H!|mNY8hJn-XMsm# zF$061G6*wPEVVBK3bL1Y`ns||;^Gm}wP>-7d&|JUV&duI7*cWT?VQ`Cs~kie`pcIx z1~F+iMl(%w@G?;CQFY)9atdmilHfH#XhF+Pc|$Aa!_H6j67SrWSN;@n=9>-M2`}!3 z+4ggo9QZIH!xJ<ZUs+gps(0!6<6S2hmxw|%M7V4>3OL%No}t7(A&kKi4NhQ2fsY+u zoC{a=x^iNz%A%C~wY4IuCm-0^ueZ83cVpU%<1@`B-rU=E^W}na?p5h`y;e8Ysx9s} z*>;UPy;e>3^xrAYuf2}TW~=X#iut`_@w{3&clnofH;dM6v7QqXan|6*-yn~|I{7U^ zr*1|3^0u;iTjO!B&~4V~T^*_UfB&jI%{jlMdXBiPWr((x%-Z-0<DGBjXnJHWox1fI ze_{HTRcx28onO7v_0lQlrvkDsoS%Q%Vi+;M_lj`D))V{8r>;D>_|BGVj(zN>6|GFB z$y;?FUbT1)kAJDtRf$t&zQS>m<`a_Hif)`#F5nBhs%7!^$&x7MmtOAIzd4^UWVRG? zznj4RGh-6p%IXcw>F!%BENkW{uI>H)u=3PvC$;;Nb84^uoZHKCZgFB;Nm$VzEg@;| z&AYeFlU$qi=8v1k%b&G<GM^94S*90V`1tj?ho^dOc3t_&`!s~pGR^VIBxM6T&3E_e z(%xh~4*2}(W?9+gIDb9&1RlRysW}-zmPL!x0{gskr|*4!+HUH<lM|&1mGV_*6-|;# z$Uee(b%s%YFGsNQ@i{-XJaYRz-P(7C{*%)kvW=VXX-tpJ|Do!x;<d`{^soO$|E_r? zue^BZSlyIGb}<#Noh_Gd`d<G2a_LgFH#g5u^xUxXY{{PJ{daEtnCssf8MtKm4#6r> zbDJrTqqlv^y24VnRM1c3fop?HlJ;H^;V&-64^l6>-<N6Gr05#m=dftE+WEIP3Ohx; zxRkYOe|)r#-X(MML`IU??E917+FPH`nxUJs@6Ag6`Ioz*7u|ZHu}ryqN&l_>s>hX2 zR`iw#m)X@7M8C<X4^le5$sl`T$)R~mGeRu8=Nr|#S#oKYy-~aKY1_x}UYXN}WwtGO zzH_Tb-y+xkx|V};)bwk+q|d9kXPVCFl$o>rRnC)wEdS&74=2q%bt&rJU#ryfUlhJh zU+S(>eN%e+S)st7NY3<})S%N*VVfph=bZhd^1(yiV}JeEhndIBo@jlJ{d$;K$c%lx zE4aCSzCNGBn)!5=Wj}xA>z7(ak*9C%W+@c*o@2M)q<=eKqUOw$OZ!ghOW)iO=bV?Z z(_q?<DYic01>gFN|9|6Lfu4y#d_*>ScJ#yCgjthao-n|2Yz2e%EJ1}L227~Hjt*^P b|1&VJP%!!!c1R6aq%e58`njxgN@xNASI`2r diff --git a/addons/skin.estuary/media/flags/audiocodec/pcm_s16le.png b/addons/skin.estuary/media/flags/audiocodec/pcm_s16le.png deleted file mode 100644 index dc514806e30862779167b94f423a87cec3516d7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1187 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+<+8QlDE4H!|mNY8hJn-XMsm# zF$061G6*wPEVVBK3bL1Y`ns||;^Gm}W6LWQXk}nvG4XV945_&FcFyh6RSqH!{pCv; zgP1fMqnV~Tcp0ens5)>4IR!OMN${E=w4i0DyrC8IVdtlMiFa<xD}RbO^Ua3sgco<i zZ2LJ(4t$u9;RzayuPm%P)w}fk@vak$OGF_WB3!l`1srWs&ro8Y5XNAM1}8A1z{id+ z&V{RbT{*E<Wl>80+FB9SlMn3d*IQkiyD{y>@tI~5Z|-fo`Eo%y_p0=}UaK2x)fV@g zY`eytUaKa1`tKCy*IvhEv(<M=#r$5ecwViXyZp<#n?-B3SkH-xIBRg@Z;(e}o%|M| zQ@0|1d0Sb%t?{^5=r-&0u8!3Fzkk)9=A2(rJx5&DGDKTTW^H_h@y<7MG(9qxPTl&9 zzc78vDz;14&ad9-dg+w&Qvul*&d)z>F^rhsdqp^6>xq5lQ&%2bd}qrw$3FJcidH7m z<gL07uUfo@$G_C+s>G=>U*R}O^9jjpMK?|=7x0B$)v|c|WJwhBOE359-<(evGFu9{ z-%ViunK6lPW%UN;boVV5mNjz}*Y<vYSb6HTliK~sIkne+&h2G6w>Yt_B&_I<mXNgf z=H1)oNv_R$^T$o&<<Htana_vjEYpiFeEj;{!&5ysyRQ7?eHy}PndW$9lCpuF=DT}! zX>T$g2Ymi?v#jiLoWGuX0*_y<)SQeU%c8|;fqmY&)Av3<Z8!Db$%#^hO8Kg@iY7@V zWFO(YI>V^Hmm^sD_?#bG9=ZLVZtXim|H<hN*~ZQHG^WSq|4?;T@ml3}`qzJ>f7d*c zS6)1HtZvF8yO@gC&X&tJeJ_81xpb-8o15n+dT!Wxwq#HA{yVpR%=K@L3|z8&hhUYc zxy_Wv(c3;{U12F(D(I*2z_r08Nqet|@D~^32dNj`@5{7oQgn^(b6B)n?flytg`J{a zT*_LtKR#MV?~=KBA|uIc_Wj9k?XAye&Ct!+_hzO3{L5X@i*CKpSf*UQr2kfb)#J)1 zD|$<W%k1h3qTgiH2PqxjWRN|v<j}mO86lS4^Ns4=EV;DH-l*OAwC!VfugvMgGTW9s z-?`PJZ;@+%UCY5aYWlTZ(&ts&Gfii7%FNmRD(6W-mj7}4hm+=>x)gQquT|>#FA86$ zFLhU`z9~KZtWaQ3Bxia~YS8JZuuYS$bIyKJ`QRb%vA_Q7!^~r5PqaSAemzVqWX8VU z72I4uU!Tum&3rn`vY)^5^-C?I$kVrWvlI$@&#~KY(!ZTAQFCU>rF|#$rEhMCbI!}y zX)x`_6kDJ0f^U7s|G#mrK+i-VJ|Y`EJNjX6!mP<IPZ(f1wt_)>mY_lr113~pM~61D b{~4H9C>VVVJER6IQW!j4{an^LB{Ts57`p-V diff --git a/addons/skin.estuary/media/flags/audiocodec/pcm_s24le.png b/addons/skin.estuary/media/flags/audiocodec/pcm_s24le.png deleted file mode 100644 index 81ceacc06d80dc0530d0bf44a7364fbdefd686a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1187 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+<+8QlDE4H!|mNY8hJn-XMsm# zF$061G6*wPEVVBK3bL1Y`ns||;^Gm};}>7KLXm-i#l+LaF{I+w+c~#OS2>6{^p`JX z3}Vu3jAokV;ANoNqw2sJ<P_92CBbWg(1Mno@`hH-hn=75CEmF$uly<E%r_gh6JFd6 zv+d_FIq+dZh9_t+zOu0HRPWOB$Gc82E)j)jh;Z3%6mYajJwu6oLKuT38l1q40v|iR zI2W$!b>+lbl|?D}YimVRPd>1-UvG77?#8qi$7h;Nyt%jS=F0`;+^f>>daZ7(Ra@L| zvh5modaau5>AzE)Uwa*w%~szf74v(=;(4`l?(#3|ZWgWCVm&7&;;g}qzd;^_b@E$; zPTh+5<!xp4w#MULq1&v}yE;<y|Nd2bnsa_h^&D|o%MfiXnYHm1#yj84(e%h%I(6$a z{=)PvtJp4GJHL9T>!nl9PX%OOI6wci#V}%i?-k*Qtta-GPhEL%@trN#9Q)W$D_WUM zleg+VylU|p9{*COs}iTme1+pA%_k(Y72P<gT)-E0Rm<Y-lO<8iFTLEae{()z$ZRR( zem8;rXT~JHmDL-V)7`gNSk}x@T-*EoVdbgUPHOij=hR;RIk%VP+~UNxlCYvbT0+v^ zn|E)UC%HE3%^x?7mp^O!WIi97vrI3#@bT+&4^Q>n?7H%k_h|^HWt!uaNy-Lxn(yw_ zrM<~~9Ps(m&9bt~asGPl2|RwaQgbqbEQ=PW1@?L8PT%|dwB6KyCnri3D&?!rDw-sf zkbQ*n>I|d)UXEbp<8yv&dF1wcy0z~N{U@h8WE(f%)0iHc|3lSX#cP$@>0kei{$2A( zUU~7*vAQXX>|!cjJ6kT_^u7H3<<g~UZ*HES=(%C%*^)ic`|sTPG1tE}GH}WA9fDP& z<~CCvM{oO-b%mvDsi2?61J?$ZB<;N-!e3mBAEaJ%zc16WNzpaB&tcJSwexRp6n2Vw zaVcxn{`hDey-ViiiHsz(+4m>EwYNT>HA6RN-<y^C^DlQrFS_+YW0`XKlKxx$RgWv5 ztmrKfF0-pEh<=k%AEb1AlR@^xl0);BW`tOF&o`=fv*gk)d!u&e)3%S{y)vf{%WPZn zeCJk=zD2J6bu9<ysOi^sNuO77&orIUDKlsLtDGkVS^mfEA5NNk>QdCbzgDT|zbJg2 zzSLc%`lj^svqFJEk(}u{sX?cs!ZuC1&N=%@<%5U3$Nu`S4>OOMJ<<9c`}HufkQw`W zS8#Lve0@HLHS_5#%YOdK*Dtk<B2VAi%~B}rJ;!dpN&j}fM9rBgm-e01m%h0n&N(k* zr@^!zQ*3?03%>Ok|Nq9h0zDIf_=s%u?C6KN39}}<JYj(4*a`;iS%L~h446=X9Ua=p b{%2rbp<whe?2sC;NMZ1F^>bP0l+XkKh^zt* diff --git a/addons/skin.estuary/media/flags/audiocodec/truehd.png b/addons/skin.estuary/media/flags/audiocodec/truehd.png deleted file mode 100644 index 4bcf8c1fda2dec58b4d7b11f1454c10bbaf4435f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1579 zcmZXVeK^wz9Kfga6h%lS{V1>3RF2V|F;3bWqP#r}bxxSK$xAhwQu5l&YeQialJ`c$ zFok)EERXk@Nyb=HL}?>Vch_^>^?aV^`~LUm8-LasB_*jS2?Bwn%+YXL5J*gVvku)M zz8Q<33?M<EZ6D0xC+$O7^VySO4@_lDJ60>ioL^f!lS4~5Zvl&0Cd)mEa*DhoWARGP zOoE-nW?-Vm5$6uiG_4sTfV&;Nf-O(I#q+X$2ez0>{|JctUv6Nh@w`&DlW56FCm7_p zEqUu#=nQ{GAEPDh)pxVMH(w(N`aQ3=zvwequT8at8N}EJLlswr$*vzxE^_h62)+=h zWcOwy;$bbvSYMQXThCt`mYDhQ{m1K^tTI_<bEELFV6NP}ESH{42+!Evpb0TaD_%() zYU<!;gzGQng}U|~YOaMD>+io7w!X68ERmTf$9s;!FzR;5k&dAH!}Wd6xGTQB$&pI% z0-%EWz)q?+EjDc@v3J-K?2?N)C)MBwb&4q~cLdAGLc5){s2%kwEf<mni6-eiBNgd| z+0E<Z&W76_;LB%xU?t`2q0%gR-yw}A){0tiUk1;p+k#`KZrzU6t6UO53V2r6Ym%7N z(6HIg6eA=xXOS~BMOglvdNq9jMFO7kRL^3%jQi<)wim&{uxG2{i7-E+9X2Q|8}MSj zQkd8WZHVvrd>pb98Xl}m1Kia=<*OC@_}S0yJL1c6-V#l2O&|<rt(`*^hyW5+J@)8+ zX{cFQh<r&Tva0E71a^5(H>I~Ay+%MXq)Ecchsj0|&S)v+r`3{;3`h4t<7{k={p1UV zIMFTI5RQ3kiftDVaK@RHF$QDLwpZ06gP;2sT=@9D8s!3`D}SlageS5L&=^Z;8y;qQ zq#s#$jBbeS$hXaR@?DE@Ra$ttD+7wF&-50q<)zXfz18sOrj^Y0eeF@Y>cuLzton&E z@rLZe%X|~1YxS9@6P+Cv;5jZw^HYb<|2B_&zdfx~-*HH9p$bwp4>n#>G@^y*PDVd1 zS;|fzD4LFq*c7L0Ny_8YQTgS93JzHg9B`s2#1AoEpBWEOA^1nY%oXz+XuGcIQ%X;U zIo96!9xq7L8G(%|Po?7qF{t_0woC7zr&dd8Ov;<sw%Yj|VfOT(Dh|9_8JXIJLD+?o zJ0};TdKgh^sCl%HlBj6>QVb55x|{Ciht|XL$E+)ZxU-VD#Jz?ozd|uaqIcv3$CQg| z9iSF6xlXUH`z&A;`j|DWyGH|l?BY;q0mAP9)748<Fem#q2@Atj8Fl-FXC0a3#sOl8 z-DCa7gXNX>jFxLDV7u=zN@=i(^Bz}SVP4(9_Lq!V&%&`GeoPn&X0c(@N$gW)*@o$O z>D2V(jFJFzo;}~c3^RZifZ^2o9)!F3u?dF~h`n=BW5`@#fMMh$7fmj8z<M8LD*vX0 zkdn0tjw`^2kFm9zFZ0~1s#1@*SWaahr1du2?$ukaAzDyU=&q+VW9gE?I$E5Q!fa`F z|M}NJ^_ghtYc>tzEBh(9iH#sN2j79`Oe(K6ohZSLBN3W@ajgDgrg7mddg$GKJLCon z76_s&!aVVW{I#Q|1|reS{Z@x2?>jEbXzn8sN%iwR--2GM2rSx-*G7`n($XZ0_^wVB zo;bEahydsf33K!n+)6q)gIIkdF|g3ZK--Oy+uDzZsFE9(FG-LaAK9po@!-Nct<6-G z2HM^$hd%QWEUiG^MUF_iE1G1Qbc&Wc5toH$8c*X*UMuWQSm%Z}9&VVki7Hl(6)+j% zNt)p%&+4uOR4;ocTVN7lZ5baRQv_NI*ZoP%rJwzTQ==mJs>ke`^pwKJ1eSub#}|9w zU0sZE<Bs+NJqxr?!VxQ$uLUu-ZK+x8(<lwd`=jvF<yBTUa-OMygIZ`i?;^ZVrtCyf zgcoz&+9-cX@&DrcM}o;)VSy78LxIT(P|!C=YPvo55Z~;7<lDu+`8p6G)>Z4a>p=i{ RaPtX(%n{b`3NyFJKLN{Q`%C}; diff --git a/addons/skin.estuary/media/flags/audiocodec/vorbis.png b/addons/skin.estuary/media/flags/audiocodec/vorbis.png deleted file mode 100644 index e7ec2c5361d05c12e824e93a22c272844e7de93c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1692 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 diff --git a/addons/skin.estuary/media/flags/audiocodec/wav.png b/addons/skin.estuary/media/flags/audiocodec/wav.png deleted file mode 100644 index 76cd02d3fc07355642a45c6788e9310e678f11b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1321 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GK6tt~hE&{oJNseaV*>%V z=|UcyOIX&iXf#eKnBw52py~kRYA9+l0+|K%de^*eO?|?BhvVZtePfCAw%S{NQ>@eF znG%ZIWE!wQh7*ja(B7M`=Vzmu0y_-WSX-X^v7h%edl3T~RA4UxBbZ7seZ2nBb{n7L z{YB9hpCi|~xTNS`FDf{{Yx3`x6`J{1i?3}eT=zvwyuSa`{E8<r^PYZs|8cg?#K$G) zo`#-VK3^<PFz-pG)xN3j-?QFd{uoymdOA)kdA8HGXS&ao-I@K1jAhrSyq@z_VgK^S zYu8ALeBvrmJ%921^-o1j8I|eUUv-|$=!{!fck_N|{YUFFd++!j-=7kHb<LldQPKVj zm8WmY-l0@<MouTORW2p)EyvP>LL#5mEwNv_Y}Px&r#2$lFTdMAnQ=CctLR=;e)QX_ zuX!o4Z-a}SUOG-Y8X&dhjbaa<O1s{S*eew)DptqFACKM}b)<6PmHvZ&TQ4>~xuAWo ztL|mMM=w#X_q?LE8rN2RSl)K&-k}!`MMqv+zUO=yu#vr6Z;3eP;i>mhR<^ax*Z7iN zE%@Cj&YF9%^T~yeWA05m98ejkZ8CL5ox02Y;K<WzQc}7Lx2PWfBIwpW$$7d$Z1uty z?2)V9P2gH!KCf(LsZqqK=q-<KJH31~yXE4-CmOj<&dK#tqD}7xcb3%eT*tHiv81Dh zec%<R%+tN*Pb@fXZ$G{!)3tBXT5GP76<fWRoVO5*6yK&gCF<78zN_96vOavVYPPq( z21KeXz4SU_x!7)(hdg&@H&x{>G}&7=Ke5EXvLxb)uAQ9L$8CY)$~`>Q4liD3|J-79 zx-DQ<+0xx@yoN<{dh*UEUY6jWx<%&r!9~A2_b$JX7?SU~HqlspO56P}B5v)I4_CR& z;}b1p>;4!0JJ02Xd1m^@`r!L(rd4qj)x1hLnV;0UaM5o^y9lT28@JD1y5rT?Dbu8L zta^2%;y9f8>ecr9OSN^)d;T+_()@Fai7K~#?-l3F)3%XMel#82G-<76$eM$T%B{Z1 zp87OTMWilH<E?oQpN9Ld6L;mbekN=*U-|99ii<*C{TGX`U$tJ`e0t(Lt~U!O@lU(^ zCA_To`gx!1it_N!b{C5!WP|#CFy7gFoBz?;*Rh3nU*8VV{jty_W%AlJyZ9D6pIR9H zc%g*w+q{5k){NK-L1}H5VwZF&9KSw!|L4?p+t;P`)hc_;MK^Af^t*ahpZ|-v8~aq{ z`i;l+rkU!jIT?Td<df$=7na=fZaeYu<dgL*Rk?QhQ>KbVSvlQGyycX6KCvXg^3!_0 z3SVp8DW#vIzfbp7EL@U!qxy?nz5V+8239L%J~HpPROQd!9s1{&vG{Mj?)$vA_6xPj z#5>#(^m=V8rxW(^>-2@M<1^Om-+S+0s@Ibr{LM?hoWCv=`E*Y8>{V}F6W6A!Pb`VB zycJg9y{JNO&+53#n{=mqlX7F9_SnmI^Np};RT|IseYjnl{CR&+Y~o`rk)Y$r%M(iy zEE$mC1ZGPxn!t<(m47S$kVKRVHT;$gXeyvOkWB-z6H@*$@SNQvFRqk*5m*E=c)I$z JtaD0e0s!XFSZ@FT diff --git a/addons/skin.estuary/media/flags/audiocodec/wavpack.png b/addons/skin.estuary/media/flags/audiocodec/wavpack.png deleted file mode 100644 index 6501af99426a86b785a14246664459bfbbcc9e40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1480 zcmZvccTf`s6vh)}6(L3~f{+$aDG9PeLdsGIL&B61ilU+%5T;>>G6V&cVSqA>Xbcd9 zAP7WcTJ{QtApuE{lt@HYfWS$C$PkL{^=y0Iy?gI{-@W(!`Ac@Rx005WmjnO+($=TV zod5tJXm`IZA+p;`s)sEB0MT@7b5ob=9Kquy_lZ;B#O{NIA}~|T&E25^#Qge20d2l0 z=Xjw-RTYWMWgK~_`amdznb2?l9_c$>7gtYmFon_-c&A!#-P{Wl-vbhXNT9%YrN0g3 z|NHrVZ9ttv$AMo@QbB17bU_PC^`D=wApmjwU-jQmuT#c7HqlKFirdLn?kP#H+_6JB z*McTrHs$tqg^XY$=;xP0g;!l;XX=8dpu&qIvJZXfde~!z2mTPWD%UM*)(IToaFPAX zoC_xz?_xMs(Km>9*4f~QALuP;=>DksmRQYeTIa;0vI0-gmYEl5Q=Vh1N7DtXrfksd z1-#~z+~>y3Iz<YRU0d%T2@2AGfi&spe9v6>OZ<#x$u_hb^-`NIycz|bT`!j(b5)5E zhW41~5cs61_VSIw<{5{BAaY)6U#Sdb_?JCq?5qhP%kvF_x{!2P`>N=cUchLAk(D4Q zq-I*TV6>;CCIs8O8G3$Z(Y`hf!|KjP>S~E;-8|BeGthpn`p)Y5BSssg<NkjCF_$ur zk%3C?J<G~GSWW74Z_5CD(ikIJ>mvMTR5*P`3>u_{%{O~1pcW`}VMVu#sd`r|=kcAc zArVD%-BJr}az7V{Az_*;XSF$p;YRaE!&rKOR+RDJhaTh|enS8rw?gyAScxVg!??;) zc2jm!zLFWfD(1A(KyO)ubULi&v26*3#bXX=Au1T{6me*OBY4Gfy40X>Z%Hv3n_mOZ z=~d~uo(ChZ>OT1q749sWmYc<NWDA!IqoO4<Dzu*YZ*DZJDtQ~GJc4OxH4S28)w!b| z+9C1H#Dr0a@NIX5(t{_tOq_n!UWbvg#aMf35Z6h-&A0BLBorDp1gljYcQm+XjD{=@ zvS6(<@FM;j8=q=WVua}$BwzwRYdR!q60G*I@J!-SJ*j8Lv--F#J>9TGm>s&Y#5SoH zk6T!{(~#V8Kz2skn?S<I6PD-&^T~)#4q*;fG63dK^|pR$$;QT`cSs8E9wj9NaN3wy zB*&!_r@nEeye7#pWQcQlhic-k!OA`e5x&T_t6pqqnqZ_2wq=~_l(cXgNeK4(?g(yG zka?-bKgBn>yt$db8ON@5GhTtWd&P6F4l!NA^_P&u^fJ=(KHQwTy1tWsQLgK#0VLJv zp`mH(rR+ryX9nW1@taEISWj3c_){!3{KH!^tRsZjaS{bZnW2U>gfY&1BysC|mOlg$ zY0Ix$HS&H<;CO8id0{U@o8%A>{$r(am)}yShPUs#a#nNw?AA8?&#KP(3|C2rLF~H} z@B==^NyQZ;413;NEj`d#zL8<de%&9zlRIr7ODeGL8;Oreny`K$?Q=s%4OvtyAp=Ys z#|r!0-px&cHr=J)cgS>Zgrn}dDJnvPa)8~Qb3ZOH64gf3<{EP6<3(3)z^UWPXr7!4 z_t-6L#?oEbl8ljza=EsN^V~iIycBg&R=g~ed>7WTQhvfV$g8)iN+AwS^Rc|;<;m}V z>Qq~ts-hS*P+=2H?gAC=oH)~}Q$La7*t1q&If<sVNS#0VX<WD!)lb;dO+ylgm;r?4 zwiONMsl3_EX`9hqm$y7g8*qT^D`o|FZrJ)6^ICPD68*a;92qO%p0JNX-<d&fQK_Wi zAqvl6&2@4kPV6hhcmU9s`UdQPkX9fB2SqXU{sllfP~7aBT>O^5Q?(69m_4Qm0xFd5 P5*lD_VQ*ex<{AGdseQn& diff --git a/addons/skin.estuary/media/flags/audiocodec/wma.png b/addons/skin.estuary/media/flags/audiocodec/wma.png deleted file mode 100644 index 20093c15d56d31d575f039703ac6da7b90d19540..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1378 zcmZY9eKga19Ki8;DB3~tlsn7A&F-xki)#F=5n(K$?5;)Dgj=+3+3q4#4x%Z^rfJng z4_tYcc~~T(Ee{D3rbcJxVR<N;Db!W>+&|oVUgvzy=X=igkI!FkhMx~s8w>%1Kp<@| zPqzagkc!UA9<8aqG7Bn29w5+~tzK>}_-OIe?J4@*-Nqh~z7X=>oHc>wu(B28b$RMr zTyXJvmFm_id&yFO(f+h<yIRG%8?m25-Z0`1OO81S{}6+owj-aY?$-G~(8y}>$7C%F zHtX46ccJh)<^&M%9og`nAr@(roy2ZcX$G5XtOla4wuJ4@sXtZA3sZE-jg!~}&g|K) z<+s!I&;GjCmUd@ULaVYv2lV9W#kCn#rBStV`)&3IJYf62cL0xZqRj$}mPsSW&k@Ag z4HJWzC;^=mA*)%-oApOjUB*_!FQQ^+LQH7i1OZ_Tgut`t%JBU<pJoDMJ^;)vp5+^f z-DdWWM%4-q4~ObzHMb%fySt*3Qmu)QcdS1ld*e7EO@>y%XiH1Ns3yDQOp>@Bso{kX zrIp+@U}KU^m2~-JJYVjHD{6O{&|-&vppE;DkBslD5tufr<t9KMy^}L<;ysJ9#^GnM z&L^<*IlK6oLG^>h75@BwC&5W{z#~U{%P);L7d6_>@)vc#d=S5N>Ps#QIs(h@w2hkb zv^LDG!n68A?o$i5zE#h9lc=dZC4*-k$8eh#fR%Hplor3*wHJz(b(K)8ZXJ;FFvCv& zK(sJ7B4B==t7k={q&eK~;MoN+y1LG}h5^33NwZBBk(Zf|wiB)<E}KGQ%?ZDx6Prl; zUhQ!Q8_|se1C^ihmV6?(KM$_iI2L*8I_{v(%dp2xpNS@`6vqyp{gWXrK39(Kmx$@2 z&e`LK)Cyw!!%AAq?Fn_i2XnixAG{ji6*cFG1BjPxtI@zr6upMS@E<)z;&zD~rU=pA z3V*<Zq9G$zCC7fC?cBLtD|S!r^bM;NKJpyA9wIV`m3gKKpmh!NhxU-XWyB{?xqD}l zUq%8<--c<4y<f*NLDJ<gEbI)X(4CGuh)fCBv$B7k`0kQyn8-KDeo&B3MO}%aP$}Au zk0Zd;c9gI9A`T(CasF|AK2(kv4?YcqAc1){UGnOzx6l?K>RI5u8<`Gw=Ipp(c`tOq zbCxJzGK3j7vRU;C!J~uqyh%0gLUV<Jbh>8@ZPV&{%dvO4kH&Grlh!wU#NVzBuCxnB zpWrn?;m^oGAX_@pnycYug;%Mi;)|UP%V!s=`uNe*U;WMa{FJorLpsRQJ*?f#%@Km~ zpb99`r7V^FLMwgHAeq<u#nh40mvGC#x*SG{Gn*M;6wl)p^~>#sR1aGv<D7|aS^}Ng z5~ULd3aC3?ZkhY(XpaSVNjO}#7+$(Tk+eJ&;~a!}?=k*cF;41+?xeMEJDTAtd*0%g zmoJRmPY6f87BKU7T_eIPU_#plDDPMru+LjjosJn#*BiJL(|LYiOQuUy5;VQ5LBCW& z6|s~H5Q?OFNJ9OLp`n(P>VT<iWCJ{pytQFcvu8su?qr6A;GTg*kt3S^RtkYX{5ZTh zw04Qptq|stMhmN<Em@n6ps1x6%SREZS!B%QbRle`z%yiQK-%BoCn!;3dX>VOm=|B+ zOBrKF`zuE|wh!DvovBjCxgn=U{ZBty@=sA#<~6m|vcXCg+4_jyb+v!L44d?CGgg8B lCpX{YvpP86Ur*DeW)nMFLI*B<YFue2$jjZwt;Cg_@Hf9^eDweT diff --git a/addons/skin.estuary/media/flags/audiocodec/wmapro.png b/addons/skin.estuary/media/flags/audiocodec/wmapro.png deleted file mode 100644 index 20093c15d56d31d575f039703ac6da7b90d19540..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1378 zcmZY9eKga19Ki8;DB3~tlsn7A&F-xki)#F=5n(K$?5;)Dgj=+3+3q4#4x%Z^rfJng z4_tYcc~~T(Ee{D3rbcJxVR<N;Db!W>+&|oVUgvzy=X=igkI!FkhMx~s8w>%1Kp<@| zPqzagkc!UA9<8aqG7Bn29w5+~tzK>}_-OIe?J4@*-Nqh~z7X=>oHc>wu(B28b$RMr zTyXJvmFm_id&yFO(f+h<yIRG%8?m25-Z0`1OO81S{}6+owj-aY?$-G~(8y}>$7C%F zHtX46ccJh)<^&M%9og`nAr@(roy2ZcX$G5XtOla4wuJ4@sXtZA3sZE-jg!~}&g|K) z<+s!I&;GjCmUd@ULaVYv2lV9W#kCn#rBStV`)&3IJYf62cL0xZqRj$}mPsSW&k@Ag z4HJWzC;^=mA*)%-oApOjUB*_!FQQ^+LQH7i1OZ_Tgut`t%JBU<pJoDMJ^;)vp5+^f z-DdWWM%4-q4~ObzHMb%fySt*3Qmu)QcdS1ld*e7EO@>y%XiH1Ns3yDQOp>@Bso{kX zrIp+@U}KU^m2~-JJYVjHD{6O{&|-&vppE;DkBslD5tufr<t9KMy^}L<;ysJ9#^GnM z&L^<*IlK6oLG^>h75@BwC&5W{z#~U{%P);L7d6_>@)vc#d=S5N>Ps#QIs(h@w2hkb zv^LDG!n68A?o$i5zE#h9lc=dZC4*-k$8eh#fR%Hplor3*wHJz(b(K)8ZXJ;FFvCv& zK(sJ7B4B==t7k={q&eK~;MoN+y1LG}h5^33NwZBBk(Zf|wiB)<E}KGQ%?ZDx6Prl; zUhQ!Q8_|se1C^ihmV6?(KM$_iI2L*8I_{v(%dp2xpNS@`6vqyp{gWXrK39(Kmx$@2 z&e`LK)Cyw!!%AAq?Fn_i2XnixAG{ji6*cFG1BjPxtI@zr6upMS@E<)z;&zD~rU=pA z3V*<Zq9G$zCC7fC?cBLtD|S!r^bM;NKJpyA9wIV`m3gKKpmh!NhxU-XWyB{?xqD}l zUq%8<--c<4y<f*NLDJ<gEbI)X(4CGuh)fCBv$B7k`0kQyn8-KDeo&B3MO}%aP$}Au zk0Zd;c9gI9A`T(CasF|AK2(kv4?YcqAc1){UGnOzx6l?K>RI5u8<`Gw=Ipp(c`tOq zbCxJzGK3j7vRU;C!J~uqyh%0gLUV<Jbh>8@ZPV&{%dvO4kH&Grlh!wU#NVzBuCxnB zpWrn?;m^oGAX_@pnycYug;%Mi;)|UP%V!s=`uNe*U;WMa{FJorLpsRQJ*?f#%@Km~ zpb99`r7V^FLMwgHAeq<u#nh40mvGC#x*SG{Gn*M;6wl)p^~>#sR1aGv<D7|aS^}Ng z5~ULd3aC3?ZkhY(XpaSVNjO}#7+$(Tk+eJ&;~a!}?=k*cF;41+?xeMEJDTAtd*0%g zmoJRmPY6f87BKU7T_eIPU_#plDDPMru+LjjosJn#*BiJL(|LYiOQuUy5;VQ5LBCW& z6|s~H5Q?OFNJ9OLp`n(P>VT<iWCJ{pytQFcvu8su?qr6A;GTg*kt3S^RtkYX{5ZTh zw04Qptq|stMhmN<Em@n6ps1x6%SREZS!B%QbRle`z%yiQK-%BoCn!;3dX>VOm=|B+ zOBrKF`zuE|wh!DvovBjCxgn=U{ZBty@=sA#<~6m|vcXCg+4_jyb+v!L44d?CGgg8B lCpX{YvpP86Ur*DeW)nMFLI*B<YFue2$jjZwt;Cg_@Hf9^eDweT diff --git a/addons/skin.estuary/media/flags/audiocodec/wmav2.png b/addons/skin.estuary/media/flags/audiocodec/wmav2.png deleted file mode 100644 index 20093c15d56d31d575f039703ac6da7b90d19540..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1378 zcmZY9eKga19Ki8;DB3~tlsn7A&F-xki)#F=5n(K$?5;)Dgj=+3+3q4#4x%Z^rfJng z4_tYcc~~T(Ee{D3rbcJxVR<N;Db!W>+&|oVUgvzy=X=igkI!FkhMx~s8w>%1Kp<@| zPqzagkc!UA9<8aqG7Bn29w5+~tzK>}_-OIe?J4@*-Nqh~z7X=>oHc>wu(B28b$RMr zTyXJvmFm_id&yFO(f+h<yIRG%8?m25-Z0`1OO81S{}6+owj-aY?$-G~(8y}>$7C%F zHtX46ccJh)<^&M%9og`nAr@(roy2ZcX$G5XtOla4wuJ4@sXtZA3sZE-jg!~}&g|K) z<+s!I&;GjCmUd@ULaVYv2lV9W#kCn#rBStV`)&3IJYf62cL0xZqRj$}mPsSW&k@Ag z4HJWzC;^=mA*)%-oApOjUB*_!FQQ^+LQH7i1OZ_Tgut`t%JBU<pJoDMJ^;)vp5+^f z-DdWWM%4-q4~ObzHMb%fySt*3Qmu)QcdS1ld*e7EO@>y%XiH1Ns3yDQOp>@Bso{kX zrIp+@U}KU^m2~-JJYVjHD{6O{&|-&vppE;DkBslD5tufr<t9KMy^}L<;ysJ9#^GnM z&L^<*IlK6oLG^>h75@BwC&5W{z#~U{%P);L7d6_>@)vc#d=S5N>Ps#QIs(h@w2hkb zv^LDG!n68A?o$i5zE#h9lc=dZC4*-k$8eh#fR%Hplor3*wHJz(b(K)8ZXJ;FFvCv& zK(sJ7B4B==t7k={q&eK~;MoN+y1LG}h5^33NwZBBk(Zf|wiB)<E}KGQ%?ZDx6Prl; zUhQ!Q8_|se1C^ihmV6?(KM$_iI2L*8I_{v(%dp2xpNS@`6vqyp{gWXrK39(Kmx$@2 z&e`LK)Cyw!!%AAq?Fn_i2XnixAG{ji6*cFG1BjPxtI@zr6upMS@E<)z;&zD~rU=pA z3V*<Zq9G$zCC7fC?cBLtD|S!r^bM;NKJpyA9wIV`m3gKKpmh!NhxU-XWyB{?xqD}l zUq%8<--c<4y<f*NLDJ<gEbI)X(4CGuh)fCBv$B7k`0kQyn8-KDeo&B3MO}%aP$}Au zk0Zd;c9gI9A`T(CasF|AK2(kv4?YcqAc1){UGnOzx6l?K>RI5u8<`Gw=Ipp(c`tOq zbCxJzGK3j7vRU;C!J~uqyh%0gLUV<Jbh>8@ZPV&{%dvO4kH&Grlh!wU#NVzBuCxnB zpWrn?;m^oGAX_@pnycYug;%Mi;)|UP%V!s=`uNe*U;WMa{FJorLpsRQJ*?f#%@Km~ zpb99`r7V^FLMwgHAeq<u#nh40mvGC#x*SG{Gn*M;6wl)p^~>#sR1aGv<D7|aS^}Ng z5~ULd3aC3?ZkhY(XpaSVNjO}#7+$(Tk+eJ&;~a!}?=k*cF;41+?xeMEJDTAtd*0%g zmoJRmPY6f87BKU7T_eIPU_#plDDPMru+LjjosJn#*BiJL(|LYiOQuUy5;VQ5LBCW& z6|s~H5Q?OFNJ9OLp`n(P>VT<iWCJ{pytQFcvu8su?qr6A;GTg*kt3S^RtkYX{5ZTh zw04Qptq|stMhmN<Em@n6ps1x6%SREZS!B%QbRle`z%yiQK-%BoCn!;3dX>VOm=|B+ zOBrKF`zuE|wh!DvovBjCxgn=U{ZBty@=sA#<~6m|vcXCg+4_jyb+v!L44d?CGgg8B lCpX{YvpP86Ur*DeW)nMFLI*B<YFue2$jjZwt;Cg_@Hf9^eDweT diff --git a/addons/skin.estuary/media/flags/rds/rds.png b/addons/skin.estuary/media/flags/rds/rds.png deleted file mode 100644 index e5de4ccfcf3e7cb0988dea8fbc7f2696617794cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1141 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GY&=~YLn>~)oqM-(k%NR= z{C7|0B`iTqx(S>)oJ&}A9K8+*ZDG-M^u8dprR8RK&KaJjh8>Y<MSgibRu7ofKmR?m z!`hfB<C*w$h6&7=kiilaDul#I%}-5!c|Q5r8O9b-2R;z|uqf)~i6lh<9%FWaFjVzG z9;%^D4~{-Qe{Wy^%LTo9QM_{B9ananS$+M*Gc!qd?S22KyOx{l!*@J9?l$@F%ViY> za}1BY@x530?}`ljyvkGI8y<e$@YLX0#v}HpKLk`VZL2R<KD$zGnYJwZ(cDUbu(DgR z^6IDNl+QI;-`er4aDJlFO54xTv)hWFugW^;(lyokedYX`ikG|2JD!m_dN%&+&8n@h zY~K}ck=DyQ?42Wff370e>&io?RGv=ol(Bu}Sajk<$T`y!S?l%Iub-2DC@g2&F^R{H z0WbHQUpQ?OyY8F>!ztzIHA`2Bh3wOsT@_k<DaQD09+OyYALqtPPk&j|CDg3^^vj~? z)YTh7sm{V@uHQ`Vk$JafLbTHj_uUHZ^JCol^`)IVeY<4pgnF-~B=??Kv*uBEs;;yn zpZ^sPsr=^~b{>8=-{tz^n>Wwc-MOh8V`=m{|JR3>JxBQzt<`PsX5AJlnUFl`#_7cA zf~H3gT0RS36?NW5OEPq!rqD8(&TZHBY?LTm65MU%d;Co3$&8-~xh*pH9Tsl=c8&K| zPt?O5Cg)^=Zmx=1xOnrsN5+loPpxC$cK7|>-Mca`=D%O_{MeTB%kMs~xc>g)PVegb zPZsRFw<SYoitt)a?ck>i%J<qOp0wGeeB9kH=v8Qb)SVRd&i^knC5ql`xo~Ku;v{v` zZNImk^4I#htTK1w+5fjxSE&9zQ}ynS)YtYWGTyK6diI}x@{Ygzgypis$09vz-Of&k z`s_A8Eb3*;rj89ps&8)0UnOx@-&1P$qVEx{#fFEyJ>LFGsobnwBC*q0oA-T>!tp1s zM0VS5l{4JBe45Pidqqd4si{AiA+@wr`Ek23fBBO|%X}|t#adYi1g@=l&Gz`jP5U^T zfVD@?3Er{GzPIV^j(z&J>EC~bl+T}iO=(MX-=1q+{W2fVt>yiC<IURjYA##OA3kMc zBx+^N_;kU}$tkyXdlpNI)-!u9a&p&ZjANNFi_NO|#ECW0Oos*X#NRElKAl?=eofl) z)l7G*fWGA9u#3;DH_a5!`jcUqa_N@uYkl{tmCfAR`wO1;oDeo``lnQy_>^_`wL?aQ zT`}b`(fyn2w@tA0E|xxTB`y2?X1e7A`O^pOf3w)Ha^^b0hzc8I5by!x-T%RpA1WCZ xGbpe_;2-uh5#<AC&@_RJKxl)pK0N-xJmq3N{~u-MpTOdT!PC{xWt~$(69B|M5{Cc) diff --git a/addons/skin.estuary/media/flags/videocodec/av1.png b/addons/skin.estuary/media/flags/videocodec/av1.png deleted file mode 100644 index f594b9ea9cb3fd956eb18fd1662f254401139b3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1025 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`Pk6dGhE&{oJLhicVgmuU zcH7$wx)+*)nWCGvu|ySkd3bTCwg|lu^5HDwT*&fGmA!4NO3V3qrjy=%?Vfw$r{Q(u z`D~MtL>R2?D>xY}vA_w;s4(d1@~?|yB4)|6PG!!}Zjb>%funh;sz;radP4XXtYs)d zg9_{@@VK7kDT}*(=R&V%o^?Idus-TkYSpo;yFckZ*sXkga)!+J>02)EZBD=1GqL&U z&hIrx*1PN~x)QX~ed?9fMIAE~EYqx>uL_=!apI#&&+@(uw$cuMzt$XWtDTn8{&TN& z&zvUnbS}@anHt-!^nKkGc+%5U?#YeHdl9Q;(?c20cxlYtf9$N*aYa|tpvi}GCOBX3 zyA*9Lwbbu*z~UtB-QNwY_E*Q7w_SSna9NPP+2OrkPHcR?t8nsn*UJU1L3<aLZf!j! z?Urq+d!=$BqvbaVx3i1Xwp`n`VF@?K(to=QH6-snEl;XV-E!AN=;Yc!`(o2m5i5_o zKi|CW@+|%1UF$QCUj0@1`R~nVzdvd|*!}M5wxT(gSIV4?|1Ge0y@BJxEgP@#)=P-K zTr$Cl>;0W6+dhSqq`i1I>%hZ@o|!jNOag-@9*#&#TmH$xLu2iGvn5lj=4Bgo$?WD| z+`Cn53uCfgyoPC?RI^Or)!s{08+JDsPM<eLZm#7swe%+)dsphT2kf<4a$9}ruCpl- z=9eZUSDw3a)#OgHUbx2DJu8<ewr@Ok`nc1|nX6jQ8OC}QpWpN3gRD#K-DBH4GbdNy z^>E~?Jt2L1#`>EQUbA;wO-_~Yb>{o*e{EaVw!qV$c=DH(D%V-A@}2(d>bw9eoh<in zi6JUa=3hA$Y#ujv^}Q><?g@HXUk#Wx`J!UDWs<$N^?c*MU)KDJvHh-<yteN7<X!Rk ze>yF+chBAxXr(Um?XJhmoy)d_zg7QjAXWd&;F|7g-@dPz?Xl~_SS%m$ELQuW@ax>( zTTYrqE_%nOW$QZWameT#{;rsAnZ&o`vufS+hO5(i7n<1g$Q>`W<5==v_^zDOx{F-a zwd>7yMYdnuar;zZvJuB`?WuixCLR)aW3zYt?*%CaC)PLEN9M>+Sp2=`{xdBR#iJSg zN?ENAyBaT7&-uCCf5MY6G1QEJnpYH(uB~U0uAI0+hT#Mw1o|&XFlGwjbHIQMPtak* bn;*<CTMHl7xE@jmW^D#fS3j3^P6<r_dnwD{ diff --git a/addons/skin.estuary/media/flags/videocodec/avc1.png b/addons/skin.estuary/media/flags/videocodec/avc1.png deleted file mode 100644 index 78da5d8936195dc173566238757d225eac4fcf05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1262 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GwtBiahE&{oJ3BY=k%Ne9 zzG?ucqtiqNAqS?Z3_@Kj6F@AF3>6X1-S%8h&QAF0;gp$q{^wCWmDV#4&(1ym<mG&Z zSCvkD4p<<=6I9r+UzPpXA4lN{%rGeD*QdVj_WGytMGR<AfxQTf6xfmAaoHI!-+roC z=lUS@y_R1;pVZ44Hrf8sd*{eaf5jeW8n$p@+p~JFwINx>i_d>s7WrwiqI*l$jpV~K zmIoXSIep~yTglqKs5=P;(`T7goj>u=@XEEH-#vCasQ%btW0x_(=KZxR&HGNR?%Eil zU)UBTENfM>X>IVt35y?Z_nu@nizRO7HH(!OW{SU6PG8ei+ZDbxs#eQ-rJ}ocj;+OK z84r<d$JTtAWqI9OSl{u7Ro%SxHok7H-+jCeTXC(mHJz|HSt3`+tN)d|^ULF@mp%md z#G9H$SsuQB`O2Q<bKQFFFLz%H?^1N<{uX|J!<_&r?H>;fUa8Gl9G87vwRM`5msM85 z)UZogIoX$aib^;Z7aM<wb<VWtxi$4-;9`#lGOI5v+bFfJhW(|IebE-a#Y=ysJvh{P z=vk{{LI(F<_CqVyYCL?LzxC6tj3g7+zP7|Ke}fvHM_EjE^V5jUm^Y{9jzN~T*NYc= zH#DDq{&V7@&~Mv!t4sEMtX%kIXQ0H^=l-m>w)Jc+6A~;c;amLN)bZ*`4)v}O4O{;6 zUmI>H2YWw^Zj{mW{_rnr!rKGSS}yHsU6wm_XQIiq-wu89s_v#CWmPMdwd@IaWq#~% z;uT++^YKP26^@@hyrQ=)Y-XLC##jBHI-3;MUGmy7xk<*}flu1)?W9e4n=*yGl9@uz zZ+%&<bo}lLy;<_MhtDq&U%hcwlMKI7`&#~guH09r1ZZqMd1%?h*C7{O`RbD!jnp-s zyby8wn<D6?{4zi)=vmdvIh*BKEcbc#{g|~x>POnbl4(Cmmqk5kyOn<8<J+Pg`wNbo z<Xrlk@s|3xo?CYAm+yyiUA8TZR5*UUWsAwfk_Xp2D<q~S?G0vn$v^S%wv|O3?U%G> z=a_i51pe=+F14DlSn!?Rr-GEw!vVX>Lp<dR=d)QAZQ)t`(D4j+f0@$FnJcySyuK_K zm{rg&W>sWz;N0(x@7LAFT1+v!o$|s<e7dmZKF7X4Pg6n@KRIQ_UC}x7MfmdLJ)S#W zPf&7SZ7}OrXR7$TzX#&{qzxY@ZrP{i$$mClZQ+-@MJGNkO*FZh*|GJ;Yvr_eArHSt zmfgxM4a!>MWbD2p?>6T|W$9|;l3k6ls$F{mr1%%V3w#aCyt(ZrW$f=}R=;(wR<OL3 zyXr$KUwgQ;*XP~hwtKJM`L4Ep(V6Hs{_cH!s%H7>VOh=zvG<pp{J!VHjoD6bQZ{85 zN~;u=c*unE+)2GQOWZg{v31$AMe^2r*Z+tQdi-5x(VokE*Vv;^F*S<7^0(y#W=jSP z2owY&tCNiN%<vMT;rIk*OqE~}m|-xw&iFs0Y3)3DpRmk_z;cYi)78&qol`;+05Hxr ATmS$7 diff --git a/addons/skin.estuary/media/flags/videocodec/bluray.png b/addons/skin.estuary/media/flags/videocodec/bluray.png deleted file mode 100644 index b8fe922c6dcbc1910d704af498eaaf19382436b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1313 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GUU<4VhE&{oJLhicDhCla z`}bbV_k=t+m$B$JPFbLuz!~AB!RU2CD2X$uY1)C>6TET>B4>moAKlxh(qUwEyZGFn z7fS>hX4~&!ZIHo)3?~>-;r^-O&jZb-*~tqlu}=tNumr&bk;PM|X^GWwvQA|7&_*`| z)rgN0+qC}OpH`A0zo+6tTlVjXMXHm`N<=0u_FNFLk*hYtD_yaD_ENrCmt)UQ4?Fi} z%W4<iXm6jXuY4_IzwIdsS=~L=X>!1_t}TmBduZQJsk(epMO=&j$%>xv9Fv9b9QvX* zZJf%k(EiZHT=94<$EnKT>rbLW?tL$O{8>(`zx&kx*Vj2kG)=$PYOOlDyI8u9OD{(E zZ=v;kAHL}HH?|pPecX>Oi!N4nTh*M@`s~llBw1~Dqsc0Fh5s8D|6{XUCphy}a)srx zTi28hu9WeX)e;I(K6vTGl&wAcoj!LJKiP3j^!fQ0x4OlA=b8qFcr#?L)n2-#*R->8 z+l9i`rOKxi+;tn=t!6FR^hqIa$1eNTrOE5ceYae6<JO%MoA=o9MThj|pxr4K!XtL% ziG8hmeQ4nfbGhxh?_*~zK6rJT`_o!^^KG93GW}m!DC=}B(s<MzZe!IicJ$4o*IjZy z&RM^cKc0~q&wcUm_UCsCO)k4kc6;SLhf{OqncrdV*2VUdPDQkBGF}?qwfFevy30Dg zd2z>oW<2vy4h`M3aJpn|+|;#vFPWA`eK*-KJNvcGyw_I?O?G-OS6#{$+^V)uN7j(t z@=DCqzf!D6mOVP(&bRf_T%kL0yNw!kyamrW->&)L@~S@S=8Ud*w<!-Z=AVn#3W~W_ zIz7$NSXl9S??!K<Kistw|G)9!;4^uh=rH?!E}Lq|jh$v*BGU_%uB;2{nK$9_w?8^( zt0z8|+j_=Fe%X^ZVxo_O`nLMHtv=KK^w>t8)Z+$=Zkb*2*mCWcm$~{@%}pnkFEN^S z!jLV<SmAD{*Xz^2uluQ~=}n#f{Oz9~7Cou+9;dVLXD4Q?`?}8cxyjU-+m7`uK5;Qo zZqaXtPbM8Q#*+o|UPehPGtKY1xbZ~d^fi8K8Lyx7I42z|@%7%VJ%wj8y*J*~32v2% zy}F_M+ibu2$MP3>IdMr_8~&Q{JK|cJja~G*n=;c=PF&QT>tZ#F@0>Mj%<pG;|BrVC zWG+p4qaVlebcO1r^mgC6uPU29zvzxuj@<M=Y8$_m&dt>U6U!>L&DXzq-dgA7?m~N` z6?25`9?1R=_5Kz=e}(Jw^H-kcI{vLq&MPhKOS>vnTK4SynfcpmWQtO%_B-;vTw?BC zI_bsMP5qDleR?PTHsa-4=kn)uzIC<6ns;^{n{%;RdeOON+pArBw|OdC|J}0ktjC>S zlD$2veoj?<d#CW;chkH5b6!RK`}=+2<!8Eo-!#jdKUW#-^yz_qmvZ}enbSYEReX@^ zn^yOX{c+o~IlsQey`AjJr$4D{`caJ{p7VFstVlV*IQ>yu`F=GAN2{DW7VoR-WfN}g zSvvpb8)KE_MenoTJ@$VnXgN=^ZIgo|7jn_z@AR7|^WbckCkzgJ5Gc{W*(|6~#DEDE f*wLYl?0<&V(~rMCZB%ao7JUq!u6{1-oD!M<jHPE< diff --git a/addons/skin.estuary/media/flags/videocodec/div3.png b/addons/skin.estuary/media/flags/videocodec/div3.png deleted file mode 100644 index 65a9a6517cb73a3aa0212fe108d78b06bddedf5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1213 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GT0C7GLn>~)o%6bKk%NfC z`rk_#qY}ImybHV)R3`{+V9{-yBH-Pjn!y>=H0{7YkDl^`xQuHbERNgTxhWLv`F_q| zva2#fSzRF)gC!O?ff*GpJ2m-htlG45{j3w2J+vESK+vG;p4Wn|*3Jt8mJM5&PB5ZE zh#DAqSbh3rlS?P!j8qCXZt!Efl+Ty17LgHp*6rM;skbgGO?`a*b5z~^0-h>vrSqG< zZaJG`f8n#_u5-O_7fw8A`{;&wYccoIi$?ojYJ4&C^L><Axu-h5-(vE#^UG(48Ld>B z>{fL7n|V<{bcFTosgvq#>%Y25TspO#?eN?;R$E=7*`rUKf9kvU*IQr5jV~YNKaaST zoAp!q`9ZZ8CznpWB>18KSbT!F{_pkv*UM!({dC>4yZ<HxdmSmAw@BuaM=<N^u1%V= zKfUAL_Fb-b+H$FiuV%`YzeIYz+Za8LS$ZserC|7^k0%Z9HkByPUeWZ?j&D=Y`g=X+ zwZ7JvZpc#JrZzL<$pkgStp5MsPuoWIXS~o%y*APL$(p2gkBh%T<Ig$#y!mj?9y_bu z+kRgwU-x%m-f`2=+$`l?1~qm9+q7mpZ=800<M}&=59Y1>$Ys2>MEFLOrrEhEMwOQ5 zvfe0u<}17E)qhB_-FN5dr|Uy^_aFCQ-}kKK@!JLS^p%s{(<H-vdrsed>S`OmI;doe zW@=v1lJX@BoMQ8Cd%W;lI<16DGnKFCi=@Q;3ZZWMyqcQb+xBdi{d{uvE}7>qPAnAm zzSJc<bF;+N>Z+F=J0-uLoIP3Qp~mK7m1&29dv<-FdZ%K)xTT!SE$=Des_sd>%KJ<L zOP{QmkbFXA?~@CK3paEGRX@?tt(}vay@SWnO~$h1jYgHoVx^h8s)MCZEjeKrV&uNv zOvn9~rQW1f9|g<rT-bgmEGsAZ%F5JNMLMB(IwmfTp7N^d@KwXO?aP>6<-NUR_^3<9 zUe0{o#ANZGb8}Z;(`YVK-h3yex@<+WO!LAGabIpMGMN}7x;Xv0*qJFs|9lerXZSYB zlrIsneIcuT{N{@bag$G}87+;u{d#ucV;|kgbKgv^y51c!FE`~{sX=DJtb}T<ggl=R zpP6rr=jgrJ{mV7K`tD<kh*y*6E|%E3ROEJ?`^>0in(m73OOGuqzOKAa<7<Dx8fNZk zVaMj)YufT@bEazksb^QB#Aa4Z*PC+f=JID|etUyYJq@%;;j{4;u39(s?*rW(>z4LJ zeBZP$nfFcgk&6MvHglrp^?q!#+ru{blvNS$8o$iS1U2o<r-~C7bFE2T-DErOb=0=< z)5|OSS-qcqT9son@o{F0V4~=stL^z4W2@!apT`;HcTc@Ft-gNo+)u{GLce{e%{*K^ z_vicsgUi*>tP0D94KfVKxwKi;MtIFb6+ue|SZ=RtV4W+dP{e=<71+_?nZEiRJ6>$5 U&Y9u~ES4BNUHx3vIVCg!0JqyQXaE2J diff --git a/addons/skin.estuary/media/flags/videocodec/divx.png b/addons/skin.estuary/media/flags/videocodec/divx.png deleted file mode 100644 index 65a9a6517cb73a3aa0212fe108d78b06bddedf5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1213 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GT0C7GLn>~)o%6bKk%NfC z`rk_#qY}ImybHV)R3`{+V9{-yBH-Pjn!y>=H0{7YkDl^`xQuHbERNgTxhWLv`F_q| zva2#fSzRF)gC!O?ff*GpJ2m-htlG45{j3w2J+vESK+vG;p4Wn|*3Jt8mJM5&PB5ZE zh#DAqSbh3rlS?P!j8qCXZt!Efl+Ty17LgHp*6rM;skbgGO?`a*b5z~^0-h>vrSqG< zZaJG`f8n#_u5-O_7fw8A`{;&wYccoIi$?ojYJ4&C^L><Axu-h5-(vE#^UG(48Ld>B z>{fL7n|V<{bcFTosgvq#>%Y25TspO#?eN?;R$E=7*`rUKf9kvU*IQr5jV~YNKaaST zoAp!q`9ZZ8CznpWB>18KSbT!F{_pkv*UM!({dC>4yZ<HxdmSmAw@BuaM=<N^u1%V= zKfUAL_Fb-b+H$FiuV%`YzeIYz+Za8LS$ZserC|7^k0%Z9HkByPUeWZ?j&D=Y`g=X+ zwZ7JvZpc#JrZzL<$pkgStp5MsPuoWIXS~o%y*APL$(p2gkBh%T<Ig$#y!mj?9y_bu z+kRgwU-x%m-f`2=+$`l?1~qm9+q7mpZ=800<M}&=59Y1>$Ys2>MEFLOrrEhEMwOQ5 zvfe0u<}17E)qhB_-FN5dr|Uy^_aFCQ-}kKK@!JLS^p%s{(<H-vdrsed>S`OmI;doe zW@=v1lJX@BoMQ8Cd%W;lI<16DGnKFCi=@Q;3ZZWMyqcQb+xBdi{d{uvE}7>qPAnAm zzSJc<bF;+N>Z+F=J0-uLoIP3Qp~mK7m1&29dv<-FdZ%K)xTT!SE$=Des_sd>%KJ<L zOP{QmkbFXA?~@CK3paEGRX@?tt(}vay@SWnO~$h1jYgHoVx^h8s)MCZEjeKrV&uNv zOvn9~rQW1f9|g<rT-bgmEGsAZ%F5JNMLMB(IwmfTp7N^d@KwXO?aP>6<-NUR_^3<9 zUe0{o#ANZGb8}Z;(`YVK-h3yex@<+WO!LAGabIpMGMN}7x;Xv0*qJFs|9lerXZSYB zlrIsneIcuT{N{@bag$G}87+;u{d#ucV;|kgbKgv^y51c!FE`~{sX=DJtb}T<ggl=R zpP6rr=jgrJ{mV7K`tD<kh*y*6E|%E3ROEJ?`^>0in(m73OOGuqzOKAa<7<Dx8fNZk zVaMj)YufT@bEazksb^QB#Aa4Z*PC+f=JID|etUyYJq@%;;j{4;u39(s?*rW(>z4LJ zeBZP$nfFcgk&6MvHglrp^?q!#+ru{blvNS$8o$iS1U2o<r-~C7bFE2T-DErOb=0=< z)5|OSS-qcqT9son@o{F0V4~=stL^z4W2@!apT`;HcTc@Ft-gNo+)u{GLce{e%{*K^ z_vicsgUi*>tP0D94KfVKxwKi;MtIFb6+ue|SZ=RtV4W+dP{e=<71+_?nZEiRJ6>$5 U&Y9u~ES4BNUHx3vIVCg!0JqyQXaE2J diff --git a/addons/skin.estuary/media/flags/videocodec/dvd.png b/addons/skin.estuary/media/flags/videocodec/dvd.png deleted file mode 100644 index 9e9bd97ab0a30e3e214c4c2a002c130f3bac8a47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1002 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`H+s4_hE&{oJNIqrDhC0F z<K>qabT2gNP7vC_62zo=aEgLzi_j7lT}ST@)em(~Slatj1I5L@#Qk5OW0a`BdGqRd zYrGg=Jd>Z!Fo78pGFYO*2a%PpYo_W?+b5@O#BQ*Lp$G&ID5Ysn(-NE4GU*H>k9LC$ zDr7jphyovWUix&yOKaiB7r|HGDI9Fr%v!Xf&%&}Z;bC}`^5$xu#{p_{-IuTOwR(`_ z>Hhn;(v0U*-`x1Wr|3`F`CX+Y0k`w*Z^v!rpX;(g!ZIwi+-m2hS&5r|FP^YyqyDtg z$5VTs^oH+KUAOIi^!tEx-S#Z&>Aj0No|UWa{#|)-rm`uI%<^0D7ku7LytRGvob1)N zj-69Vi1?`1J|*Z~R=x9`8`W=j%{O{sd)@!)rza*&t|vaO3BJkyGvs_(Z>8t5XG(?V zcdNYGaVC?+Cw1>9)p)PQwT^rfmpr%KVzKiw$Ba`|GGTSkRjZ!x_juknna>eAJO8<$ z!J3Mvi{9NyYdgXF^I+_~4Xy_^JenD!$HvNK8Evh*cH6mff48%O3hm$T1g;R}E)JYk z@~roF_r*k@P<mgMwUOA;j~dNgx=c@IOq5A{Jnvk{a#MRX?x`|6rJ7>g8f4~Ke!s^2 z?2e#xQO|9T35ScXChp5G4V<-Y+WvKOqkF^{i_W<9nJt?dWP2uMF;KZ2XZ7xrAyalq zhvWb)aBsS|?xJgEOzGlzx1RBrXEey1J|lD2HS>`{{shakGq(&@zR~iUd~Ly}3_~jw zrY9fVmb|;Qc&P@js?bD_(sVOVmJ<&(^Vfv-8JvyK+j{5WCJ9-fg%yg1A>v;bnXDIG zy76#QNd!<)s>em|$Y<*=R+stN<?S{F`lV*xrr`5-)lp)r#5!ce79Uo2=<B?CW3^{W z-p=gL_nb<*I3^??ih8`n>Y0Z8v~M$0zn1*rNIQ9Za;BiSMrLm4Cyn>l4t-Lpe4X7g z-BWpn>gj(+XU~1T{z}f$$NRqR`9HlN+2z4+uYEV{8Ej6c6&x4Y%DQUvV$;rOU}Vqi z@#&KGK5*ybsTXg7LBMRc_HNP6)yD1Oiyyl<oMpTs^MOTy9UFwCb%vq_@i+Hbw0C-> t@G(4rLjE-~dKhLBk$o5}A3XiRZzyEce?KEq4VZ@+JYD@<);T3K0RY+qww(X~ diff --git a/addons/skin.estuary/media/flags/videocodec/dx50.png b/addons/skin.estuary/media/flags/videocodec/dx50.png deleted file mode 100644 index 65a9a6517cb73a3aa0212fe108d78b06bddedf5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1213 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GT0C7GLn>~)o%6bKk%NfC z`rk_#qY}ImybHV)R3`{+V9{-yBH-Pjn!y>=H0{7YkDl^`xQuHbERNgTxhWLv`F_q| zva2#fSzRF)gC!O?ff*GpJ2m-htlG45{j3w2J+vESK+vG;p4Wn|*3Jt8mJM5&PB5ZE zh#DAqSbh3rlS?P!j8qCXZt!Efl+Ty17LgHp*6rM;skbgGO?`a*b5z~^0-h>vrSqG< zZaJG`f8n#_u5-O_7fw8A`{;&wYccoIi$?ojYJ4&C^L><Axu-h5-(vE#^UG(48Ld>B z>{fL7n|V<{bcFTosgvq#>%Y25TspO#?eN?;R$E=7*`rUKf9kvU*IQr5jV~YNKaaST zoAp!q`9ZZ8CznpWB>18KSbT!F{_pkv*UM!({dC>4yZ<HxdmSmAw@BuaM=<N^u1%V= zKfUAL_Fb-b+H$FiuV%`YzeIYz+Za8LS$ZserC|7^k0%Z9HkByPUeWZ?j&D=Y`g=X+ zwZ7JvZpc#JrZzL<$pkgStp5MsPuoWIXS~o%y*APL$(p2gkBh%T<Ig$#y!mj?9y_bu z+kRgwU-x%m-f`2=+$`l?1~qm9+q7mpZ=800<M}&=59Y1>$Ys2>MEFLOrrEhEMwOQ5 zvfe0u<}17E)qhB_-FN5dr|Uy^_aFCQ-}kKK@!JLS^p%s{(<H-vdrsed>S`OmI;doe zW@=v1lJX@BoMQ8Cd%W;lI<16DGnKFCi=@Q;3ZZWMyqcQb+xBdi{d{uvE}7>qPAnAm zzSJc<bF;+N>Z+F=J0-uLoIP3Qp~mK7m1&29dv<-FdZ%K)xTT!SE$=Des_sd>%KJ<L zOP{QmkbFXA?~@CK3paEGRX@?tt(}vay@SWnO~$h1jYgHoVx^h8s)MCZEjeKrV&uNv zOvn9~rQW1f9|g<rT-bgmEGsAZ%F5JNMLMB(IwmfTp7N^d@KwXO?aP>6<-NUR_^3<9 zUe0{o#ANZGb8}Z;(`YVK-h3yex@<+WO!LAGabIpMGMN}7x;Xv0*qJFs|9lerXZSYB zlrIsneIcuT{N{@bag$G}87+;u{d#ucV;|kgbKgv^y51c!FE`~{sX=DJtb}T<ggl=R zpP6rr=jgrJ{mV7K`tD<kh*y*6E|%E3ROEJ?`^>0in(m73OOGuqzOKAa<7<Dx8fNZk zVaMj)YufT@bEazksb^QB#Aa4Z*PC+f=JID|etUyYJq@%;;j{4;u39(s?*rW(>z4LJ zeBZP$nfFcgk&6MvHglrp^?q!#+ru{blvNS$8o$iS1U2o<r-~C7bFE2T-DErOb=0=< z)5|OSS-qcqT9son@o{F0V4~=stL^z4W2@!apT`;HcTc@Ft-gNo+)u{GLce{e%{*K^ z_vicsgUi*>tP0D94KfVKxwKi;MtIFb6+ue|SZ=RtV4W+dP{e=<71+_?nZEiRJ6>$5 U&Y9u~ES4BNUHx3vIVCg!0JqyQXaE2J diff --git a/addons/skin.estuary/media/flags/videocodec/flv.png b/addons/skin.estuary/media/flags/videocodec/flv.png deleted file mode 100644 index 8b1b5775d50545e11951ef96b6c273a9d605433e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 744 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`)_b}*hE&{od-r1DEe8>| z#EFH>TO7E7D9cfIg3u!&CvL5SQxc{#sI~~5XqMe5=ba?z)wFKkVQ%Iq#!EA+|6k2& zJGztM*3V}G4KfPsCm4&6C<nd?%ug6Bp_IVX<=4XRMp)U4X|Zn+1<M$8-aIu`q+4N5 zC|`y)s&2S8u<i<lb2Iy=Uo*Tt&sKic+PLo2W9E@!>(6#DTPAJJp0~DJEBW`Pi0wV9 z?gf@zi_T1X>}30P{y&j-Det;#&+lG7`+K?8t7k3iyM5o^yT5(Mib9p+i%)*Esd}q1 zdDn*d-ghsAeJZh5Q{QhtB`)aXM495#vZwFe2~>UcX4(JECkwMR);_xwa$=&i*JaMP zyOLLjE-~=xt9jMKnY&?^W1r%}Evq-?D7OX{@$fDU&A!Va>UPr5WXiPS46P>;VqWG) zUwKRDCIh8=#Jx`UNg3xxg>MV)^0X<sQPy`et?tx9$&af)&pqtY7jdjI%(AWR(w49# zxoUoQ6^?5xG)W7dd3Dm?+ZqeEWVJ-pvYao;iss*;*&y>jpzrzq9p9}47ykLNL@N#G zfxPhducuz}4z`TjdAnI=^GVtF+_|$YzV}ReTxaPwKg<0B(9MYpxA+Aef3Ce)z39x` zFJ3RtSDem$vO|ALoZ8XZD-x&3Bwy*<P}(f>y*;RCf$<bJ-o;OT9PXdD^mJ%g@=aG8 zohLi&r?l}bej=eg<z3RhBO2WK_h+Z)Gavi%yn9`MR9v6yYkSM3`oE;t?})KHe~T*x zCGtVB2#;X7?$_l_b7fVg!i0YC>-e}WKu$W~goK&~7%UB}>zH?5yk_+%$}SR^f*Cwr L{an^LB{Ts5r8Y!Y diff --git a/addons/skin.estuary/media/flags/videocodec/h264.png b/addons/skin.estuary/media/flags/videocodec/h264.png deleted file mode 100644 index 64cfa04ec9114482e7b23bee991a20a024ab5eea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1160 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>G{5@S9Ln>~)o&7fUlYxl) z|Goz0DS|015{&K#1QS>!4g&cHI1HL(5B4pPpYUy&?=6v9xnSGp!v9`)XYrYvo4>rf zo8iwLMRo-&kf8__9=Jb=Ir-lL&J&C<xOnp8zW=w^Kb5s)K!X#QEx~94GZK7kZ(6fB z_W4Biv-b9)v-ozoFI)QT>dl~qG46BOV-i=U`{w<K&)xd(PstS~%b>~1m&3f@o4ebt z?AUdz+3(r<$Sq;TvS;p2P&TxbD>2yrLv5*C>6^XBT+~x9uH~8AF4K8(;>#y1Caw-x zHg)mwHRe@se4m{BD)Kj_T0Ke8J*jBUmj`ddZs*p{-F9u;ubOK*zjnT<JTY;OjJ7TN z`rNNCPATSW6BJJU6UkMiqV8V%r&})i=Yi-WwRhJj+6k7PckX-gWW(M4AC}B5eUq{J zaku`*&J5$*4nWZr%O@^<m%qy4#+t<CXB%dnxHrdruCk-$rdQXizxjpFUme-K|F+TI zds8}YNnVQF{Oacf<(*dUJ}<uWYk&1wenH7faO?9}p@=6Eb7h40hV9S4U;ElI-9zcs zyzeW-Ki8Zvl(Kwdvi<H)vuummQJyPfpU>)<otrb~xpUvd6N$@by{$RqCMvIgtD;?U zcJ38jm%jY03Z3|=i)H`(U76Jp#O?p*gZQ-x$st8I9^ChHd#m%*X`NG+$YJiYA#CdV z%-EEUcRbnfddpkAykor4_aid1+oa1m&rLfW`*6bJXsNn4k#_N+m51II?RdRqU*(C} zKlfQa*^%zQ`pn^H2EGp`B&%>{E^Chb{6X~U+X;`e`~Ux5mumAwqE%*{)LWM;XJx#6 zqn-Owx?`?CRGyNna6IEl!PSbOcebm}SvZ|8{Oa@Eh;tj~n)fnKJmUH~`IgNP4LW>E zFv~aAZugc&J6A5BulRcRiI3->c7y_jtyXEapLLp$TvBvob>d@(6{cSWr#BvYC+*lb z@uZ;(uQa=prIW4k&T9_S*2c!?^%dn7$Ho5ak<mQyaMi{Cv$qx|yoz+(_;PvuK4+&{ zGi0uRzj$lC=3KUw5APi{|Gr_j&9dfs{FZete9^ngc0WjnYwVGI`8GN$ud`;+-kovx z8CqpLPdxNfdwuZ0s*A40%crj`7dy4}a_Z%Wx2BvsJbThaWlhUFOE+Z)?x}wm+o<v2 znWo*jsEXCgPfV<Mvg7}oZQIZBTE`ZdEOwu)xZXwSIRELy`j=l#+j3YxJmoK5YkN3o zUCjd*5zBoCZn3=Hr~2DU?9HC=jo}ge8O}RCCA^Px@r`vpAMU2M0ha54bfXLqHDEx9 z`1aF`^~_Hgps=Cv1S6(OkO%{kf#58?e<r`skL~hzzn|O=EMXWtUHx3vIVCg!0C{61 AH2?qr diff --git a/addons/skin.estuary/media/flags/videocodec/hddvd.png b/addons/skin.estuary/media/flags/videocodec/hddvd.png deleted file mode 100644 index 4a170a91b6719afb6109bfe811e68e5a2a71b839..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1100 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GggspxLn>~)o&7rWm4k?z zz4rm31uVLbKxzYvhNIU6p(QLDjNTW3)cgL`Cpph3_n*{B`Sx$>))S{@8gDi(o}prX zpQ)g@S*8ICWH`Zy3R6!u8nVtpDE;vK$&Q8h*G<cR!hi-H_@01~10NFXyS6Yqiv9eZ zmk*!62;S@v9kZtNO4xi)>s8fr_<ig9?tNX6Y;w!R`tIDPbF{A~J^3L!sn=}rDZ#$Y zSG-^6sAet<KmK!Pk*-hQ|GD4Re4qIA!_k>9PXDQs-|IGaqsi)LO))#;7Rpq=^{<;9 z9{gF~{}x|f?tY8>S>ea~)59~0mOGd3xf=W}W_<wP_t0rkA+PtU*nXc=)PAh;!aA#a zFDxZqyLvwtcz$ZDgr&;M(C&lfS3X#j23B3siI`HC`RI&!_&V2<7j-z2osE}lU1;%b z`vu##dTzzfqGrgPU1?>umOpc?z2EIcS7Ljfo!M$(sb)3f>yK@p|4vcroBQd#?SHjp zzY4?_TNdm3^<5~LyKU<~i)|XQ*`l!`e%fs@JH<4PyU2$wyq>l`u?Hxz_iYHs6bH*$ z)x5Flw@nI@TwliIH(CHaAJlhY%G@BndYR}c@4iaNT)KHPcoI;*W_#@C-Rq95d~{>c zmy#dOM>EdF=zH~j<C%Q8?B9%{mPajYXYajTr~3`8bncbi|Ni@nACI4MZU3C-GmWn+ zJ>8Jrujy{lw_d74pJiv!UJc7{6PHivJ|KIweAU|Tl5ZCLaFvleUh04Q)S)otcmKs- z-g0oPYO58An?E;>uY68H;HzLa<*kgCOuIS0>N!_g-YGofnz=^8_b%HLi-UF(R?W}# zIeX7(a_J?DhnGG-s7MbvS?tQE|CQl~+4{>OOFmjG|Ln3mwi6T%?eCxNzOpt~__*Eg z8?RQn8qPAl&h&Hx^MzVx%hEY%fmInRCavZ@@lb>5@9JM4u5Ig0K7V`m^=c=5d0@mD zST4IY^LqTRJKirJ%bdF>-p`x5E~b0Z;+wI5uRZuum=kzwoqEyJs0lLP)~?t#U-?^R z?sZ^b_lkp}#i?)SvM4jQ1@+~<g3EtOI{vXcKlQcfDZ%s;D%NguF7Lg0V|oEF`2G6) z*G{T^Fj=op*X!|rZJ=pWWTf9tt}~yd=eK?R`{Jinj72(UUrAJ_#IIkP{XVp2ZmG|u zz{f{s&RC^f*9uIa4Kf8U71+^XkpepiF~H%0sV5mzk@A`O69%Mwgp{Yy^Ib#sGkKn! Wje3$Qb;`g}g2B_(&t;ucLK6Ut8Ucy` diff --git a/addons/skin.estuary/media/flags/videocodec/hdmv.png b/addons/skin.estuary/media/flags/videocodec/hdmv.png deleted file mode 100644 index b8fe922c6dcbc1910d704af498eaaf19382436b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1313 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GUU<4VhE&{oJLhicDhCla z`}bbV_k=t+m$B$JPFbLuz!~AB!RU2CD2X$uY1)C>6TET>B4>moAKlxh(qUwEyZGFn z7fS>hX4~&!ZIHo)3?~>-;r^-O&jZb-*~tqlu}=tNumr&bk;PM|X^GWwvQA|7&_*`| z)rgN0+qC}OpH`A0zo+6tTlVjXMXHm`N<=0u_FNFLk*hYtD_yaD_ENrCmt)UQ4?Fi} z%W4<iXm6jXuY4_IzwIdsS=~L=X>!1_t}TmBduZQJsk(epMO=&j$%>xv9Fv9b9QvX* zZJf%k(EiZHT=94<$EnKT>rbLW?tL$O{8>(`zx&kx*Vj2kG)=$PYOOlDyI8u9OD{(E zZ=v;kAHL}HH?|pPecX>Oi!N4nTh*M@`s~llBw1~Dqsc0Fh5s8D|6{XUCphy}a)srx zTi28hu9WeX)e;I(K6vTGl&wAcoj!LJKiP3j^!fQ0x4OlA=b8qFcr#?L)n2-#*R->8 z+l9i`rOKxi+;tn=t!6FR^hqIa$1eNTrOE5ceYae6<JO%MoA=o9MThj|pxr4K!XtL% ziG8hmeQ4nfbGhxh?_*~zK6rJT`_o!^^KG93GW}m!DC=}B(s<MzZe!IicJ$4o*IjZy z&RM^cKc0~q&wcUm_UCsCO)k4kc6;SLhf{OqncrdV*2VUdPDQkBGF}?qwfFevy30Dg zd2z>oW<2vy4h`M3aJpn|+|;#vFPWA`eK*-KJNvcGyw_I?O?G-OS6#{$+^V)uN7j(t z@=DCqzf!D6mOVP(&bRf_T%kL0yNw!kyamrW->&)L@~S@S=8Ud*w<!-Z=AVn#3W~W_ zIz7$NSXl9S??!K<Kistw|G)9!;4^uh=rH?!E}Lq|jh$v*BGU_%uB;2{nK$9_w?8^( zt0z8|+j_=Fe%X^ZVxo_O`nLMHtv=KK^w>t8)Z+$=Zkb*2*mCWcm$~{@%}pnkFEN^S z!jLV<SmAD{*Xz^2uluQ~=}n#f{Oz9~7Cou+9;dVLXD4Q?`?}8cxyjU-+m7`uK5;Qo zZqaXtPbM8Q#*+o|UPehPGtKY1xbZ~d^fi8K8Lyx7I42z|@%7%VJ%wj8y*J*~32v2% zy}F_M+ibu2$MP3>IdMr_8~&Q{JK|cJja~G*n=;c=PF&QT>tZ#F@0>Mj%<pG;|BrVC zWG+p4qaVlebcO1r^mgC6uPU29zvzxuj@<M=Y8$_m&dt>U6U!>L&DXzq-dgA7?m~N` z6?25`9?1R=_5Kz=e}(Jw^H-kcI{vLq&MPhKOS>vnTK4SynfcpmWQtO%_B-;vTw?BC zI_bsMP5qDleR?PTHsa-4=kn)uzIC<6ns;^{n{%;RdeOON+pArBw|OdC|J}0ktjC>S zlD$2veoj?<d#CW;chkH5b6!RK`}=+2<!8Eo-!#jdKUW#-^yz_qmvZ}enbSYEReX@^ zn^yOX{c+o~IlsQey`AjJr$4D{`caJ{p7VFstVlV*IQ>yu`F=GAN2{DW7VoR-WfN}g zSvvpb8)KE_MenoTJ@$VnXgN=^ZIgo|7jn_z@AR7|^WbckCkzgJ5Gc{W*(|6~#DEDE f*wLYl?0<&V(~rMCZB%ao7JUq!u6{1-oD!M<jHPE< diff --git a/addons/skin.estuary/media/flags/videocodec/hev1.png b/addons/skin.estuary/media/flags/videocodec/hev1.png deleted file mode 100644 index 1e1d3c186b85128a9c849722b3e9ce9acce425b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1261 zcmZXUc~FuG9ELxN)X}2sFe`O3Jh9osGt@j9Q%l1mLzyyNvDMI|1jYNHu~{lmFx1sH zu_6<!HbMli4%1NcLsuaq#WlptYjsJyZFP3`58HWW-ub;Vzki>X5{UBIrn_Gk0Dx^L ze7%AJ00LXPQ*@wfS;g=51^_7kgqLSXihT0+P%6iDcTWDCNN<|z;qyX&RL7Z2+u_54 zkLnPiMi8GaeIj%!|NJa;GewhSKDxA6xn=94PbxDc#F7dK7g~G(G35W`7;R*_dO0~k z)%wRf?6DD`ZjT3S9mAIR7Q5?tmKCN1U_kev_C|ojjVwR0FD{-j!i3ZM2UBSwH`7S) z^dA`m(pUbpv6XPcq?_Y4`F7Cf5gMK^yWm%BnHXJ^sDpzS2Cq0yx_)O$xJmIJJKz8g z2m6_Hc}G<}WrTmCH_2epv=W~8h49V;NMD4W&BSTESjONOxTQ(vT2OAkExF(QOD^?; zZ*F{SD4|PL;>^eoHMq=2f?`~{>SFUl?6?Bn^XJ!Sp`>r$r_c$s0j^8LV9V`7Lz5LQ z_MP{I%NYz&rQVH(rO3AxF@`a2_??TkvG*hM9Ti7C1L)YpW{A<UvPMg$3y;<9J?Oz7 z0Aa|e^2&3^`!t{t1LU;$Z!=%H+D5#MYxo$iI>=Q-nkxeLB6d}8vOVJnqErs5`Dv)S z6m#wthn?ER$F!Cr!_Iy-p$o~FhY1%=Zt2-djX$SGSzBn@trPh%w(oK-8K+Z&E1RH; zea|cklZjz=*27kD!-fHhu9t%Y6X7m)WZy%1y}<~5vq%trh*uzAcx+X2q<H6SYeiGJ zl8(1`6@RD&F;>VCC-zhg;`e%F+Y&W6oh#0YwVr1WrN5aUv2<5b2-#m$>dTrzXQh8l zh-UZ8CwV07p6=(X<kuLS)2W{?VeH?y80csr%HeMom{pfW#~K7+2vIA|IHigIK0-kt zA{>N9*ip0qjABmJ_TWi2RlT?uN0Q!#&~VxH9Zv^W48pp_?7HO!Vo2O)v=)XrS%1CE zVvdL$$z5rZ4mognR~RSRT2w;Jos&{lPZBkob8c7Gy&+k*+v)eQ(y-)yS|KOr&MJnr zU93;KaF5?RJ-6(FXBEaQT+V6~?q`lcUuor4W~Z&5ttr3~ar5d|@LaG!xsTWCdCDU* z7Z#D(Nrz`SpcSwS+)bs<+SSkMRbda~)f6N5&V^L|m<3C)f3;rpB^;ZFecsg34CCu7 z-#RWns5o`sY17dw`iI9ex>#wfXeOyhyo=kIr-oePojE9}=ACVqw+D3m^!;@<%3(1S zH^E}c+|;!KTs4!)J1u!&y(&AemN`=?vm$0pQ8UbQs$VX-%QVGFW|C~&OL7QX7g#-d z#ggsdN9n|n24y>Gd?X=S&LL-{B$&IlxypS^%QHOcg4EZf=JA8v2v9X3((ve2vmU)k z27k+f818K+S8ouC5-k+w>6y)U@NI5E?>O8&17wjB-<OQ?+l%hDF?+31GRtzOR_biP znRdw4j+dOnfu>uAfp7U&_4WKk@9SKFV}@@u?7ycRNXZZTAhzF#zdi#S)n9KfI%c#< XaXSbmlLDKy83RrnM|nLy7D@gGm*8MQ diff --git a/addons/skin.estuary/media/flags/videocodec/hevc.png b/addons/skin.estuary/media/flags/videocodec/hevc.png deleted file mode 100644 index 1e1d3c186b85128a9c849722b3e9ce9acce425b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1261 zcmZXUc~FuG9ELxN)X}2sFe`O3Jh9osGt@j9Q%l1mLzyyNvDMI|1jYNHu~{lmFx1sH zu_6<!HbMli4%1NcLsuaq#WlptYjsJyZFP3`58HWW-ub;Vzki>X5{UBIrn_Gk0Dx^L ze7%AJ00LXPQ*@wfS;g=51^_7kgqLSXihT0+P%6iDcTWDCNN<|z;qyX&RL7Z2+u_54 zkLnPiMi8GaeIj%!|NJa;GewhSKDxA6xn=94PbxDc#F7dK7g~G(G35W`7;R*_dO0~k z)%wRf?6DD`ZjT3S9mAIR7Q5?tmKCN1U_kev_C|ojjVwR0FD{-j!i3ZM2UBSwH`7S) z^dA`m(pUbpv6XPcq?_Y4`F7Cf5gMK^yWm%BnHXJ^sDpzS2Cq0yx_)O$xJmIJJKz8g z2m6_Hc}G<}WrTmCH_2epv=W~8h49V;NMD4W&BSTESjONOxTQ(vT2OAkExF(QOD^?; zZ*F{SD4|PL;>^eoHMq=2f?`~{>SFUl?6?Bn^XJ!Sp`>r$r_c$s0j^8LV9V`7Lz5LQ z_MP{I%NYz&rQVH(rO3AxF@`a2_??TkvG*hM9Ti7C1L)YpW{A<UvPMg$3y;<9J?Oz7 z0Aa|e^2&3^`!t{t1LU;$Z!=%H+D5#MYxo$iI>=Q-nkxeLB6d}8vOVJnqErs5`Dv)S z6m#wthn?ER$F!Cr!_Iy-p$o~FhY1%=Zt2-djX$SGSzBn@trPh%w(oK-8K+Z&E1RH; zea|cklZjz=*27kD!-fHhu9t%Y6X7m)WZy%1y}<~5vq%trh*uzAcx+X2q<H6SYeiGJ zl8(1`6@RD&F;>VCC-zhg;`e%F+Y&W6oh#0YwVr1WrN5aUv2<5b2-#m$>dTrzXQh8l zh-UZ8CwV07p6=(X<kuLS)2W{?VeH?y80csr%HeMom{pfW#~K7+2vIA|IHigIK0-kt zA{>N9*ip0qjABmJ_TWi2RlT?uN0Q!#&~VxH9Zv^W48pp_?7HO!Vo2O)v=)XrS%1CE zVvdL$$z5rZ4mognR~RSRT2w;Jos&{lPZBkob8c7Gy&+k*+v)eQ(y-)yS|KOr&MJnr zU93;KaF5?RJ-6(FXBEaQT+V6~?q`lcUuor4W~Z&5ttr3~ar5d|@LaG!xsTWCdCDU* z7Z#D(Nrz`SpcSwS+)bs<+SSkMRbda~)f6N5&V^L|m<3C)f3;rpB^;ZFecsg34CCu7 z-#RWns5o`sY17dw`iI9ex>#wfXeOyhyo=kIr-oePojE9}=ACVqw+D3m^!;@<%3(1S zH^E}c+|;!KTs4!)J1u!&y(&AemN`=?vm$0pQ8UbQs$VX-%QVGFW|C~&OL7QX7g#-d z#ggsdN9n|n24y>Gd?X=S&LL-{B$&IlxypS^%QHOcg4EZf=JA8v2v9X3((ve2vmU)k z27k+f818K+S8ouC5-k+w>6y)U@NI5E?>O8&17wjB-<OQ?+l%hDF?+31GRtzOR_biP znRdw4j+dOnfu>uAfp7U&_4WKk@9SKFV}@@u?7ycRNXZZTAhzF#zdi#S)n9KfI%c#< XaXSbmlLDKy83RrnM|nLy7D@gGm*8MQ diff --git a/addons/skin.estuary/media/flags/videocodec/hvc1.png b/addons/skin.estuary/media/flags/videocodec/hvc1.png deleted file mode 100644 index 1e1d3c186b85128a9c849722b3e9ce9acce425b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1261 zcmZXUc~FuG9ELxN)X}2sFe`O3Jh9osGt@j9Q%l1mLzyyNvDMI|1jYNHu~{lmFx1sH zu_6<!HbMli4%1NcLsuaq#WlptYjsJyZFP3`58HWW-ub;Vzki>X5{UBIrn_Gk0Dx^L ze7%AJ00LXPQ*@wfS;g=51^_7kgqLSXihT0+P%6iDcTWDCNN<|z;qyX&RL7Z2+u_54 zkLnPiMi8GaeIj%!|NJa;GewhSKDxA6xn=94PbxDc#F7dK7g~G(G35W`7;R*_dO0~k z)%wRf?6DD`ZjT3S9mAIR7Q5?tmKCN1U_kev_C|ojjVwR0FD{-j!i3ZM2UBSwH`7S) z^dA`m(pUbpv6XPcq?_Y4`F7Cf5gMK^yWm%BnHXJ^sDpzS2Cq0yx_)O$xJmIJJKz8g z2m6_Hc}G<}WrTmCH_2epv=W~8h49V;NMD4W&BSTESjONOxTQ(vT2OAkExF(QOD^?; zZ*F{SD4|PL;>^eoHMq=2f?`~{>SFUl?6?Bn^XJ!Sp`>r$r_c$s0j^8LV9V`7Lz5LQ z_MP{I%NYz&rQVH(rO3AxF@`a2_??TkvG*hM9Ti7C1L)YpW{A<UvPMg$3y;<9J?Oz7 z0Aa|e^2&3^`!t{t1LU;$Z!=%H+D5#MYxo$iI>=Q-nkxeLB6d}8vOVJnqErs5`Dv)S z6m#wthn?ER$F!Cr!_Iy-p$o~FhY1%=Zt2-djX$SGSzBn@trPh%w(oK-8K+Z&E1RH; zea|cklZjz=*27kD!-fHhu9t%Y6X7m)WZy%1y}<~5vq%trh*uzAcx+X2q<H6SYeiGJ zl8(1`6@RD&F;>VCC-zhg;`e%F+Y&W6oh#0YwVr1WrN5aUv2<5b2-#m$>dTrzXQh8l zh-UZ8CwV07p6=(X<kuLS)2W{?VeH?y80csr%HeMom{pfW#~K7+2vIA|IHigIK0-kt zA{>N9*ip0qjABmJ_TWi2RlT?uN0Q!#&~VxH9Zv^W48pp_?7HO!Vo2O)v=)XrS%1CE zVvdL$$z5rZ4mognR~RSRT2w;Jos&{lPZBkob8c7Gy&+k*+v)eQ(y-)yS|KOr&MJnr zU93;KaF5?RJ-6(FXBEaQT+V6~?q`lcUuor4W~Z&5ttr3~ar5d|@LaG!xsTWCdCDU* z7Z#D(Nrz`SpcSwS+)bs<+SSkMRbda~)f6N5&V^L|m<3C)f3;rpB^;ZFecsg34CCu7 z-#RWns5o`sY17dw`iI9ex>#wfXeOyhyo=kIr-oePojE9}=ACVqw+D3m^!;@<%3(1S zH^E}c+|;!KTs4!)J1u!&y(&AemN`=?vm$0pQ8UbQs$VX-%QVGFW|C~&OL7QX7g#-d z#ggsdN9n|n24y>Gd?X=S&LL-{B$&IlxypS^%QHOcg4EZf=JA8v2v9X3((ve2vmU)k z27k+f818K+S8ouC5-k+w>6y)U@NI5E?>O8&17wjB-<OQ?+l%hDF?+31GRtzOR_biP znRdw4j+dOnfu>uAfp7U&_4WKk@9SKFV}@@u?7ycRNXZZTAhzF#zdi#S)n9KfI%c#< XaXSbmlLDKy83RrnM|nLy7D@gGm*8MQ diff --git a/addons/skin.estuary/media/flags/videocodec/mp4v.png b/addons/skin.estuary/media/flags/videocodec/mp4v.png deleted file mode 100644 index da9d967bb80c4ea699b6ac2542a9d0a1d7cdbc23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1362 zcmZwHX;9J$00r>BwwWEe#U@cYqp9GorH<N~h`DAW0!fOL;ZYfyV|Za&P$sSGv8kY# zb_re`h*_ZtnmO7=9O7AGu6Jz+hLIaiDWbXU?1!dl=Qs1_eR*Hs#b6xDY~8kX005Yw z1O4#;U}XNeQ%%47oV+GQ006AsiuT`2ppIzEUtO#7+UP5tPh2$y%789lj$!*j{OrTV zrB%>+XyDrLtjjjg6#Cl;tIdAJHuY>#<ubwE5(m5F+MQ}ttFmqdidTILfPd{}l)g5j zjB#s#6rcodTlkv)Yr*19S?*5oL^fMyw13A1tFHkvqpv*6Z}V&q=QJOT>m_&0eQL?y z^tvBxpzxc>&H;Zt>a!LHD)vo^aZZ}K3f?}u3iF=l`g(ra#F&mVm@rpW?c`zp^IbRn z@Yy4gw86Nd{%Yj-ELv<3$tb6NX8J>z-W{syF!&P_*UaC&KSTt#PGF}NATe&`SbIcX zUk=DCljIox{!H-oU}Lz9d$##g8>%rMNM>_Iw=&L7-_eR<7F7e7ilZFk*=5MxA}iH9 zPVAepx$!|w-vjH>d{aty&x%xV96vKcmQZTW-G~=!h5p~GTe`N>iml0$bR4tAuRvN( z6Ernz8&Z0+CiXMQq#+KXbGyDR&F+zWlb3q4idU;BP26B3O-W}~MSt|*s7DIqWH2bL z!jSH)tAxg3ltl?KPMt2j9wshX$jfw}6x`C=__`nWED6$~4l`VLG#iv;i6uIY!|YIp z;M-=C1C9~hn>tF;ta3tra=YnrxNm$>WncbA#@gk4Pz~BbpY`s1mXni@7{*H8hq-#X z5rg8x(aIW)$)G-$x|TPGH_>^AzvAqkN|@4>7tY|ER&wB|8c7U+XlsZb;E}-<%Bw?4 z*JVE8H#^p{0`#1NnJ$ctDo)4D7(<DwpvLy8{$WXMZ35}fL3u!WF3;(j1ILeAUosM< zjjO`~YD7;`XY@)ars@##Ql$L0W8`(b*^^qz0CaM}QOw|QPtS9bA%Y9zZN6gG3%JM0 zNXf}g$Y~sM%W-9$*F(1PnJ&1dgA4!E(zNvA2Sat8<!&F7M>=GQEyiE0b$1jtZPikG zTGbJvr=UOZTwyOF4Ey9d!)uGa8Df7_GboUyO>IOIcsnkamCh7rN`hcya(kt)4cAYi zPp9fNtJkYSx>#s@;ry-aRQ!!!KJXnqY3q9&@?;IX$o!S(2_!1Kog{6WW)m1)!o44k z+%6bee2hdgkv)uo^vPNs!sA8#3BqtLv`C|OIA^F}NQ2ze4)n$|@dcx_u0wIz2NLpj z(IUDLD={mf^uQh5a!NzkR$oPzg|nMqE<Fj#qsC@L31+T_>^e|jrzHIizS-z9Xuo7p zza)=VG}30zJFz<Dt^1h;rVq|l<5sDy=128)!b!8rQ)6+fQM2$oa)Co*5I@rVQXb3I z10=u@Wer35Yv3r}>jB#q=9Mye4mkDRuna={*t}gD>i&+~9Rs6FLY8=pl*+pEzdkLw zQXykJ4fI(OzMPlC$E*`|m8Y8MR;i;kV2X|UDwy0QDx30r@KNWfym_Uuqlkbf1|Bvb zQeYR^65>P19jm@%>L@SizM(hNC6Rak1Y2o;vDCVHA#;ybFn?q*Lc8-m&R^kRYLSc; zorkF}uv}t|zP!t~*8@Y%;rMKvZ7N{1=06)A7m7_ce%X9!|Ff1J-OYTdF|l;9`23Xs M8j172=XWgYA9G)f(*OVf diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg1.png b/addons/skin.estuary/media/flags/videocodec/mpeg1.png deleted file mode 100644 index 8b210cf2d156879d2974f2a49c7215543fa92552..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1294 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>Gu6nvShE&{oJNsefDjNxh z_<hT{3)~+_H(E2vI=UYaEMSpzbl=gZpgcveghf*E`|;e|0}PE0ju!3q+eAGkPd3kI z`Z$GQ#?O8&h9_8{10O1k(8@n{|5MV-UmrST4s2jL!3cu-hS8ZPlM)p*l-Lc{FchId z1$GqZu72h5kIOIjEG|n6s6P9uaJq|7_a+nh>1{=<lTIxB^q1dLMQ+QreeWmuB(!e~ zD@j@4oSs}87GkBJv18fH#h;>2T{^OKSxDzDvw7tP_qP5nys~a4&o!ZIZ*6*I!e4LJ z>nqYI4Zpac$VE4)M$r0t#LxQq4t?7_*)}^QSw`0APdf3bYi3IS`8rLO-$fJ4->(1q z+%{O^P)(ApOx}~d<-9+OCKh~Jk_>domAhIW!hUDeK3VZ?weQP2*-!Ioe?Pt`plBEJ zsqzkQbJYG_Tvv`?-Wq1S=EmR6A1_;eE9&^<Ejous;rPJ?M~lsh@-^1q6_jFM7kaWV zf9<Z-lVuh)1v&3JB^dc!q%*j<t&PKEqRi#B#Xocxmwz%Te0$3Gbhgt++4W^QPnGsd z7cC2^I~i9t>Dvx(75OO7;*-~Rafq3l?6*I$C(hsOFyrZ6xtUv>f>yWuj^wRPeX6sc zSM%ecry4zbH{Oz{J3MFM@3}_|`Hn6<F`+lixV3EhTdC>iFJD`krO<x-`DKTq9RgW} z%U_tr+?aU!-RH}G-(J^jn{>)I+T7r|wA$2PYnH!|^r$as^<UifYjee;&g<C$Q6A?e zX4M**=}oNNSe|vIK*6$UmQ5RbzH-4Op<PQ7;{q(cD#TA-BjIE8L}KpVYub}<seZnq z*&@GX$+wW-vY&+xY+ZD-JWSz)q1ntiTW>s+{@gn|c;Cn6BIa#Uhq<5pxOwAYc4lTw zmvk+AqszNZD?J<U7WMslz2*4)>5p?aS>IQwb?N+Zv8Gcjde@h4&S&>LpWl}0%yN3o zDJhHf(_Bo~^WA+?|HR;I<i6><_%==bWc9>gHD~LaEsSSW+_(S!u}Nv!l*JRiT)b~C z<hkzaznq3hN4@8bBByngj^DEob>~}tL(oR(qR;E;yU%gvg>hbc?%00d{RzQqXCl6o z-oH`NYg<%u^>N0GL-VZEC*17lzV(zd=Ss0vwe;2&?%8W=GABs3&#o-Iwrm5pq*0tB zU+|-sRv%^DA015<S+C$8c9O5F(xzn9)Wv%~b;&4iUjN7Bc<71f%!}`uWbDH~uKf1X z<nk=9tu7`yd(X7XD2La4vs{vs9upp|E6(*a>a$Yvoey==MPC+7DF1k5a{T7HSCx~? z*VgQlahY27<k+N+NsB*sU;Lcdw3ajW{GM-jW+m+RU39wa#NwcH)w#3#j%2ZaIJ8}D zkJj$Cvs0g1Y%adRXUTmp<E`A}_dZUYS@W62Cwc8_F+W!4*Z0p~%r9!|^;l#1r8-|H zzltgHT)L<A(f#+aOHb%}eRu9|nP;S~bjzGWw0r5&xhE|Xo&`rD$_30~qv7hse`+%p zP3n+gfRqp?7=<4gN-`>;76o7)T4@0+Jr2D3!9C%-=l%fc${Juf$KdJe=d#Wzp$P!K CL|_5{ diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg1video.png b/addons/skin.estuary/media/flags/videocodec/mpeg1video.png deleted file mode 100644 index 8b210cf2d156879d2974f2a49c7215543fa92552..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1294 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>Gu6nvShE&{oJNsefDjNxh z_<hT{3)~+_H(E2vI=UYaEMSpzbl=gZpgcveghf*E`|;e|0}PE0ju!3q+eAGkPd3kI z`Z$GQ#?O8&h9_8{10O1k(8@n{|5MV-UmrST4s2jL!3cu-hS8ZPlM)p*l-Lc{FchId z1$GqZu72h5kIOIjEG|n6s6P9uaJq|7_a+nh>1{=<lTIxB^q1dLMQ+QreeWmuB(!e~ zD@j@4oSs}87GkBJv18fH#h;>2T{^OKSxDzDvw7tP_qP5nys~a4&o!ZIZ*6*I!e4LJ z>nqYI4Zpac$VE4)M$r0t#LxQq4t?7_*)}^QSw`0APdf3bYi3IS`8rLO-$fJ4->(1q z+%{O^P)(ApOx}~d<-9+OCKh~Jk_>domAhIW!hUDeK3VZ?weQP2*-!Ioe?Pt`plBEJ zsqzkQbJYG_Tvv`?-Wq1S=EmR6A1_;eE9&^<Ejous;rPJ?M~lsh@-^1q6_jFM7kaWV zf9<Z-lVuh)1v&3JB^dc!q%*j<t&PKEqRi#B#Xocxmwz%Te0$3Gbhgt++4W^QPnGsd z7cC2^I~i9t>Dvx(75OO7;*-~Rafq3l?6*I$C(hsOFyrZ6xtUv>f>yWuj^wRPeX6sc zSM%ecry4zbH{Oz{J3MFM@3}_|`Hn6<F`+lixV3EhTdC>iFJD`krO<x-`DKTq9RgW} z%U_tr+?aU!-RH}G-(J^jn{>)I+T7r|wA$2PYnH!|^r$as^<UifYjee;&g<C$Q6A?e zX4M**=}oNNSe|vIK*6$UmQ5RbzH-4Op<PQ7;{q(cD#TA-BjIE8L}KpVYub}<seZnq z*&@GX$+wW-vY&+xY+ZD-JWSz)q1ntiTW>s+{@gn|c;Cn6BIa#Uhq<5pxOwAYc4lTw zmvk+AqszNZD?J<U7WMslz2*4)>5p?aS>IQwb?N+Zv8Gcjde@h4&S&>LpWl}0%yN3o zDJhHf(_Bo~^WA+?|HR;I<i6><_%==bWc9>gHD~LaEsSSW+_(S!u}Nv!l*JRiT)b~C z<hkzaznq3hN4@8bBByngj^DEob>~}tL(oR(qR;E;yU%gvg>hbc?%00d{RzQqXCl6o z-oH`NYg<%u^>N0GL-VZEC*17lzV(zd=Ss0vwe;2&?%8W=GABs3&#o-Iwrm5pq*0tB zU+|-sRv%^DA015<S+C$8c9O5F(xzn9)Wv%~b;&4iUjN7Bc<71f%!}`uWbDH~uKf1X z<nk=9tu7`yd(X7XD2La4vs{vs9upp|E6(*a>a$Yvoey==MPC+7DF1k5a{T7HSCx~? z*VgQlahY27<k+N+NsB*sU;Lcdw3ajW{GM-jW+m+RU39wa#NwcH)w#3#j%2ZaIJ8}D zkJj$Cvs0g1Y%adRXUTmp<E`A}_dZUYS@W62Cwc8_F+W!4*Z0p~%r9!|^;l#1r8-|H zzltgHT)L<A(f#+aOHb%}eRu9|nP;S~bjzGWw0r5&xhE|Xo&`rD$_30~qv7hse`+%p zP3n+gfRqp?7=<4gN-`>;76o7)T4@0+Jr2D3!9C%-=l%fc${Juf$KdJe=d#Wzp$P!K CL|_5{ diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg2.png b/addons/skin.estuary/media/flags/videocodec/mpeg2.png deleted file mode 100644 index f46483b765fe2ee260f2456db4134dc9ccd911a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1429 zcma)+`#Tc~9L85@Nv(Dq<uaE@F1d{9XfAt9Y(|MJPMDEf<{)E8NIjetnJH`JmPbUL zt<jKda){-gxl9wOZ5`6+(WX!w=ci6R=ZEurzR&x8pZ5>=T=nuq@7kfc0{{T*!nnDd z1OT>xHhao;#m%f^kD>qor9g}e5}P8FmP&C;(4Wy8*D?*-x7%&Kc6;+dw`wC%&%t4e z8b}n&;B(psAUA4+o;5RaGXYVz$GB&XYioIA1)N!-Oa%NFdcf~_Nr$K>kPm(vcY0Ek zzy9K%K~?w>I91`F1AK?Rr5I<qMH<xj_=|&}ZrKh;48Pt0D&s#z_6d0rspD9!zv9r` zLjnXcZb0@&_Fnp{6T<o1qR$d)kZihs%vtws^8&H-Mf81#7Kj$ZCi*Ph<Rd3K*(xcO z*-}ob1EP$1n2!_&qJK)&fLu85O3osh$%Mw;#iTIB^P}V%WJN?6F;Qw~8*9@O55@U{ zmiY~%mFr1umBx4pb4eJFsY4Lb><I5k{Qd*JCIX*^*<l|zall@2`T^h3-;&pa#XCet z3d?0;PC$C=f;^uA8|;bj#}MY*gH}hYn&l-J8!P@UcZx-413!Asa!Nw2S%qp#5jsY^ zL7$8W8sVv_^==MNYGi>g9ut{(M+pPq!cLK&VKe#J{sCF|wxv0Dc&`Z)VHL~Hz2Y95 zeX4BuxLjLfahowZ;JIURJO>i1nF76PhyvXwFvCS$`!Ulq?$P9nb9LD{6W}Iws2luN z#{w2#e#;R{iR7MdwGRZ@cA2XVs;AYP2#$vOdz>Q4yeMC+-^+FR-Fa_TDv1-=;1Q=s zkS4Ew_LzAQPQP>b5FQLfoAJhXayE3>V;w=c9!|SyBJT=}nDt;Jrktd*pwBpVY&}WE z70-YQ&}>K@KPNA=IY&#XhAB9U4y1aP?hInA#=M*`g_&meh8R^e#neK4$0tX%LQV}` zx!a&>ME0ZebCsv!w&&A2r=w%9?lUDbjvU-DiA`&AnFRNQc(^8K-pha58?u;etHEAm zJnGGu3J&gIS+UldBu)~Rgck5dEnei{6i=K`>sq=Tz=6!&WFBQT`rJFi77!o7t~VMz zEWcmi1=ndbmL?RADbqx}glgP){NqK0?GP;|UASa1>?Zj@&uzQF&V?MuR=Lx0FU+7s zc8_;Z#-b^;^~74Cto=m3gry{kW3l6_iqrxP(9A}oqvXAJa0JTaGb@<tiVRk*)AyRG zYeuqZn6S6;1-)WMl|q^jskJy2lWmDKH;`XmV)m?__?*J>pS5QW&+4P$j-{IR)>u#p zU{c{i<t6RUrw4|gtlnTUHRlgR`Tz~l@_tm~`&_)DhatJloZbAP58T!eGF0`_Id8hp z8)5jaBxU^~{7xJUU$k)va<Q@-Ag+D;W+_vS1eN?M{QX@jd>xgy<=JZszm+7%H9azp z7Bxxd`cj^p4BbPVpnM{sbj9Z)eFt)91wF%w%W{x}g`5<WeWi9zW!Mu=YdJD+h&~RD zs$YTlaHA?8KP*l!=q&Sp+sldZ69$&6S&+?#;uzgd;<GiDL^l|>cx|;gz238)&rUS9 z!&2V$A3v-T*>?c{n=|F+kRU70sOwW#4Ru+b#Ub9ZR)hc4x8IyScaB_9s05lx?o19V z2-PFHJ>hLlI-D=*B@E`@ZMg^T^)}NFA2$C~t<llluLJUaFmY^c-wogD)~_jDZF2`_ zTB6qdfEzD>|HC>nLE_A{ZyE|#Cj!3t>)-811GX2f0F}Tni$Lpg(xz?!7*|ghrgQM+ FzX8p;k)Z$p diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg2video.png b/addons/skin.estuary/media/flags/videocodec/mpeg2video.png deleted file mode 100644 index f46483b765fe2ee260f2456db4134dc9ccd911a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1429 zcma)+`#Tc~9L85@Nv(Dq<uaE@F1d{9XfAt9Y(|MJPMDEf<{)E8NIjetnJH`JmPbUL zt<jKda){-gxl9wOZ5`6+(WX!w=ci6R=ZEurzR&x8pZ5>=T=nuq@7kfc0{{T*!nnDd z1OT>xHhao;#m%f^kD>qor9g}e5}P8FmP&C;(4Wy8*D?*-x7%&Kc6;+dw`wC%&%t4e z8b}n&;B(psAUA4+o;5RaGXYVz$GB&XYioIA1)N!-Oa%NFdcf~_Nr$K>kPm(vcY0Ek zzy9K%K~?w>I91`F1AK?Rr5I<qMH<xj_=|&}ZrKh;48Pt0D&s#z_6d0rspD9!zv9r` zLjnXcZb0@&_Fnp{6T<o1qR$d)kZihs%vtws^8&H-Mf81#7Kj$ZCi*Ph<Rd3K*(xcO z*-}ob1EP$1n2!_&qJK)&fLu85O3osh$%Mw;#iTIB^P}V%WJN?6F;Qw~8*9@O55@U{ zmiY~%mFr1umBx4pb4eJFsY4Lb><I5k{Qd*JCIX*^*<l|zall@2`T^h3-;&pa#XCet z3d?0;PC$C=f;^uA8|;bj#}MY*gH}hYn&l-J8!P@UcZx-413!Asa!Nw2S%qp#5jsY^ zL7$8W8sVv_^==MNYGi>g9ut{(M+pPq!cLK&VKe#J{sCF|wxv0Dc&`Z)VHL~Hz2Y95 zeX4BuxLjLfahowZ;JIURJO>i1nF76PhyvXwFvCS$`!Ulq?$P9nb9LD{6W}Iws2luN z#{w2#e#;R{iR7MdwGRZ@cA2XVs;AYP2#$vOdz>Q4yeMC+-^+FR-Fa_TDv1-=;1Q=s zkS4Ew_LzAQPQP>b5FQLfoAJhXayE3>V;w=c9!|SyBJT=}nDt;Jrktd*pwBpVY&}WE z70-YQ&}>K@KPNA=IY&#XhAB9U4y1aP?hInA#=M*`g_&meh8R^e#neK4$0tX%LQV}` zx!a&>ME0ZebCsv!w&&A2r=w%9?lUDbjvU-DiA`&AnFRNQc(^8K-pha58?u;etHEAm zJnGGu3J&gIS+UldBu)~Rgck5dEnei{6i=K`>sq=Tz=6!&WFBQT`rJFi77!o7t~VMz zEWcmi1=ndbmL?RADbqx}glgP){NqK0?GP;|UASa1>?Zj@&uzQF&V?MuR=Lx0FU+7s zc8_;Z#-b^;^~74Cto=m3gry{kW3l6_iqrxP(9A}oqvXAJa0JTaGb@<tiVRk*)AyRG zYeuqZn6S6;1-)WMl|q^jskJy2lWmDKH;`XmV)m?__?*J>pS5QW&+4P$j-{IR)>u#p zU{c{i<t6RUrw4|gtlnTUHRlgR`Tz~l@_tm~`&_)DhatJloZbAP58T!eGF0`_Id8hp z8)5jaBxU^~{7xJUU$k)va<Q@-Ag+D;W+_vS1eN?M{QX@jd>xgy<=JZszm+7%H9azp z7Bxxd`cj^p4BbPVpnM{sbj9Z)eFt)91wF%w%W{x}g`5<WeWi9zW!Mu=YdJD+h&~RD zs$YTlaHA?8KP*l!=q&Sp+sldZ69$&6S&+?#;uzgd;<GiDL^l|>cx|;gz238)&rUS9 z!&2V$A3v-T*>?c{n=|F+kRU70sOwW#4Ru+b#Ub9ZR)hc4x8IyScaB_9s05lx?o19V z2-PFHJ>hLlI-D=*B@E`@ZMg^T^)}NFA2$C~t<llluLJUaFmY^c-wogD)~_jDZF2`_ zTB6qdfEzD>|HC>nLE_A{ZyE|#Cj!3t>)-811GX2f0F}Tni$Lpg(xz?!7*|ghrgQM+ FzX8p;k)Z$p diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg4.png b/addons/skin.estuary/media/flags/videocodec/mpeg4.png deleted file mode 100644 index da9d967bb80c4ea699b6ac2542a9d0a1d7cdbc23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1362 zcmZwHX;9J$00r>BwwWEe#U@cYqp9GorH<N~h`DAW0!fOL;ZYfyV|Za&P$sSGv8kY# zb_re`h*_ZtnmO7=9O7AGu6Jz+hLIaiDWbXU?1!dl=Qs1_eR*Hs#b6xDY~8kX005Yw z1O4#;U}XNeQ%%47oV+GQ006AsiuT`2ppIzEUtO#7+UP5tPh2$y%789lj$!*j{OrTV zrB%>+XyDrLtjjjg6#Cl;tIdAJHuY>#<ubwE5(m5F+MQ}ttFmqdidTILfPd{}l)g5j zjB#s#6rcodTlkv)Yr*19S?*5oL^fMyw13A1tFHkvqpv*6Z}V&q=QJOT>m_&0eQL?y z^tvBxpzxc>&H;Zt>a!LHD)vo^aZZ}K3f?}u3iF=l`g(ra#F&mVm@rpW?c`zp^IbRn z@Yy4gw86Nd{%Yj-ELv<3$tb6NX8J>z-W{syF!&P_*UaC&KSTt#PGF}NATe&`SbIcX zUk=DCljIox{!H-oU}Lz9d$##g8>%rMNM>_Iw=&L7-_eR<7F7e7ilZFk*=5MxA}iH9 zPVAepx$!|w-vjH>d{aty&x%xV96vKcmQZTW-G~=!h5p~GTe`N>iml0$bR4tAuRvN( z6Ernz8&Z0+CiXMQq#+KXbGyDR&F+zWlb3q4idU;BP26B3O-W}~MSt|*s7DIqWH2bL z!jSH)tAxg3ltl?KPMt2j9wshX$jfw}6x`C=__`nWED6$~4l`VLG#iv;i6uIY!|YIp z;M-=C1C9~hn>tF;ta3tra=YnrxNm$>WncbA#@gk4Pz~BbpY`s1mXni@7{*H8hq-#X z5rg8x(aIW)$)G-$x|TPGH_>^AzvAqkN|@4>7tY|ER&wB|8c7U+XlsZb;E}-<%Bw?4 z*JVE8H#^p{0`#1NnJ$ctDo)4D7(<DwpvLy8{$WXMZ35}fL3u!WF3;(j1ILeAUosM< zjjO`~YD7;`XY@)ars@##Ql$L0W8`(b*^^qz0CaM}QOw|QPtS9bA%Y9zZN6gG3%JM0 zNXf}g$Y~sM%W-9$*F(1PnJ&1dgA4!E(zNvA2Sat8<!&F7M>=GQEyiE0b$1jtZPikG zTGbJvr=UOZTwyOF4Ey9d!)uGa8Df7_GboUyO>IOIcsnkamCh7rN`hcya(kt)4cAYi zPp9fNtJkYSx>#s@;ry-aRQ!!!KJXnqY3q9&@?;IX$o!S(2_!1Kog{6WW)m1)!o44k z+%6bee2hdgkv)uo^vPNs!sA8#3BqtLv`C|OIA^F}NQ2ze4)n$|@dcx_u0wIz2NLpj z(IUDLD={mf^uQh5a!NzkR$oPzg|nMqE<Fj#qsC@L31+T_>^e|jrzHIizS-z9Xuo7p zza)=VG}30zJFz<Dt^1h;rVq|l<5sDy=128)!b!8rQ)6+fQM2$oa)Co*5I@rVQXb3I z10=u@Wer35Yv3r}>jB#q=9Mye4mkDRuna={*t}gD>i&+~9Rs6FLY8=pl*+pEzdkLw zQXykJ4fI(OzMPlC$E*`|m8Y8MR;i;kV2X|UDwy0QDx30r@KNWfym_Uuqlkbf1|Bvb zQeYR^65>P19jm@%>L@SizM(hNC6Rak1Y2o;vDCVHA#;ybFn?q*Lc8-m&R^kRYLSc; zorkF}uv}t|zP!t~*8@Y%;rMKvZ7N{1=06)A7m7_ce%X9!|Ff1J-OYTdF|l;9`23Xs M8j172=XWgYA9G)f(*OVf diff --git a/addons/skin.estuary/media/flags/videocodec/theora.png b/addons/skin.estuary/media/flags/videocodec/theora.png deleted file mode 100644 index 8f1af4bb946c5d2e452324233a88ef308998e8e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1515 zcmZY9i9ghN9LMo#Wy!hDb*_wSh+T~9muqWg%ur;EE4hzBl8hr1TCL2OF^<O3kg1$) z<QhjSqony|Xf%!v9$3d9;gRHsV%zrdu#eiu>;3pXKHq=fNpf~Vi;F0UKp+rtdkoSA z0@)|=`A!ht|M|Vm9I}Hz1RL#<M_dy|mgtd0IX8t%pox#E-r!cQ9X-%1B^Y0sCz#@@ zq@H(6aXmytT@WihoL!@@bzDH4)a6k9#27_-z!^H|m}MjO5p%#`a3DUW*4-z0t05HZ zu!O(qm`w860Q&2$t#X06@x<A_z6;t=X?w8qR~q8~A%U+e&+Q2azlZ|#IRRJ+GQpGf zh%nnTd1&kvnLzv|SO($CU>SRX;dmc~aP*0dS9)N1n(di(AApAVG7YkOEan`}D)WuY z>0`7qBio#KGMe{L$c67L@!u@qGxHoVvxaPu_$JCq$<;(}OYNq#C0Yzo^*)$DALboX zUY-_cadr@F|ByL%&nCtMT_oG`XZ@w?DwUL{R&<#czd!5?RBhiZG&X}5`f{)g0y9to zJRO2CduGEglD9%7LSq{9)o)vu^vcjGgS~8SqS;{uKS&wnrbZw;eKk(+pV0QQWP8)~ z(4U?}BRFaj^_NAZn;wi%x<|0fj?7ebMyU4G&Mb9^ckxKKCv~pd<|n9wXVI&1D8E__ zND=xAr+v=AM}gh7IivrC6veHq-#r-9mVP!i0JhrYmO|UtjL(W&$^Rr#_8JxX{fegL zbO*x4ZdJ8V2Vc2BS>6uV7ACh{y))nKKZ{dbr{btvIRfj3R;F5hM@E${_;K4NV?%$0 zj1o$5z9PKs`9hhoyw+*oQ7yYB^5X{q_<_sv%yRQ-z#K`Lv8d1E0!w46UfaIF0)iUp zO|L#Bx3Emw@aAG6DLp`i+k4TNr$q@Tgs2c_`w3($jwM{%kD*lNO{)a^vx!kPTG4V5 zM<d4ar-yoZ@QSm9>S9Kb`LbhV1OVxkUS+2Z&5z$)hZx{y8yW<6M!>FUNf`LLCq_&x zPRD73`j&~!8+nd^(Aee$jH&d__bl@(TazSSjsE5fstDT2gF2`Iy4^e*cd@v7dnikI z?K+Vdrq<#B-!4^z`eG<}%)`$3FpGC`d<$!WYwO#Pmn@(^3iSs*Gaw+s5kDbww0i)T zQ&(FMR7#LVQ;iZi*toH5$y3LSw^<eL+vz<#lJ0%BUdyVE+zPpsAZt&JPP~n^_kcnx zOH52nVaSipH&o`}S!(bk4=S_=n_QM)%I|HIVIJTO!@-Pe$&$N%a1hB^aK-_dnrKFS zhhg1L#*3s9M}o&x<opc5>QvAbuii7uK?)u7O05+tsJt>1CNGL}XSa9C%Fex;tRCn- zP7otCwG7^SDKv0J6D`|F2ir93q+IK6cwfct##MJ3L;(dETe+-S6D!~+C;P-ucokRX zIee&qcQ^`25mgh~)EkBH!=C<TSi9jES)0iHE_KbJk4L*VU6P=iir+U;;kL%IZs=5u zf}tDu#3H5jr4zwM#qf0NkNHgwIP3F^wOY6XQS3L(g|Y`hbCNO^**(0{SWJ+0qY|^H z;%#SBww~oKQ-b4*-UUQb4V9<PlUo%~l851!bh?zpRqix*B^d*yqHFx)Rkj|_7$p4) zX-`m{4`Zol>EG4pug^aebL*GDIQ*h5<!65rDXGX08qF1Q%)kzUtRI!AeLTdEqdNjJ zl_U#wSQg3j%ep)Uul)wkmB;949s#-^nW#_2@xz1(NMs3!91jt#P{B(U7lGAE6Cs*) zn?fhM%s3W(bv2=Qc9qqKV9e+{F=gGVG{(ut8Y}SL+C>kEaD_d7_8|Hfrmwuh1m!*& yu*m#Jv|B3j{CZdhzb^;zHGX@)Z=K}gcLY3HjPm?N`R31vL+nvb$h$}V)BXlPv(4cE diff --git a/addons/skin.estuary/media/flags/videocodec/tv.png b/addons/skin.estuary/media/flags/videocodec/tv.png deleted file mode 100644 index b7cb3574420807569872662534e485a385a4fd57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 708 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`x;<SSLn>~)y?b%uA_oz- zhh?W(d=83uG=k6_0W((5MiHJwo<ucPrvmwHT(?D8o~5S!syi<39j&v=|L*VYNjrC| zCd`Uo#_)u}(t&RRGlF6$YLHQ2Kfws08iQ{7+r2t;sp3rtUxqeV{DJ$MFtH#Vu7}Ht z9HJRfb;GoQbsyip)OY*Y?Zs8{@Aoe~+*NM5Wcx?a_fIrt=j{n!T6Xk@TBz`PRriNQ zORlV$^w=-{QOld|m}l9)Y%j9--3*GGKkvVX^Q+^m>x1U%>EB=MRa&;EU+H<_CdTJW zobrypnR9EVVX4`(Jq3I0Z{B#;e(vNUt88t{>1&uwxEF3Yo8=e(tYkv6Ub#%%vp%!n z<=Ul2Ck*AiZl6e;B6FUHU;0*7+7&|w3Dx5)f4g{dJ9attU0+zTZsVL&{EMGN@GX6t za+gEE?c_!ouh~0%TV=c#Zt*+5+~AfA$KoeHd^B>?vUn_++AdwLKmT-oPuvMX+bPes zD&-r;zMSmPm%Gp;AXxGEtbkiRSC#JH`j)BC9#FMjJa-Auj?#r&YO}PCwg(oiIV<~r zU&d<h{b^HVqMt~}Wx1~~@akI{aLc~sl3EbZUDE=p`aYFPTV89s^gHxb=^CJH?pEc9 zy9z)VFTOKvmjZx3nV+c>J11BjC?uuPGby?H?F*&3KkUxT<h$9wdcCxBW`Jec<o{2m z*uTi%b1F4)?pDzgNHGhEV0hfu=ucbEvU{e=RAy-8|6nRJQw~6lULX%Sae$2AI3B;- XdgHXz=vDH-)XCuK>gTe~DWM4fUT-a( diff --git a/addons/skin.estuary/media/flags/videocodec/vc-1.png b/addons/skin.estuary/media/flags/videocodec/vc-1.png deleted file mode 100644 index 843497f6a9cf3bbf91bdef246f1aeee9fff48520..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1045 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`?|8a6hE&{oJNshkDhCl) ze=h^o2|`;~bRE47RF4QHaYi*wTcDc38RVq%aBsiHJ;@o8uBt1l>o+P%h;5!*y@MzJ zKTE(_=^_SfP=Os4?wKYd@wrJ2A!sv4CO>BT@~83`x)s=qzzCrh$Z}79GevgWHmQvH zclIxyx~u(djC|wXwo_Na@BdM~9<y_rxz+3ZTG?%Cvy_hS{c)*$W9_+Cx$J2-?$<3o zA9q=r=b6vr?sJlsS(h&QZ`>QIzw`g8GwkxGyp50f%spcML@KlTsO0)NlAM<BPB|S; zs$Ni9a(|uK(QiSEAEeBG6#RLX*8DE1A`^%B)k)Rs&Kqa%HTfjeGWU)D{ps>ufzN6u zUE0v)!|l)eB*S3JyRU-0OYL1RemK0sK3m7MylwyDjVG0|dG+j0uM<-}I$`m}0IAs@ z4kunYXLl)X{?zZPGt*vq-&!5+rJZRSHZ_yYGD^tnaAHY%-Q{XtrN-hFKBv2{7CgP} z<yz_3w>$Z>lfJ>0nPrUzS?N<geBAEoy8r+0C7MMhQeMkvsdV>CyS?oZvic>J<N5P% z-IE`i1Af&7N^CXu(z<Xhbw%ssjtPrH7k;s@dCIuaeOX>>K5NmRNR73-E^b+CWZrYE zO8&%0k)j<7ujKc%A6_xr;@<n95Rir|`=4wp`LXwGa1^KgQrABA>B?eW$xL(XR$u<^ z(#Jkgxn#4{?BEHHU7u`NctwqSw%e(dkE|^3uwC!0=yd9v)^cfrk-M3z_U}1g6;6EI zP!u9MAwr@wzsUKm@skQJ%T<e>t8GlX^377Oe%+**|2j{LSl(*0_djxYg~;o<LLT#d z`ufU_8c$j3)@krc{$j{-X3Ho+FY`XFm&&s;7bv@PJH5QM<=ElXc5elDR`G7RF!AyE z8&jrzT*lQ_t0O)yal&KHCo8ISHSIWNYT6sx_I0<cmkoN7;i}Qwe(_bcSmtU^S3$4+ zy6rM+r!W4)cgk|<p;%9?wOg<KS#Tv_=02H+>!STSWOO-~el&O`x7A}GPh`1Aa{dFk z%q-bk-KQV6UHZIRT(&lR&v&(ax0zQW=J(0y@-ChJ_na~BUZJG!le>;(PdIULSAdn7 zcx`;PMXljF?UK7s>?)nLBjV%u98lmB2fil^81TWay)q{lVX$A39cxxAVnE3$z>H^O b{GU-|m7aX@t0~`s*`2}D)z4*}Q$iB}o#Ma~ diff --git a/addons/skin.estuary/media/flags/videocodec/vc1.png b/addons/skin.estuary/media/flags/videocodec/vc1.png deleted file mode 100644 index 843497f6a9cf3bbf91bdef246f1aeee9fff48520..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1045 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`?|8a6hE&{oJNshkDhCl) ze=h^o2|`;~bRE47RF4QHaYi*wTcDc38RVq%aBsiHJ;@o8uBt1l>o+P%h;5!*y@MzJ zKTE(_=^_SfP=Os4?wKYd@wrJ2A!sv4CO>BT@~83`x)s=qzzCrh$Z}79GevgWHmQvH zclIxyx~u(djC|wXwo_Na@BdM~9<y_rxz+3ZTG?%Cvy_hS{c)*$W9_+Cx$J2-?$<3o zA9q=r=b6vr?sJlsS(h&QZ`>QIzw`g8GwkxGyp50f%spcML@KlTsO0)NlAM<BPB|S; zs$Ni9a(|uK(QiSEAEeBG6#RLX*8DE1A`^%B)k)Rs&Kqa%HTfjeGWU)D{ps>ufzN6u zUE0v)!|l)eB*S3JyRU-0OYL1RemK0sK3m7MylwyDjVG0|dG+j0uM<-}I$`m}0IAs@ z4kunYXLl)X{?zZPGt*vq-&!5+rJZRSHZ_yYGD^tnaAHY%-Q{XtrN-hFKBv2{7CgP} z<yz_3w>$Z>lfJ>0nPrUzS?N<geBAEoy8r+0C7MMhQeMkvsdV>CyS?oZvic>J<N5P% z-IE`i1Af&7N^CXu(z<Xhbw%ssjtPrH7k;s@dCIuaeOX>>K5NmRNR73-E^b+CWZrYE zO8&%0k)j<7ujKc%A6_xr;@<n95Rir|`=4wp`LXwGa1^KgQrABA>B?eW$xL(XR$u<^ z(#Jkgxn#4{?BEHHU7u`NctwqSw%e(dkE|^3uwC!0=yd9v)^cfrk-M3z_U}1g6;6EI zP!u9MAwr@wzsUKm@skQJ%T<e>t8GlX^377Oe%+**|2j{LSl(*0_djxYg~;o<LLT#d z`ufU_8c$j3)@krc{$j{-X3Ho+FY`XFm&&s;7bv@PJH5QM<=ElXc5elDR`G7RF!AyE z8&jrzT*lQ_t0O)yal&KHCo8ISHSIWNYT6sx_I0<cmkoN7;i}Qwe(_bcSmtU^S3$4+ zy6rM+r!W4)cgk|<p;%9?wOg<KS#Tv_=02H+>!STSWOO-~el&O`x7A}GPh`1Aa{dFk z%q-bk-KQV6UHZIRT(&lR&v&(ax0zQW=J(0y@-ChJ_na~BUZJG!le>;(PdIULSAdn7 zcx`;PMXljF?UK7s>?)nLBjV%u98lmB2fil^81TWay)q{lVX$A39cxxAVnE3$z>H^O b{GU-|m7aX@t0~`s*`2}D)z4*}Q$iB}o#Ma~ diff --git a/addons/skin.estuary/media/flags/videocodec/vhs.png b/addons/skin.estuary/media/flags/videocodec/vhs.png deleted file mode 100644 index a1a4ff3198b25a79741b2c83139f0a2bcd0d6f08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1097 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>G1Uy|FLn>~)o%6bMm4k?z z{d+IwB`iTrnvGKq=r)RN05Yd7P|e`<<P2z<cHr;i2a=WRVg%dXoRvR(nCDzv@i`f{ z@TCm1ejavbIKhYs8)Q)7hLCwu-#2ae@_l2{6NW=v3hW?QVHo}LWKyEylpYy}2*xK2 zsL+8A1@@gh5n7ik&&4)J{>9>5O6$K(yE^S#OnuIdTdHAM_tuB33*jvCIk>KBtK1dV zjn{au-|P0+`R&x4+J#2B>>2Gc%BK{MHb2=>w4*pWNLF;4+}SDGPJQl0hOv9z{r&J( zl3lbs*Yi=2M621YJ3YRM?N-Ihb8hL(dMY+kEU)_9*)ux}AIC1UeP6KV+?lz56D!T! zm5NnzcP7O}``iE4YCp9n>)}owtMfM7ORoGsJ#nU4i~04I_;A;Mryjl!DNBE*RQUXW ze9yF=xXBN{NA1x1vTSOY)x-&Yv!|#{R$098;=2W`kJihGr#tP=dwbyHq>C3ad5g|; z9d6)!J7aRh_G^tYn;)gPH7(i7Rd|;1ov!=CE5=MuX5<IWGU=0kthzWTvB`5q|I=PM z?}>{oH}uGc`1!FFUAg%Gx#sDKht-w_t`(A$vV1qUr_Mhi-?)FRSFZTtj_s}+s+NA) zHf8Fp<iPW>e5&t%$m>O})p2Vrx#X33d*Yt&di7fF)8+oQ)@m)`_wh<j@8Ea$am&r9 z%w5~0_UprsjkCj2i`N*YpZUYbG}kxn<Mygt<^RRW6IYpZrj}2%Ub$<>1#fRhov>RL zbN^m>-BaB4uI`)NniY3uU8_6(^T3<i-}zSA*M3-b>+rTKESvL%4xQSgwbb);OL+{} z-q)`A=8q?AFP^S?;$nzquW$QYxqI`>bI;t*j$gg}bVThw<Bj%{M0$6nl|;<iRs2cm z$&7%$ZsW2elUb&Id!?J0ddchM!4%oN)GtPnvnM3CdA=;Way-|WvsaC^$Y=JHx>=vD zFP?O9#nNpLw;j6O+I0G-)su%03+*ok&OFz9zS-Hh(X}s9b!q<u<rVujZo0m0*|B|6 zfn4?1r|Y@*d9M{d=Ii!$)i;-=m)1LM3R`u@XQ}zWWy}1d_}XVjJ=x)yy8J}s@2RHq zwtlRP{WYns>7d%u$Lfox&$h}CuFpQ*I3Zcg^QE%d#`jT~k2Nxn_oUz7fA-OT<!W<J zA1mRI>90?HIe*XnTchBj&Kr^23qLH`bn)`})03FrX|db?X4$Xu94&u5VL;9wAKL!0 z9#fSUQf6l;g1`eTYi9H?D1&nvB)`E(m<j}aVEtqHX}j6>EUFNE2`nNQJYD@<);T3K F0RTPN{{8>} diff --git a/addons/skin.estuary/media/flags/videocodec/vp8.png b/addons/skin.estuary/media/flags/videocodec/vp8.png deleted file mode 100644 index d9ad357901d2db8800fa3d7d1529baf8c17d9953..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1193 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>G3OrpLLn>~)oqM-(wt<Ml z`roOHMof|iWgGh(+!rWM5d6ULjpH1LL6dQlEmO||I}<}84-bpNpC2vv>=5CRx7zvr zRo60URfe*<4;&1ZSl|R^RCw*w<Whb8X?AkbO6(KDAQ}<`H;ej<a%(#ZS~hH9I>Cqv z8)Q)7<lwGfH_i$5+AZF{e!u9X>iw1zW3t}P2`cIgPi+4>^Tmmt?J>L0+dTN;W4&+3 zJKd@MLAkQ`ca+ZC8-8)k{MZ8)`q6zJSFiuJVV7CY|D>!%M(^jOgU08y;<m~7ck@;E z?(zKg(Q=y0s)LJ@J=e|GuG}tDII($}j?m-xT!J?xtG5M7)vsGJ$;Vq`<CIUqQkPDJ zL?^L)`k*o;^0DVlOXX1Y$B{EmKF!%%mgBO>HZ%2<ul&A;I`{d~-g=lVRTrJwX7O%H zG0(o`k*7EQS@~x6H%n;?y{k<B5|4cUKdpOOmXhwuEiY59TRy)!<FvulxjoI(u3mPu ze4=J4_4vgRE%*Ipm6x^npUl{zk;{JOYq3;jQAm^y$3mm7sA$RT-P*Gfj5}o?^~%mv z7Hs|SZlW^Ropu@F59@g2Ur9_mGf{0{(bc-9jZ5cq7oAZpbGde7;+=?|Z;KBcRdT=9 ztv<8NuT>`ae8jASlBd@m&8XjZG2?oUtEE(xN>Ie<)L99_xz*{by+WS62x<9h8RJ_X zc-ybqHgJ*AW5098@$r$h!fWgUTockweEMn}*1AOBepwv#>(;5LCl{=jmOg*}^~sB# zwiAI8=ashV{M{K|^!)#su=|Q%MVEG(&6WtxUoy#CcV}ViI~9xRRSSYF^><qDS~lx& z%szeDs~&IGYF_E^NSGJCI_BwI58-dqI5VHxi!a^iK67KylV;B6DI%+826iqvVR(I! z$!C$pb(UFEqo2uqOz`P+-`8RiY-x6CUUt;9FSl9zum4&!F<nqlbA9FiV*mNyW_YjH zx@mb%b4%8fqnB9ErquualfA9i;J(FrjiN7)k6d6e-*euu=G?_Xm%R<U9w(-0-}TG< z_4M2efsLh+=k|Tubi?&`>B)-{?;DOqY445t#3WHv6aGdZ_o(u&;5!_VbNY2&8=f%y z_^CEE(ktI>|6##>3HkNSDcLcymT3~!HN}1JCn%fkS~35PZF!5#WbvinrR8ch_q}*o zXmZbBuflPe?n}RU{gYi5JNZpI9rkbS2@U0|e-d+3*Uh~&fvv3avDYiR1#jnV(6YY2 z=!@LZ3~AxE#fMUl?Y+qEt9@50x%~KRnI|`Yu>0&<u>RAg9MhE4Z0+2B#mg=qLuKB+ zKCpe?OgYK?b!O#nR@IdI8d{utJ%xFnD_`APVVj7vXxZ-sBXagLsQ&YRY1<=3K}!Z$ tzE(>(oWjz9mPtW+7*O*uh{u0CUejph)t+;&%78@>gQu&X%Q~loCIETnAc6n@ diff --git a/addons/skin.estuary/media/flags/videocodec/vp9.png b/addons/skin.estuary/media/flags/videocodec/vp9.png deleted file mode 100644 index c09a28cb10d6d0180f9c06d6be6e2fccc52c026a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1143 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>G>^xl@Ln>~)opZZ%v4McY z@#;Ga(M-A<R3``(aOQCCU|G(xn<bb@PjQOCbOEmm|0+6DROG%LRCpR!*P+|doR{X- zpR$<cz&U+6Mg?|E$WVj|9aiM5UB7ALm**Q5ix@IlCoqHH9*MO)AsYoax=t`I5q020 zg$z$nVZ;otD{p^H$@nCC_xi5mj$MM~eoH6#zg@Lir13^#t(MiMTZ=E)r5Y}(UANVK z%lfda4lmVn)oSNncZ=HZocQ<6W|@h<vh1He@Ay__t|t7XpuE&hA@s?1ndtuJIOU~J zwttQY+WBQ$pK9)T{h;Wmn5RpYc3r7>ub}k4{B5zNRQ&WH=}e8OUjHV9O|uGmTP&xp zvha?S|Ek^EMqd+cIE{B6={u36HmhmNraKGE<Ld?f=iIKfoaAddp-bzyb^P2}PuA=e z|Eb;n-udPwj?Z~Uowq+<Zu$NG$*QHDCuS}AHdp0q_^jKJSAMUQICbvS%C~G2*M##` zdh{L3m>c!3eCwU{7hg?gv7DykwSTu_>6Wf7=k{c5OyCh;yyES=sm$Su#}{pMW=nQ| z=i!pPg8fOxn($at!->g;S!;hAxraaLGclAGELx*m=5p=EM7xNdGVAYIo#NlNOyqG9 zds1<;W9duTjW$s|_rw=gx!*o+@GYWMX0@i*<a7CT{|XGN)<@@BZsrX$_FXAuc`bU1 zU2spANS|l9ZItZ0utZ}^Yp$sKA8OjRSO-_xtN(mBLsHh*xyoRk%d<Is;j4mLyF!+9 zopXvi^5y3x_jeJNo@-BkIb4*o>cX{}x7p^;1y}EMzZQ0FYt8!dPl3NZA02G{v+rs7 z?X8Eaj@X?RJZ0v%SL3hTE<0o6Rhy@qzkY3f^XoN{_x?5KMK??hu*h7RuJ`-gl8B!R zO5Pv)<y$ThEA@ElYLT}UFK^vsd?YEIx;|*D*u6hG^10hhK1I8@tSVdc@&8o4`?<Tk zGcH?)%kSL7n`0k!IAh8F`OGUVb6hixB6|4VKc7*SvUs1e>G4CxOK)B_UVOQ8Yi{%P z=%mlHCOpaLv0d<SGEmGiTlAb>3%k@cdDE$V>#AJuSY!x)uU`M$m3ivoZF!qA<z!a+ zC|>5ec<Xzh*NmbY>}x)khvxj5ct~$byW~8Xvl@xdR-1jR^RH>uaW7wf{m;=Aak*0# zE}0_m^u@8xo!x<ZZ)G@19B9*V&pqRPF1AO_ZR<z#Ia7p|$)vrRVSd8hZEN7Z1-mLW zZ!0{VvFT3Owu#Rs-krC6Ue@tyr>KhNl{{aJ|9|7G3pvYp0u46EAm9VW!`25y_&ZN9 u!o)T(NV6$BpyfGm4n)nCAl{F{Kg@->hZ#$D`&I%A6b4UMKbLh*2~7YhMGR&D diff --git a/addons/skin.estuary/media/flags/videocodec/wmv.png b/addons/skin.estuary/media/flags/videocodec/wmv.png deleted file mode 100644 index 34a7cedf3bdebeeffae845321f900915290de1d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1394 zcmZXUdo&XY9LGn>qe416iFK2C=5Zsg(G)Va-CAwlvKcNJhRyp5#qD@p+dQXH>b5P{ zWh7!=cM)Tg+~ob}!6Y&z#+Aozold9Qz2|ez_xJtd^Zoaic^>7wS4l$&008Voz#TCF zfQ<6T9<LzxG0W=)oB)8G)(A&?*Z48<jVCco8&$idQsQL~SrhKgdO4h8m?BrMOb#Pk zfJbKh_(0cAG5K)GMVcL0F*YiDByk-9F{k$i23bVrJ+zg%yG*GD<m~uA0PAnTS6^ya zoN4Rl(RSqSfE!%~eL}S}q|>Fd9mU;meYsAyTjpCnJ73cm&X_oSK@3V*2T5{SRCFN( zw>q*hF>G9nC0ct+N2W^!QkI}=8o1@u*FIT~$SVq)OHz%350b8`yV+1zMW@%V!`@Ah zr(;HEpCK12WT_ZakQ7)l3yrWOXCw5Jdj?7CQ)rI)D-g6n*tCY-^pOLx6|U_kz+P&s z1Rmh`c|%TR-8>O^hF)O5ECeb#ld@>a=vnZX$jRHQ*t2wx45G8pbJq-9FAbbz+mQVJ zk4MGfA${n_6>1uU8ImpzwiLT^XSZz)2<Dg&N6*+EL1&R;k^0(pFAb45-$svzU`AIl z^%6b0|7zkq`t^Ld-aR)7-=}rJE3V*5Q=a>P`1eY)I@WDnx{9T3FR3g|_&&=R?`AP+ zDtu!_cds{2hjQv?!id&xl-gKA3z>_eH@4K%ilS_eb_uR8vtKE&YJ=tyFms?xT@}xA z9f5&{&k+IM=Y|r~vm^Wz=b9visaM#Rq5QO?A99-(cG_0ZI-F=SWXxzPKG+gkw#|MX zE+W8ue-Q$3nqDnvS|sTbDgh%NbRe?SuvDRo3{dlR_M#)zTP(kazm3dPah@&Dxp^<T zbRdij!YUu^G^L*#2xF;FUID$gIbnnd{27U4j}}<Z<jh38_4M5gDU?u|L)5hil$l17 zeX-6BgRO-%^w?;t`Ubb60g<G}SwRvRyz<UqINIAFF2DfpL8QcxAdm7WCN(VNM2)lZ zIxfg)%lucv*5j71VFVt-KUzNb;R4}R?anvLu|?vl;Ks-&@oI-2*Lt=Kx%*Ow40~bn zu^S|CYPVmg|KL#kG2a@C(!iNbm$8t7XBd1FIDII-BCQnto7NWR<Q!g%aHW!F?W}(~ zP{*(>$d?!TeUE&47RYRd8wELZ>m%4v&~R`wd-z3STf~%lV=m6dIo*<FV8T4=ln?pt z*rFayEE0M^u{9u$%GBTS`zwlt!3?R7#q4<Hl5fWN3U&{<Bgg&Dp!C)cO}!ociD}#| z7y6JU`Mx>R_=R%#$wuIL;?#vnSg#*Fvyj<sQB<|9-%)oifl+RDM(16R83$MIp1tTq zoq(LJ(q8s&oqu|jEUE>&h8XvxdP_$u|6*%xrJf|rx5zwCz#aOKEM^sZx!4H<26CYr zK&*X8gdfFy-<X|W`?w!3S<}NX>3-c(fT0sOj~NwxMAxe4c1*8;o2e@%O71_iX)fzt z3M5Bg(HCh<OkXo4H1{b;-p+gRE@&&0H%WXipz2ZQh7X5QbkapRo3$9z#1uBR@K#Ph z+g`}9#OkbZbrQ0cVin$@)0G$r4W@*yedza!<8L^-X7hE^p}hfjLDf7=gJlWq5-qr@ ztZOo_<RY7-R0%<)Xi+I`U}pX_e(M~HtS{w<^5P#KPgzxrJs$-i{|hboZ&^^1=Hui3 w)X|pzB0bm*xvMUt{U7hs>WlI9KJe{I(VX1n!*;my@v8y=2pGz-$^oDH4+6%A00000 diff --git a/addons/skin.estuary/media/flags/videocodec/wmv3.png b/addons/skin.estuary/media/flags/videocodec/wmv3.png deleted file mode 100644 index 34a7cedf3bdebeeffae845321f900915290de1d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1394 zcmZXUdo&XY9LGn>qe416iFK2C=5Zsg(G)Va-CAwlvKcNJhRyp5#qD@p+dQXH>b5P{ zWh7!=cM)Tg+~ob}!6Y&z#+Aozold9Qz2|ez_xJtd^Zoaic^>7wS4l$&008Voz#TCF zfQ<6T9<LzxG0W=)oB)8G)(A&?*Z48<jVCco8&$idQsQL~SrhKgdO4h8m?BrMOb#Pk zfJbKh_(0cAG5K)GMVcL0F*YiDByk-9F{k$i23bVrJ+zg%yG*GD<m~uA0PAnTS6^ya zoN4Rl(RSqSfE!%~eL}S}q|>Fd9mU;meYsAyTjpCnJ73cm&X_oSK@3V*2T5{SRCFN( zw>q*hF>G9nC0ct+N2W^!QkI}=8o1@u*FIT~$SVq)OHz%350b8`yV+1zMW@%V!`@Ah zr(;HEpCK12WT_ZakQ7)l3yrWOXCw5Jdj?7CQ)rI)D-g6n*tCY-^pOLx6|U_kz+P&s z1Rmh`c|%TR-8>O^hF)O5ECeb#ld@>a=vnZX$jRHQ*t2wx45G8pbJq-9FAbbz+mQVJ zk4MGfA${n_6>1uU8ImpzwiLT^XSZz)2<Dg&N6*+EL1&R;k^0(pFAb45-$svzU`AIl z^%6b0|7zkq`t^Ld-aR)7-=}rJE3V*5Q=a>P`1eY)I@WDnx{9T3FR3g|_&&=R?`AP+ zDtu!_cds{2hjQv?!id&xl-gKA3z>_eH@4K%ilS_eb_uR8vtKE&YJ=tyFms?xT@}xA z9f5&{&k+IM=Y|r~vm^Wz=b9visaM#Rq5QO?A99-(cG_0ZI-F=SWXxzPKG+gkw#|MX zE+W8ue-Q$3nqDnvS|sTbDgh%NbRe?SuvDRo3{dlR_M#)zTP(kazm3dPah@&Dxp^<T zbRdij!YUu^G^L*#2xF;FUID$gIbnnd{27U4j}}<Z<jh38_4M5gDU?u|L)5hil$l17 zeX-6BgRO-%^w?;t`Ubb60g<G}SwRvRyz<UqINIAFF2DfpL8QcxAdm7WCN(VNM2)lZ zIxfg)%lucv*5j71VFVt-KUzNb;R4}R?anvLu|?vl;Ks-&@oI-2*Lt=Kx%*Ow40~bn zu^S|CYPVmg|KL#kG2a@C(!iNbm$8t7XBd1FIDII-BCQnto7NWR<Q!g%aHW!F?W}(~ zP{*(>$d?!TeUE&47RYRd8wELZ>m%4v&~R`wd-z3STf~%lV=m6dIo*<FV8T4=ln?pt z*rFayEE0M^u{9u$%GBTS`zwlt!3?R7#q4<Hl5fWN3U&{<Bgg&Dp!C)cO}!ociD}#| z7y6JU`Mx>R_=R%#$wuIL;?#vnSg#*Fvyj<sQB<|9-%)oifl+RDM(16R83$MIp1tTq zoq(LJ(q8s&oqu|jEUE>&h8XvxdP_$u|6*%xrJf|rx5zwCz#aOKEM^sZx!4H<26CYr zK&*X8gdfFy-<X|W`?w!3S<}NX>3-c(fT0sOj~NwxMAxe4c1*8;o2e@%O71_iX)fzt z3M5Bg(HCh<OkXo4H1{b;-p+gRE@&&0H%WXipz2ZQh7X5QbkapRo3$9z#1uBR@K#Ph z+g`}9#OkbZbrQ0cVin$@)0G$r4W@*yedza!<8L^-X7hE^p}hfjLDf7=gJlWq5-qr@ ztZOo_<RY7-R0%<)Xi+I`U}pX_e(M~HtS{w<^5P#KPgzxrJs$-i{|hboZ&^^1=Hui3 w)X|pzB0bm*xvMUt{U7hs>WlI9KJe{I(VX1n!*;my@v8y=2pGz-$^oDH4+6%A00000 diff --git a/addons/skin.estuary/media/flags/videocodec/wvc1.png b/addons/skin.estuary/media/flags/videocodec/wvc1.png deleted file mode 100644 index 843497f6a9cf3bbf91bdef246f1aeee9fff48520..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1045 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`?|8a6hE&{oJNshkDhCl) ze=h^o2|`;~bRE47RF4QHaYi*wTcDc38RVq%aBsiHJ;@o8uBt1l>o+P%h;5!*y@MzJ zKTE(_=^_SfP=Os4?wKYd@wrJ2A!sv4CO>BT@~83`x)s=qzzCrh$Z}79GevgWHmQvH zclIxyx~u(djC|wXwo_Na@BdM~9<y_rxz+3ZTG?%Cvy_hS{c)*$W9_+Cx$J2-?$<3o zA9q=r=b6vr?sJlsS(h&QZ`>QIzw`g8GwkxGyp50f%spcML@KlTsO0)NlAM<BPB|S; zs$Ni9a(|uK(QiSEAEeBG6#RLX*8DE1A`^%B)k)Rs&Kqa%HTfjeGWU)D{ps>ufzN6u zUE0v)!|l)eB*S3JyRU-0OYL1RemK0sK3m7MylwyDjVG0|dG+j0uM<-}I$`m}0IAs@ z4kunYXLl)X{?zZPGt*vq-&!5+rJZRSHZ_yYGD^tnaAHY%-Q{XtrN-hFKBv2{7CgP} z<yz_3w>$Z>lfJ>0nPrUzS?N<geBAEoy8r+0C7MMhQeMkvsdV>CyS?oZvic>J<N5P% z-IE`i1Af&7N^CXu(z<Xhbw%ssjtPrH7k;s@dCIuaeOX>>K5NmRNR73-E^b+CWZrYE zO8&%0k)j<7ujKc%A6_xr;@<n95Rir|`=4wp`LXwGa1^KgQrABA>B?eW$xL(XR$u<^ z(#Jkgxn#4{?BEHHU7u`NctwqSw%e(dkE|^3uwC!0=yd9v)^cfrk-M3z_U}1g6;6EI zP!u9MAwr@wzsUKm@skQJ%T<e>t8GlX^377Oe%+**|2j{LSl(*0_djxYg~;o<LLT#d z`ufU_8c$j3)@krc{$j{-X3Ho+FY`XFm&&s;7bv@PJH5QM<=ElXc5elDR`G7RF!AyE z8&jrzT*lQ_t0O)yal&KHCo8ISHSIWNYT6sx_I0<cmkoN7;i}Qwe(_bcSmtU^S3$4+ zy6rM+r!W4)cgk|<p;%9?wOg<KS#Tv_=02H+>!STSWOO-~el&O`x7A}GPh`1Aa{dFk z%q-bk-KQV6UHZIRT(&lR&v&(ax0zQW=J(0y@-ChJ_na~BUZJG!le>;(PdIULSAdn7 zcx`;PMXljF?UK7s>?)nLBjV%u98lmB2fil^81TWay)q{lVX$A39cxxAVnE3$z>H^O b{GU-|m7aX@t0~`s*`2}D)z4*}Q$iB}o#Ma~ diff --git a/addons/skin.estuary/media/flags/videocodec/xvid.png b/addons/skin.estuary/media/flags/videocodec/xvid.png deleted file mode 100644 index b835bf687324e1b17969c80618ee79908aceea02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1195 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>GiacE$Ln>~)opZNxk%NfC z`rk_#qY}ImybHV!2;C6cz@qEueL*ORGpcFY0@V%toki?v3{xGC%{lCR#ple4Id`o0 z&lF)wcrL!3VFEKIWUxeq4`P3A*mvsKrTNEOPB1PJb>IU*j-)M@E_Jt>FH|UE$Yez~ z1l53hts&=vJQS-v>mD7SA7Re?QvCR4VV!A<tIQ_nI^T5p=3meD_qX4t+lBQxI!~3N zEqnKUy?txvhFB@di&Ixu&Fx;X@JZlQpXwjE&vut2$EPgbRb9TlI3hDuv;FpC53zGa zJCAHX{`wfl<+(53&G$$VEpy`1Tzlp6(fFHNr&ykp-)VE_@r@Pf`$K1Gyg$D#ta;0_ z!)w%SJ+;eOe=pv8WMBJ~pxWiFkN-XCvy$trp3yop?9k-HIe+#9&I{$&dgdAzyvCPL zb4i3{wa$r$AH8~h9X<9vNqL>iz5J|u-rr~PW#-GItecnH^M37>$3GA6?cKNQ{As_t z&kgxYo4aMMF3ftTxztA5a!E%|m77an;*3bPbsL{#O)*-}led2Uj~6mi-sPu$ik4Zm zuSi~8)Y`>p+4*)a-LDh&2Aqxa)r>ZMKhuhBUFR(6m)&buxp&Dp8-{=8d-?t8ydPF8 z!;RWywz1Vt>z>8#DL2EzxsUzlayk8w@GY}~R&7+?cqYoNE$Z*F9g{SRc#irlowh@9 zX$j|tnL*jlefuOV+hla+>8#J4yYz17(eOJH7_Oz=59#Zk7`1O!(#;E&TT~}zEsFde z=dN`8^p_RiWEZvVe>hipV@jOW;%`z)#}n3M6|Ir-T0d20qG{ik{dH39+Fmz`o=ht| zz9sRH*YVafN~caX%Lo?jX>wT`K8Gju5#L5-=>^7KPbws<PUfzAW;QG6bd>3>D?eVT zuj~-K&3D3(r~6cxgYBBRR@V(*%?-MH|AXP1IjeRw$}lgT@=NLVnT37X4!^HnKVf^k z*Qn@?)9#ZCPbDs!a=QG&yG`|r-b`_uwCdc8qunwyL%p9!7%Yv6l<F>=nS9TYKO^|- zX1k!SdDHm)`Yvqe4BO7M?AWzEI}V#U|1LZH%I?CS#<O;X`peUAy!l+>rRn^Ao9R{= zotyR7tJ6)U`HSA)#(1ok;c3JcnTT1^tJg<*FTZk~$6d`m^t^K}Z^ss+0<WEMk+0Xi zUtu9V|KpviJqxO%JiGUNdiOi<olX7W#hiCH{aCKG^<}}Tl#d%^-sV_nTlY;Va%KN` zWmkFeTho<Ol&8!z{OuI=v21C%OoS1?h-&ZiXLi<eqITOCt#PY5^|r6<AIGUDJEdYx z^pkY0_k>^C^(e3}k*}|6M_2*Vy`zCbw=}C?nCF^oGyeaL^Il#qS|&uzf(iw<%9}** ys3@^B6hYtt?imIWj7sbZ7!alcK_BRUZ2zO^#g+n9c^6<I#Ng@b=d#Wzp$Pz4Fd;|) diff --git a/addons/skin.estuary/media/flags/videohdr/dolbyvision.png b/addons/skin.estuary/media/flags/videohdr/dolbyvision.png deleted file mode 100644 index 5875eb80e7a7859a8cb929fdd0d58726f2af51b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2759 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 HZqz>jU{Orl diff --git a/addons/skin.estuary/media/flags/videohdr/hdr10.png b/addons/skin.estuary/media/flags/videohdr/hdr10.png deleted file mode 100644 index e4b671fe1238cf747a8b8b775fd3106ef4243ac1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2910 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 diff --git a/addons/skin.estuary/media/flags/videohdr/hlg.png b/addons/skin.estuary/media/flags/videohdr/hlg.png deleted file mode 100644 index a8bc078d70d3738e7daddcfee1a332500494a86c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2078 zcmZ9Mdpr{i7sr<r3R}(H++{8m<~HS0X4qUJBe`!bne7#8d)z5`kWngRGqF-Ad0Zx` zP%e`&54j|lXfEZh5g}gnKJW8>-sg{VKIi<-`TYL+o^#F7!CK^i%mDxZAcC^7JPQEq z5#6mL1o?M!39Hu%0N@*s!nl#oV#5_fNf!h0!~jKdWN3h5Km<N;ck#n~wg(BKB#hiS zkIcTTn%GPVl6M(Aot<xMq<6O>ueodj>XP(HH(05J()%8}wQkmAq>q+CoaK3vt_PJA zHj2yMIHxsjr<=#?V~=)MnY^C|AynNaH}I?OB^G7c0wFVGllH5N(77Sdh)BMD_H^~) zjnfvKJB%Jf47<9mWnQ<}Pd3<NSvcSZ893|Szqq8T<6oi$5#oVvMR4Mfii8YIskEN$ zmC<TRX6UgYsl#7aqG}A-t^B6Y_{GNAIoZ1Wm@*H9_atl)g5@pj?KwxP-JG^=CEZBL zSIB8znWB8lsoG?4qh8QqFnTyLudO~HNJHMHmeC4(j#dBm+qZ;;3ikd9)29&yMw-n9 zu)RlSF!&z$fR%`nnmo~sCVT1_jT$z-AFYa~(1~XYsaJithh7~O`sl}echUHV0V!gE z-$hMI1@b)r*bu!=lb<4LAz8QZ7d}Y%dQ;MMBx(&12L*#dLZZ~x`;E5O9o-&dz`d?? zIc+y?7!9839c=5s;WKTC(tYR~D|;$+%;S)-kYKajy-W?6szXXQL{Ev|tWq^Qbs1Bu zV&kApyIR5fnyzP6bX@C#H*VM-z+9&b6H_s-&?qPKY={BIMZYB2)fwo{3T)FLa0b6V zpLlfbh;w`3lxykp2-51@)ItkkT>07F>IqzntwuGeI240@Y|&srT2it;vGOKaE-v%F zVdfcIdykO*mszeS!@oRJi(2TfyW=hJ`;}Z=AyWk?YxcGnTx}Teute`nu339&cyR7a z?zT`ct7I7!{ADmtrttM#U_w9T9#uLeOc_*z1nldio+qQPN!GR;m0(*l(-bKcLU|Ip zigs7spd*8I8JnP*f^DUcEhq`g+?1T)`?GHHn`m|YHmCab`&IEWhSlo<fxNf!I=JSQ zn%O%{ZG=zK_5}ni%xmmp{}R}1mANpwh9BLfUlor)IHC}U|H8hDI3q8k0^Q^&mehd3 zBgsx-8KTnsMF~`z=OYV{7_QWpg1#+AMG2$O%OWx0*c{P>G1+y#1DtK66Y!jtgULJG zP|nWBXl4Ku>wbK}g6{JB>{{}jCao7dtK8#+xj%&(VT;kDLg~{y@%GQQT@eOdKO#Rk zc_#JLY98d><0JUYbvzm_PfZ`X60aTy8gil|KS7s&%d|@BEX!AWx77M>IPeJPILF?n zuVpgF*@S3iN6T>Rz8yFN883%YGz6a&sb;D*&vfcLhpA{zPLLF3QpmYUFQ1&~a(-(g z500cIo;({#H|f}k`y-aom)k#O0!%FkIach>)qQE+I0Nd>$9{jVMEJ5cBooMpn##Yp z#WyeJcwrt6I=uTM_;9w?mH_*EWAX~SolvOF`AY!6LGaJm1E4dcc0~a)${r~&xnFLd z!V&1E#c2Rw?=6(&Y0U2L7_kk)oC91Qk9Q}Of=~(S7OB?~sBW6d#rwij_Z;aJ$dPbA z4Jvgi_Dv*Oid8&ww5;exN+LB5B^Ij-9=9xaT<W$EL$b(D4}yBFSUKv_M&BaT0%2hL ztmws^&MElaoQ~D}OA0g;DYEc&?3h|c*c-S%1STq-oQ7a4aeua|zw+NAlO<#Dxvsuk zKblkqA=N1Vnr5a{WgWJzKwo>Ql|2099~L%$8U3<oec^~S8?Ha98*bNKyWCLe|K10& zkNW;KzKoY)wh{5l<pkc(;lwAYNgh|EuZNA+Jh<@qA$@-~Aigs9>Pjui|BQ%vD3!9e zC#A3A&aA$yb$`diVS=<5$=g(J&i3mEBBZIWL%TQ2G}_cWvg4*W)!4JVbHlhD&sjEF zd*h5UtqSsYVwEv;t`l=i6w@6HFBxoH(jM^~15~r>hnE@SDKD~!a2ppXb3c*x13eLw z+J-uLO$=OCQRQHH23v<ymEecq)VCR-;$M|PCWpk*!?SHv#MAD3R(AloL&Ua5gyD5k z?4deB#JQe7Vr5$2&?V(ALf*1}$r4sx<O^J-A}%oiIF!7#d2?O&Fa^6paJ>uslH#Wf z-kI*wp;usI2H;|kQ`l=%PO#r<!O6n-bd}D#R$L&JBh%HZFE{XjdwE5Rgcvo8o9$Xk zgF5j^j}M}2w*~JDA*Vg<23CPw^?^mKe$8iwsAgj@r43%m^^`KuX&Xt!wMNRbePoky zBjlto0=cgV&L5)?S3JxAR5}^gu<R#w(Qac%G|jKDnxvHm?8ZT&9wa|7i80<z`2l&r zeB=7BhokVGLcOSubFYTtwT~{~lxmu+j}}qEG3~5SRIG4*RaP$9@%EO;r#084S!KQN zw0+}keU{*iX$5&FR3G6?P27f;aV1wiJ*G7;VUvW>qtOSRPNM=<SEDQ0%Yhq%?PBiF zbekC=A87XAbHh*k&uGuHcp5xfr@k2$B5v|9S-_JElY}dHIoTHv7c`VYead|`pP;wQ zUwGVX{eN<Q0@wCGoSBL0KVYw4%C==8{sNr2ra0Y};%WRh-2WloA9CxTXP|z`8}Pj6 V-qzrKxt~`IKp`D0%Pf2o{ta$7)P(>5 diff --git a/addons/skin.estuary/media/flags/videoresolution/1080.png b/addons/skin.estuary/media/flags/videoresolution/1080.png deleted file mode 100644 index fa076b8d2d5b85de00b112d633f3dec33163e0d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1441 zcmZvce>4*a0LRCb<TCCR{Ti-c?y^OZUd*J(%#Sr}wzHbrB0|pQ*NhbD8Mer;*O1F^ zYv%W_;_0O|)V9*X_AqBVOq`tgojKjTd+*%4_j&L8zTfx#|3gP1;kr5pbN~Q=F2cph z3jojnZuS`MZJSxuD0T(_wg)1d&ZA?bGX=BpY`c99CNjUhqegd`4)9-&y!&oi`>dwX zd3fcKQEl7AbQHXM{hiNlcTJ+$L8Q!ks&9+{uE5DRTmjQx;D!M5|M2~et+?m>@Tmm? zS*N|Nw7C<oJ>#3NIG^%WPV!zlp~N7E2F-4bt=s`^Wv>@~bp;F+d6Z?(%*7omg7u|b z_v$6MVcd=@8N?>fj#;k#<F=*CJT-RVSqTc4ki^`!ioZDk>9}D!<t>>jdxM^FX+k4L zaca9yuGG~+TA1*;giXz`RQ@<Y>+$qMb<-|2i<ybSjz?|Spcl9Yd2)QlbU)c{_zBsy zG?z2cRwaHIScL6!F<+UZm^ZcKSXt8LcKuBB_-l-xLjP!g$6$B)g2y!l(G*Y3T4>GB zL5EsY*0(rQetveJFpnNVmzDJ3v{EB$SY5Ys>M;WX{t#ynd<(mn^O&MK{HDOeRjE`S z)gZ!rnTxRFm~;%&J^qSVveqYEo9D`vlNI5mQ;*o%`d}>^_qtB=sr^^b!WK~Pc>PDI zs&Lv?XwlekPRKNFoYf(n%?p}QQQ3@seyhC?C@!$mBJ*b4JgDtG;xgX$-UPY8Ns43E za3)yEu}De@?m@pi!jDpdhw0b=eLds5@fL+hGf7Qrv)FLFwNe&+hzQF~@{|eqrhMc` zf_lL`jnI%!ub?h>j8lJ|l9dy5S%A8+Yt^tg-k?JNr#J2amR9V*B2aT;xB;%1UOTVS zQ-z(;Ao9@BA)NY4fAH{q@7)4T5Y{{M<<|C$klyQ63x|Q5|0|*IIiwd;m8wY_h#1^x zUtYakm<#eY*W{&oGb5zrWj;tvI#}J6|D}C>IM}hhu-(?nSW68w!SGcVDl6LCBG0E| znWY~)sZjy_&V&N~{a2}{icATV%h9ytkV0&?zidjxm$=`HOG*5^M^H`hMwl}))k`3U z;uFshzj+%<-@XV1D;SiRFHmr%Rzd;rL#|^iW{iFeYUaTxgfK5QWVE6AdvZ?a?yPYm zEo8~NXm)x>;pAYOu^=BH%xwLY*VQT?$7uyG<|g)aX?$cPr`091E5}Gs*AhM^=);|8 z6!UWG#)?Jg5hq0;M-&o$*I$~#^GN}x={S;j7r8Lp?mQByBXppl9%@MKUr`lw()J$! zb7Q+&(AUTU*QKXiA}Z>1bb91D)tn@DiaeQ&!hzR@R_m*2Y_o3-!R4m@+@6gc3S1B` z1pfr(+fY(x_bEVjsG$G~@v)O<mn{Z`KRtP>=Y61v5=32jj8E;K_8eSfVft5zI1Wx+ z$%yW2$y&Mv9Z@AWl~E2R1t0Oc1qwf*W$(xK+n=A!Wf*j6%BjJ@-j-^kuyw`B0Sp+! zM$>!oX>(R#tksFB9*OY-hyw@S>seXf#M-d6Hip0@B)qKeIappmoj?MiygLyTgW0HV zb-row5bNYFS?Y(RcHcF?tdwj*3mg9^wbY{<xNS^V4;0VzM7SiZWb6&DgD5066SWUF zjJ6NQe9)yYEPD>;+sR0~ti2Zr?5Nk6k~t3Fsx}Jgs7y;*6Q0SgemTpp#Vxiz7h)HG z*Twi(>^pQL7}~qiOm-NG$hO*;*`3Z7{YyJrh4XcN-Z81(u-Ws-Nucr1`56$f%lQuL mFN6K<v40X^^It|qb9&|Gp(X}(S%OXV0uUFGPTUJu6aNHl!^4*V diff --git a/addons/skin.estuary/media/flags/videoresolution/3D.png b/addons/skin.estuary/media/flags/videoresolution/3D.png deleted file mode 100644 index 9b6c26254fb097fe2f0aad2212d0de1686bd5fe1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 938 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`3q4&NLn>~)opZPFk%5St zy;0@?-3splF9p>ZLOz_!Sa!2$DC#kKaj3QkIdR@<PibcP$9Tpk?E3Q$EoTgrUl+We zs$*Epl<~YXn&AW^CTx&Fg$YxiUkfvv=GQMdk=a8VA}4S(Cv`y=tFna>`-CtCOEfrv z83ihH1x>wm>hB)AP_6fWwn$fmYEAV!)_1M?!QzhimDR;>Yj*rhIKR8H-1W-$r>6?% z?JP_7JF>gIWL@t2uS?DL+P+U;zx3Nh_qKb5+qB)6eSh%Rja^l%dE4~vhqhm9mfyX( zHRfjIX{X*)%QwrW@~`L5UcZ-Tl6U#})7SEKo8OmCTt2<AaGRF<J>xU`<UUJ3oiI6? z{k!^h?e?oTz1}Tt|MeznZS(Y+y!4e*zvr!OS(%e_^u@|+JC=M@+x0!5`jG0X3CioX z_SWxz_SiM@=T$2)`(Gkq$;UUJjFUC_J#YG(C|3`+?4pphTbH}{$`}U5Pc3=Zuh_dv z{$$bYBA<^pyY|Oz-1$5DPs`MsK05xwA&<{?>m{%1ug%s`TI=GuNU1|=cY>FdU!REO zqo<!V4`!9jE!a?ZdD*V{$zL0UK4sVz`God)`5k3`YH%{-(y8#$K$iU<woVHCsJvD0 zREU!M+NiDnGQ>m{UR@FC)+F=Ir1E3iCY2%ucMDcq*Z!X!Pgk!=j_Q^Pyj;TbG51IJ zhZBiyi?_QK=07>n^wdPrT_<JLlP7yxKOGM)aZ&P?xT7|MFYl4}9{&{ceBmjRXMca^ zk~;fA{po|;pPxQh|8Jh_hby;FFP!L~;i0`htRPLdYDrvl@o^QCtJh{9GfSJ?b@T3X z`MJm0H$AxZcK7-2&71Q%-G!6vW<9v`ZTGhmF``?-&F4m+jD2axyVCIC;|Wh6totxG zuDaT8&K|Lg%X@)wyZc;Vwq?QJ(vXY0JK}mBb>x>!3ld%Xd1{pJ@mbGICnhhRJ5i}* z6Z^`)sv9dO_<Y-YGQ)$%yZ^=n<`tF=t_BW#$dKU)3Y6ygU)lXwP0*47mM-fWB_|6i j6fs~z1$K1k)3<-;xpTKS{9siD=0OHeS3j3^P6<r_aZ;&q diff --git a/addons/skin.estuary/media/flags/videoresolution/480.png b/addons/skin.estuary/media/flags/videoresolution/480.png deleted file mode 100644 index 66deb31b80debc7b2bb3e1614e4d480136bdbeba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1594 zcma)-do<Gx7{`}|ay`1p+XxA<-0x*BS?0F6#BW%+6uHJs#+pm0W?mMF-jb0^QZBhg zQ7S^Tu_%d6Z_J%7nkB2fo%eD&@8ys8e9rkk&w0M*`TqGlWH%H-T4J{Z2n3RLbbxz+ zK$~Q~_X%4zf7hy}VS5lrY?CA0)-&PVQV}PqRA1rT1dAgdaAADQnau}9lu{|@)MmGs zr~O_G(<nI`0TJ6B9Bl3Y#XWjxbz=?L=x=<#CQ$3Tog4`FAE=1`9ZT($B2Gj7r}YF? z9Ec|IhbhQpIF;;@lugt4G5fLh&s2dzETg#YA^)ML(`a$sYY%5FZEUo9MP#-yR7<Zn zOt&INPlu>gf%U5b*Ad{Xx@@CL4IB@BO0f2h=yfMz>DDW<CbASwHD=#VTyCa3_E*Fl zKUQB3<|U@!_5rygaw2h~5$xfxiM(5D+U(@~)Ns~9<atF++gBSolN_drmKPk0#L#Fi z@GElk{NWYt9w64k`GlbTioM|rx^;+0f5aJG$!id6lZHsP4V=)?V{cm2t=0UP39y2Z zjoZ%dVdkD(ka~|VbLW+>e{=E49<nsN>ZEKbS8B}S=ZDR$Pveh4LxFdx{)paO-i|ZD zi3JJe5tRnK49VHmRUQciUaP>Ex>4}U&Gg*Pkuh$+0cYLVfEw0_mCX$U+$~^Pv%TGM z$w;y+m}r~V|8eFj&lGifC4PZ99ok-*W%{+Pr5;=Eqkz7+T9Va<WEy!Z5FD$MX-5ZQ zr!Mn7OA5;-A9WKrdU%<e@B(_oSKOzW_a3m_Wn+aZq>Rgbx1aa~-PyPD=m57|Ih#LZ zyH3~8BbV_)cl4?@97NB%v%n+EHwLgR53k3@X~Lx4H>khPM^!%<pDSK!mb-+T`UNtu zKxz!cqJvsU0goGFy3&qwsgCzX7z+87L5B#R^f!=mI*<Zlj+z-@CNFoqo##{Bm0@04 zTAh{LRX;Rn?9NabS7$b9jLefeh06#?t^YpK=ve4pB1HM3%!X?-N-~^0i3-PA$QRwJ z-lp?9D05MaJ)O$d$1L%CjUkhLQ?b(V(NY5Z%CsUvQJj(}$Ty2cRiR6Rt!(2=al6KS zDX(U6w04*XdT3<;a~?wACCY0eT%aA??pBXA`vZz&3o1B=aLc8I+0pEy*1-!p_g&Ti zJz+>@ylNe*-j$KpR=;ZIs)5)GRm(}1a`5p{VwAlMI#pFstz&7uvpjx9Ll`t&-8cL> z>g1O|zG|QJ2mrt&{bC}ZzR@+Jwa>hHpZzdZr2tB1S2gJl{dzdx%Fa~2Vpo*i7hVMP zBoWe=v9%2d_4jQvpDqe<<-zNx(n6YZF9`Bl0HdRVwcZ%0#jxlj>A9bnZ|&Y%`pTF* z^$t{v2F&6Fn}L^2OmKfLMu?A8H;>otU}+r^ZNUSR4Qh6iS+Ya@G4r={YZ8npFEb#n z`+Gi1IHV7F5|@G}58#Ez^(3eIw{~Nb3b#>hD72l=57{5M%g$pju2>xoq|J=yU{32> z^$|HPPXxyp{XoS9&AT3?%sC&CkwGJ)6<nACx;tKRyRwlTWx9JTi^Cmv61w5p%x4=W z)r&G<uYgc*@#RtGbKkQqTpA2K)B3ck;bNBs(Sh{BT`Y4HM)crEZl#OUwRgb0D%)4p z4LUl3lh4H_Z{qg7fuk8lwgB-1IcZ9((TY4Omc-qY;?}(>BMEx{JbS+{qHx=kf;M6! z()p{;TgQ+B!zD+r@QNcLHJu;>@$G%|r#*|YK;t6Jdr%Wm>+HO~aQkBpu4y2?^@Vt4 zW}&HTd&rqeb11P!jGf93CA}ZCc+FjZldk!^HMW6J;<B+y^?!6l*@be{=!5QfL%ic% zoYhQcdP11eJ*f%k#rez=@Zr^;@t2}I9?*otp41tMf?RYm(!U4EDjdtvyEb~S#k`Jq z%JguGXb>S?{k{!s;;tfA9LEhQe!u(%09pJ=4*1_tZ*a+GNL4ut&|hBE6UlTrmH%^& eRm3fogqvEOFkn$mzs>ib0CGHng4Z4nNc#=Qf9p{I diff --git a/addons/skin.estuary/media/flags/videoresolution/4K.png b/addons/skin.estuary/media/flags/videoresolution/4K.png deleted file mode 100644 index f3d13bdccf1ea8c6eda390d4ed6cae696b231b8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 814 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>`zIwVihE&{od+TE1BLfk) zho}9d7O+MwVAWvsQc!i^T*eZ_q{#^6dqsGCuzS;|oX??hY-Yn3+5HxbCdt1Q?*3lv zD0`r?ZYQe(`w7OP1{ox30`n6FO9wtE_2ks$oBwY`M9DE5v+HPsl|4}0HB~!Q$7rEh z@qtZDcr^F1FYWquf1~y9s=G3$`HxE+)2qDSCR1s!^TP@I^JZ#if7;DEIg$Br+`gx? zU3#6*=xSGP=)8C~Stt489QkB9^`a*SjMB{eH+HPDv_ERo$8||YLcM=$_0dUs-k&XA zgne3Jw$1j%*4&jFub*0F_Rpv*muL3=u#dBT>uS69MF!=ru|99JIHjlbpZQuIOE=G@ z1s$Qw4YwKQor|~bbL_Kzl51&E>Z8i#X?AMzVy}Il&(DoIU$)6mcd5_m_`XP?>FcdU zC%#Qdo|XS1be2r!*1|U-nmSP@kC{Bbu-e3O)smlc%72u^2E7QKCX*SnZ`aR*?>_sR zotmiJdpf@QgsPft%9<xTxG(MabEEM1QM<5@m$dR$FM6}$ysYNO1;@>HwtZy3zI}Jm z*V9(7R*0|tZCHK7`qR_rb6%ghn7;MnrB_9lmR#BNX##J~{;O`*@;AS2NbHcYKendy zU){bMZNv5Xs&)5|^m%Fee4g^yx@lF(`Rf+f|2|VavGKpy!%G6W7f;K5vwp65dbQQj zKS{9xru=`Na$CtJ&C}5cmD$XHbW?7s{pCZo>!$klr+#|%zG}(Tqd)hhO6-0zUD}l0 z>s9oL!sz=(CoKcikFTxu-sRmR^CMsUrP_6exiWV%-rL_-z1{y*hX35Elg@g3rNrwu zL?73DAE%lWFrVLkQuz$EO;1l1Z(HMV7A;W~F~Ablf!e}a^@z7>Gc6e)NvBahVS!}h lOq5gyN_=SPvhmRmX5GAJRu6O|;(+;p!PC{xWt~$(69AR~aA*Jk diff --git a/addons/skin.estuary/media/flags/videoresolution/540.png b/addons/skin.estuary/media/flags/videoresolution/540.png deleted file mode 100644 index dd6c3823d115b3490e345c47b8a16334ff4635e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1541 zcmZY9c{tPw9LMpWMTv$`iAF0EGa0F&WLlw&X2#Vx#!=2hwjt4z=FsrSx#XTfj@gV` zj)uya+~<nPncK`P85Cl3HLHD|efFt+UeEJ=zJGlFen;ZW(I9aJaR2~7SPaS%0E8rW zf1;Sk?k%Y3zXAZF-B{EmYvTLG^qq%U#s>|Sci3-gWrqKO+2^&-E|?yq5+{S%cP%(c zN##>3w?Mc@?q>^`o@vHoSakRD#B@(tkt)!;DwPbx3I88}{N7aFO4dVKXik@#5f@;K zEA9OzAgbn28|*Mu3=}Vnfd_%V2eb=)2O(|WYxjyd>#IF<HHrQpbR~eon+x5h=wS2K z&>Ig5AKu~&Yp*0ucQ5q5v{w1D;{9$INmf*`(X&vPfIMlu8{=hA<2J%--6B#K(mo5> zj+HZJmD(nJ8;oP!B5cqf^rh`DtZvKq6%C|XeIN#b8NsO4IF9?C2EL4`R70A-V32d9 zO6x?p=7<>Yqa#lA`ItttpTOs%cAh3^ET7xga|?5QR_n!_<DGHt!IObTnu~P`B`u9? zV;!{$eqc)uM32`wV!~T{{2=XWm{`1g;!}92dqyQa!6sO>D)Xc~F{B+L?N0BpSc@RX zB$!(!mFO_qq9~od6DxzSFmVw62x7f23>%Z^&cLl7cAC`%Mfg)6Ipb%n-dgl>{d5I3 z>vV6aM|up(7@*qe$)So^!An(+p(SWcvx}O;YTnYtzJDn5!PBsFGF4T3OCpu0YCHty zTTZr7`YqzBLrlxCx*HvS5d?1jo=6&e{I&M$)Bf9R0&gwfR$`{36f>Tn>&OC6=Vx0w z1lqr--Oz$+JRQ3-kNiBhREVc#e0{Z(+swS#;fD%S-mJ=BtX??Edlz^r9?CX$Niftw zE7I5`*D{7Kx&ODuJ}ud<0OjzxXc_GWELLID{_*upYI$zSo78Hxrj$nO*4uJJosms~ zX%pF@^U>hV3*3ob{JbZV6|Ma~<01__S`&}fU^?ohDu+82+Z%9oCbgdNUM{v<c?uUj z;X6}A7F@TMe(@>BBLyjrijx`mGg2OBmgybEj(`bGGqJ?;X5oW*3CF5jFKWFa<?u@{ zS)Yw5<>4IlA>s>?P&sRJB#QamGN0Zh;ay{5ND44U_AJ6qX%$V2A%jWnh#{H_tyWM8 zf+Y@qV)ee}FJIa`u)UAkA!0>nvur_iymVShY4S(k?UlSf;I~0|YXYrZAE-Mqu}46K zS&n}|1Q6BYngn48>%!x&V61iZ^nqiLKm;x(O}YLinDlW!E)wjVa;;hpZk?MoQF~Zu zqf^>EE^H#_7{Sop{|o4=5ON!HO)$8>wDf*>7u>$6JoI@@ZXBolC)TQ|oAwPOFYc#j zUN*vkeM@9!^<5VSrAaf~k?0C<w4`@9o~eOfL=@9a2NgMOV)J&q0yx&$LmeFeZ6hrW z*aVI_Ox6?buWjKgP!zs=Y`WqgG@p#ijyi-agr3&Q{8f6vL8VT5U41^O)u?+@#(HE+ zKDE0t#D@dx9f@*rCG@$m7S3hQvPlbelNo<~W#!~-eM}aCUMxy3$l>?<=hg8%i!9DY zIk}`eo_<uOOFd|=gdQf3!OrTQoRvKvVl;{)0W-rJ=68^6b0H^n`<QizXqo_m8MFVL z0s6wqxtFEBbs(Ek;`t_IRAt2|<03Y;iiUdaPp(ubr$bhWXU`n{>~0d9cX?@<5{<FT z9c|TzZ{>%qfY`=29+KDkDF%O%tF_D1;h8vov#;6W+hJ9+AaJtjb<bbmN0;4AOw_tw z)ZarWEiHl&hB~DMB*k`4^QXC4JhQ&O?l~D}2Dy!w52q!t2%)aCu%__$Ba_EPcgpar z!gOyfTN^&KAMtEQR}MLaG}0jGuTJVUNTt$5xxjx6B)|6sQOO_@DlsU@e@8*kvKbGN mh#!LZEssA&aW|1s9DQdIzm{VT?AG1916U(-RLNzR;J*P_!|IIy diff --git a/addons/skin.estuary/media/flags/videoresolution/576.png b/addons/skin.estuary/media/flags/videoresolution/576.png deleted file mode 100644 index 8a96be926c3d2947c99cf94b65d34ef39ccbe21a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1451 zcmZY9do&XY7y$6m+(|{Eka=xsVd?3yERxv_kx4X>n^y=~%qUbIjbTRf=o;lQjk{d0 zlBrY}CJW1B^T?eMU7<`#kL#TNxZOIxbH4MP?>pajzQ4ZI<4%}O3fmO`0Kg`!9SR2k z$SAJmBzfRkWYr7M0Dv3;i?TjJ63#J1QKg4ek4QL7877bk#QWIm>R)ika?sU(wZ(2d zaZMwT1Dt}@=)X`|$a%P8@3=YF6jKPzKNTh0(wz_~qYL^Kfbf4UoOisBp4xCPZ6gS1 zFZ+A5#lg?0h~q(0IrLxchq0fwavkk@5%*7uILrUA!^0FJ6vhULz+nq_1AB4NqXX<D z)tQ{(-;u6cBi^%TN9u)RQEG`*qmH5j6wCSM5cZsXV6kvGTA~$;yii!IN0=<)Q_X@* zlr!G-<H053(Se9^_Y!IR9+iSSZQg!-W%KyMPY|CY7p*UASbY=33rYS4doYo6r;Kbk zGYu0Js&wW&TAmiHVjt`l2qPwT8uSB)DM}7!;OFsJt=pvTT#Lk2{?3Nu+6g@{|2|#@ zH<TS`xI3`S87C1}(78O9(|E%G#Mm;S*)<P15?a_4`P8d#p}K$mKnGN!KIXi&Lk~8x zC6l6=O3cU%k{*NzqpTC4CZRdyyxwjh&TizKEU3#$nW*dnt3PHEU6p6djbG}vG7h^l z7+~^%?ngu{HRd95nV5>&PKjk)W|H*g?!vbAYz?ov^hA$w<R<F$pZ-pgY@;&P{uBLF zw_P*y!;Q|RWyLyB5h7Tc?ze2Yg6nzcQ4208Cy&W%jL!9Gni-Zqr1sX#-r4AFxLk*! z*5N>%VY=fR(3xfK+bUbJ0vC*7#$eGSMM8$_DuoQwgN9^Vut(WLJ>aiOW{gH}ru;qW zU6Z7#bq%oVb~k_mlnO1zuN^ArdFtkva>Kd1Vr++JP(u%$0fs-DOME-z8ZP_#%wHC^ z0%VzV7u~itf5M3-7WUSf!OMwsp2pwk;Km+_4_QPOxEwlI1?!@Xum%=o3Hj9w(-NHe z+PzieeKs`2&bEn2b(jSK$md8}8}$y#C~)`V)gJU^6Z;5z>WYK#tXxmvMSmd3=II-r zYxjmv$=Ms&Aj$-0@$tRz)A&_`c#758!{^NY$a=B^f0px_3;eR)ce}^<=G^K1IA^&@ zBM%e@lF&Lnc5%B{Vn>5EZz*s})0`cjhcmPcOFX9@<E`T_%NW%OkXgQwUMGM^e$+ym zIUBOVvSrqAyXcKq!9@ihUJr3~%v!w?mjY7sle;}Awu-(N8kAyLqX=?ugdVE(1)au9 zimOQG(Jq1DE<5QAv7tq*yJ79{6iGVu!F<*;uPX+6UGfRrJV<Tg2Ob~G(F`|#+4j}u z1wc5M{vPXkv~suUT^;0aO2*eZofNg(=G2(MA+(hyzA5Qybp$fQ^m4#RJv|e2<>h<h zQi>1j-hcqUD&re7lTzlMAGR(ycXk%7?Gnzulond*EpG=FGH(W8%fJ${9QQsQ`i31n zIsVM!k~ch^s`-VRCYmo@ri?!)%iS;T$dg(cK)R;eZpkMonH1p_H5T5U8w$b390^G! zxQVv$vTupcV;IpJpECxki(>Hx;A%+pXy+o{`TU4FnwsUmT!U9DvW$9c>K!SIhz}69 z??QhRY3-}lm$bimq`erNQ;&iKrCR9;qE(?jh3*VQ@`KtY*JIHB%Jk`1s1JJ~{_uv9 z!5jFax69klWOk)2ojl1`vU6jIKf?(vzO(8*5mUu+*UY@>a+81`9Z~!d3H$k5IT`~~ uMgX$beW%@$<_9E$ulxegA@eheSjRxRO@~!l*|+wD0a#ln)ZL>#*Zu`WGMiEW diff --git a/addons/skin.estuary/media/flags/videoresolution/720.png b/addons/skin.estuary/media/flags/videoresolution/720.png deleted file mode 100644 index 0b6406004affc5e3335ce95b3d562704863b9b10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1317 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+!z>G-gvq=hE&{oJLhfXDg%)Q z|KBBzYbIDT$u{;aP`)5oz#{ADenBvSMRs9Nfo$XN)m2Wy`__5sygxl(tn<;y(<j*v zPLE>vvqy-@fe#ZhJVAvG_fO3(*XQ<rew=$Ib4D10B?vyybkmmI8}UQ+N($eCNJca* z3}~kI1?DW3+wgP4B=3cNKPSaZ>hs?^>2%=Ko8ES>-mHIrA#`8BbMY$v|J$a;<cO42 zocC8YzqjqHz^tXYd--J|ub6%4REtoPule@(wAggz{ZWggpGBmb_y0XmRktopb>d<j zru9pUrj#Vi+$xf^=<m8{%d)g1yFY#V;D5*SYN1P+=c>@fnFXth)Tc(A)cY@e^K^B; z(aK30Q=RUqch_mACZ9iaQ4wg!Q-k?wOS8S3SN>iRcXQR>>g9zK<x<yp{VLr%Z%yRK z{qAw~mZlSYudX?we%y3LefHvS>AH?rt5p1E98T?WnECSQkDJEP3hveScCb~ye_8a) z`!8>r$jaWTQu(<u>u$VDHM-6Bvb3RM+T2_DA@+&i7hj3odb%@f|N6bXz01$I$!0Eo zJt6PPPsvDQ&Sks1q%EyuH)k)MHgD2Yt|wbh?YFEv{bo+z0+$u;ORZiC_pG|Nh|68< zgtXGsHF~0N4_Ac#K6d}U{pp8Fu0jiC(r=y!JDO&kZn<Vx-Q7KkL8UFnR!pc1-yZ1f zX21Nj`m(@tr7|hCKgGI_pB4<>Jc<3YoA@sGqEAnnf&=<qTi?(qc3&wKW*l$%rR3Ve ziRQZ^DmJxz{NjGdR<tvSFZ^}MzM?07;W-iaPJ|lx+iU&aej@R?R{J^WyTWgL*XFDa zdw%VR?X=S4Du;5{ea<@NRayIM&&GgM{(FU<_Lh(KFqW$o_nepRlzJ2TNwU`XqkYkt zl6lEF_X^(c)SDY{&suT%LB>zt_q6OinQFXHBj10Hd5o!D6LXkxM%|MAGH<1pn@>FK zqj~)G<!8Lwtz7><9WSkUck#fC{}Ubuil1BaRAM?vy{+s;yP_Kr$5?f1wf=7?Q!DB@ zDed;HPipry#|4~bIv-C@)Vi6j-Yz-Oyhr3!*DoHQt<&!bw68lIJmsv{qH`PO+_!OG z_RsE40PlA%@8)6^;kJKz7k|1Iz36<uDV+6d=;5^=Tpvu^H+P-w`>RV*R$ZJuS0?gx z)LK2$Fxl^U<@b)AzJ7b9TFgV)cRKl3>sNc8OFU${WLatN@_cK)8U;`Nuspj@TO_^~ zTwZ)_LR32Iuev(!p07f;N&-T^uk~B)RbyGT<eaw9Oqs~XH_eUBE!&>;E_$BavW}(l z@kjS)@404T|I`IfZd_3;I{o)5!HqJ9KOLK(p0n9eb@RlBd%kbp^gTCVQJqQR3yai0 zuRffgcJ5B39h;s<jmlJ=RLiHFKC15Pj%lurGwMxOejmpg_DTKHPu_`hb)KHTlDuO2 zE&G;0_dAsW)vrPwo$GuT^PGu#;XeJ;L}TeH$E2H|rOn!3RxX$5pSAYwisvhzG)TNy zkzV(WbJ5=IOeYv&ut5eL8U*jEpTAhmBZUuEtTb>|BpNWK5K;6nJZZT3kw2n*mqcUy SNpWED$KdJe=d#Wzp$PzV)@=v? diff --git a/addons/skin.estuary/media/flags/videoresolution/8K.png b/addons/skin.estuary/media/flags/videoresolution/8K.png deleted file mode 100644 index de46f91e9b3af199019a1710b90ea820edb10580..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1277 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>&GIB=cUGvp|Y1$=lt9;Xep2*t>i(P=vF< zBeIx*fm;}a85w5HkpK#^mw5WRvOnUI=M)oMll|~D0|QH!r;B4q#jUq@ALc%a5NWu0 zT8npss)6bRArDSZAiOE$#JQv;z)2%9$mx=kS3=#Bg>xPE9Xod8;2FyxlPKPgQ4uwd zpMURBzx~#X;nt7i(F`2y3Nj4^42Kvw*r61dk11SmZe7%#YtttmdOF+UF#}I1!$XD^ zW&ys2BUd@M8GbZ7^rB>zYUwecF~pen@%n7{Bgrm%j$b#oe*1sVwEgq9-pRYVO|JjO z{>+>0tKP1av3r>CBVmu*k@FoDt2hrY`t!H?=K??Wd*`Qb;{R<X`0;@O7yIGJDG54J z|L)cP+<VjE_fD^GA9a=9$s8-(^+~Mfmyh!2_@BjLP8;*X>i%0P*;U^;fBeb+|AC7> zz6`K>oM5qfR)UXGQe>T+(xdww5$Yc+UKPr}pZvCdT1JgU&!K}GoTpR@^`@R>|7!WO zz`y#|ah<&K)EP|$1_c)1awlcHX@9!;;kR9Ui{r8uZ7k$yZGL!ma)HdJnS0(%5%)}2 zmAb4FlilubVIl9UZ}hD)HZf{Zpy;C1W@Dw6=0;!tKf99msH8i;{!u@T{o#YzdrBqp zbgHblH-<S|NF-Y1Ngq+J_PyRcwKUr_Pe$+I1DiG4=Y-0xi*H(Z!e-TS_xX!&E3MhJ zRmwcEae6^PnA{|bJ;&#pFFbpy{{NbeQ|`yJ`M*zGe{}~Z_a41lDZB5Q)#iRX)Vcj* z^c}Z-af?nCEI;|`>|4Rx`?xQ!OTO^(vyRN;ZM&Z@IUCjE+nvpyd-!77?H&u6Hm%2Y zk1d|*U3Zndbm!X!|8Bl?M~PJzHiw3sPjRV<op48Y+bp-g;_p+wZ@(h@+4twnt8We) zt+{IuBG|Y2)086**49L<UKU=yS^o00>nhvWlY+||uWaReD9~qYsC~F+RYr{YkGnZ# zPmfQ0*des>&9^_V3)J7ZwjDO@`xtxu#J^H|mDzQ3UAMoMJw3~~`^`O3H$RJGs=LLP zyqwjUb1?imfB83A>D{|i1^LeD$mF&&#l9=G6zT6znO{43XMsVHyltMjw9(z;h5exp z78Z@G65Vb`KYecdF8=P_4gMF;-x6$TRz4?ot41Q-)55P(Ea>}R>D2`rLo6g>%4!$A zG*FmrX8ZMfLz=PfZuMQhvR)4pEN-qj5&UGe1wXH-*~Rlw+{U_x4$k~hYgu{K;6ds8 zH_6+d{Mq|#&64v=AKvsit@NvQ8Mp1`qLMjbyypyN)or-0+y8Un@91x5A7>xUn%cgi z?jHBw%|&1IzX;rwySC?d)PBWxsw-z_M!N3bY$LI1!*k1ha_?`Xt=Je{_D{L3)*$Gv z&*|(r{}u)S!{6!dn^T`$Zdbn+nEPhp<k)Q|&3Qswm|GTqeSR~5HN)2enkOAV3;{kc z1<Ipv;Rf!q(x%*;y}b{5nBlSy9&Mc&`cZ=AfFvU8D#(CK1ZY-<=V-X_fuFxwMXtT8 Vczb^NY+ymf;OXk;vd$@?2>@ciNnrp0 diff --git a/addons/skin.estuary/xml/DialogSeekBar.xml b/addons/skin.estuary/xml/DialogSeekBar.xml index 5e870977a5dc8..958757d98bfa1 100644 --- a/addons/skin.estuary/xml/DialogSeekBar.xml +++ b/addons/skin.estuary/xml/DialogSeekBar.xml @@ -49,115 +49,14 @@ <label>[COLOR red][B]$LOCALIZE[19158][/B][/COLOR]</label> </control> </control> - <control type="grouplist"> - <right>20</right> - <centertop>125</centertop> - <width>800</width> - <height>50</height> - <align>right</align> - <include>Animation_BottomSlide</include> - <orientation>horizontal</orientation> - <itemgap>5</itemgap> - <visible>[Player.ShowInfo | Window.IsActive(fullscreeninfo)] + !Player.ChannelPreviewActive + Window.IsActive(fullscreenvideo)</visible> - <animation effect="fade" start="0" end="100" time="200" delay="1000">Visible</animation> - <include content="MediaFlag"> - <param name="texture" value="$INFO[VideoPlayer.VideoCodec,flags/videocodec/,.png]" /> - <param name="visible" value="!String.IsEmpty(VideoPlayer.VideoCodec)" /> - </include> - <include content="MediaFlag"> - <param name="texture" value="$INFO[VideoPlayer.VideoResolution,flags/videoresolution/,.png]" /> - <param name="visible" value="!String.IsEmpty(VideoPlayer.VideoResolution)" /> - </include> - <include content="MediaFlag"> - <param name="texture" value="$INFO[VideoPlayer.HdrType,flags/videohdr/,.png]" /> - <param name="visible" value="!String.IsEmpty(VideoPlayer.HdrType)" /> - </include> - <include content="MediaFlag"> - <param name="texture" value="$INFO[VideoPlayer.VideoAspect,flags/aspectratio/,.png]" /> - <param name="visible" value="!String.IsEmpty(VideoPlayer.VideoAspect)" /> - </include> - <include content="MediaFlag"> - <param name="texture" value="$INFO[VideoPlayer.AudioCodec,flags/audiocodec/,.png]" /> - <param name="visible" value="!String.IsEmpty(VideoPlayer.AudioCodec)" /> - </include> - <include content="MediaFlag"> - <param name="texture" value="$INFO[VideoPlayer.AudioChannels,flags/audiochannel/,.png]" /> - <param name="visible" value="!String.IsEmpty(VideoPlayer.AudioChannels)" /> - </include> - </control> - <control type="grouplist"> - <right>20</right> - <centertop>125</centertop> - <width>800</width> - <height>50</height> - <align>right</align> - <include>Animation_BottomSlide</include> - <orientation>horizontal</orientation> - <itemgap>10</itemgap> - <visible>Player.ShowInfo + !Player.ChannelPreviewActive + Window.IsActive(visualisation)</visible> - <animation effect="fade" start="0" end="100" time="200" delay="1000">Visible</animation> - <include content="MediaFlag"> - <param name="texture" value="flags/rds/rds.png" /> - <param name="visible" value="RDS.HasRDS" /> - </include> - <include content="MediaFlag"> - <param name="texture" value="$INFO[MusicPlayer.Codec,flags/audiocodec/,.png]" /> - <param name="visible" value="!String.IsEmpty(MusicPlayer.Codec)" /> - </include> - <include content="MediaFlag"> - <param name="texture" value="$INFO[MusicPlayer.Channels,flags/audiochannel/,.png]" /> - <param name="visible" value="!String.IsEmpty(MusicPlayer.Channels)" /> - </include> - <control type="group"> - <visible>!String.IsEmpty(MusicPlayer.SampleRate)</visible> - <width>115</width> - <control type="label"> - <width>115</width> - <height>60</height> - <align>center</align> - <aligny>center</aligny> - <label>$INFO[MusicPlayer.SampleRate, ,kHz]</label> - <font>font_flag</font> - </control> - <include content="MediaFlag"> - <param name="texture" value="flags/flag.png" /> - </include> - </control> - <control type="group"> - <visible>!String.IsEmpty(MusicPlayer.BitRate)</visible> - <width>115</width> - <control type="label"> - <width>115</width> - <height>60</height> - <align>center</align> - <aligny>center</aligny> - <label>$INFO[MusicPlayer.BitRate, ,kbps ]</label> - <font>font_flag</font> - </control> - <include content="MediaFlag"> - <param name="texture" value="flags/flag.png" /> - </include> - </control> - <control type="group"> - <visible>!String.IsEmpty(MusicPlayer.BitsPerSample)</visible> - <width>115</width> - <control type="label"> - <width>115</width> - <height>60</height> - <align>center</align> - <aligny>center</aligny> - <label>$INFO[MusicPlayer.BitsPerSample, ,bit]</label> - <font>font_flag</font> - </control> - <include content="MediaFlag"> - <param name="texture" value="flags/flag.png" /> - </include> - </control> + <control type="group"> + <top>195</top> + <include condition="!Skin.HasSetting(hide_mediaflags)">MediaFlags</include> </control> </control> <control type="label"> <centerleft>50%</centerleft> - <centertop>125</centertop> + <centertop>120</centertop> <width>50%</width> <height>60</height> <align>center</align> @@ -170,8 +69,8 @@ <control type="label" id="40000"> <centerleft>50%</centerleft> <centertop>175</centertop> - <width>50%</width> - <height>50</height> + <width>380</width> + <height>60</height> <align>center</align> <aligny>top</aligny> <label>$VAR[SeekLabel]</label> diff --git a/addons/skin.estuary/xml/Home.xml b/addons/skin.estuary/xml/Home.xml index 24d032df238ee..5ac611a33f088 100644 --- a/addons/skin.estuary/xml/Home.xml +++ b/addons/skin.estuary/xml/Home.xml @@ -1120,10 +1120,7 @@ <height>70</height> <animation effect="fade" start="0" end="100" time="300" delay="300">WindowOpen</animation> <animation effect="fade" start="100" end="0" time="200">WindowClose</animation> - <include condition="!Skin.HasSetting(hide_mediaflags)" content="MediaFlags"> - <param name="infolabel_prefix" value="Container." /> - <param name="resolution_var" value="$VAR[ContainerResolutionFlagVar]" /> - </include> + <include condition="!Skin.HasSetting(hide_mediaflags)">MediaFlags</include> <control type="rss"> <animation effect="slide" end="0,90" time="300" tween="sine" easing="inout" condition="$EXP[infodialog_active]">conditional</animation> <left>0</left> diff --git a/addons/skin.estuary/xml/Includes.xml b/addons/skin.estuary/xml/Includes.xml index 294f13e800217..b649528a36b84 100644 --- a/addons/skin.estuary/xml/Includes.xml +++ b/addons/skin.estuary/xml/Includes.xml @@ -339,37 +339,47 @@ <include name="MediaFlag"> <param name="width">115</param> <param name="height">60</param> - <param name="visible">true</param> + <param name="flag_visible">true</param> + <param name="texture">flags/flag.png</param> <definition> - <control type="image"> + <control type="group"> <width>$PARAM[width]</width> <height>$PARAM[height]</height> - <fadetime>0</fadetime> - <aspectratio align="center" aligny="center">keep</aspectratio> - <texture>$PARAM[texture]</texture> - <visible>$PARAM[visible]</visible> + <visible>$PARAM[flag_visible]</visible> + <control type="image"> + <fadetime>0</fadetime> + <align>center</align> + <aligny>center</aligny> + <aspectratio>keep</aspectratio> + <texture>$PARAM[texture]</texture> + </control> + <control type="label"> + <align>center</align> + <aligny>center</aligny> + <label>$PARAM[flag_label]</label> + <font>font_flag</font> + </control> </control> </definition> </include> <include name="MediaFlags"> - <param name="infolabel_prefix"></param> - <param name="resolution_var">$VAR[ResolutionFlagVar]</param> + <param name="visible">true</param> <definition> <control type="grouplist"> <visible>Window.IsActive(Home) | Window.IsActive(10025)</visible> <orientation>horizontal</orientation> <right>20</right> - <top>0</top> - <height>70</height> + <bottom>0</bottom> + <height>60</height> <align>right</align> <itemgap>10</itemgap> <width>1900</width> <usecontrolcoords>true</usecontrolcoords> <control type="group"> <width>150</width> - <visible>System.AddonIsEnabled(resource.images.studios.white) + !String.IsEmpty($PARAM[infolabel_prefix]ListItem.Studio)</visible> + <visible>System.AddonIsEnabled(resource.images.studios.white) + !String.IsEmpty(ListItem.Studio)</visible> <include content="MediaFlag"> - <param name="texture" value="$INFO[$PARAM[infolabel_prefix]ListItem.Studio,resource://resource.images.studios.white/,.png]" /> + <param name="texture" value="$INFO[ListItem.Studio,resource://resource.images.studios.white/,.png]" /> </include> </control> <control type="group"> @@ -378,154 +388,158 @@ <visible>!String.IsEmpty($PARAM[infolabel_prefix]ListItem.Premiered)</visible> <include content="InfoFlag"> <param name="icon" value="lists/year.png" /> - <param name="label" value="$INFO[$PARAM[infolabel_prefix]ListItem.Premiered]" /> - </include> - </control> - <control type="group"> - <width>115</width> - <visible>!String.IsEmpty($PARAM[infolabel_prefix]ListItem.Duration)</visible> - <control type="label"> - <width>115</width> - <height>60</height> - <align>center</align> - <aligny>center</aligny> - <label>$INFO[$PARAM[infolabel_prefix]ListItem.Duration]</label> - <font>font_flag</font> - </control> - <include content="MediaFlag"> - <param name="texture" value="flags/flag.png" /> + <param name="label" value="$INFO[ListItem.Premiered]" /> </include> </control> <include content="MediaFlag"> - <param name="texture" value="$INFO[ListItem.VideoCodec,flags/videocodec/,.png]" /> - <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.VideoCodec)" /> + <param name="flag_visible" value="!String.IsEmpty(ListItem.Duration)" /> + <param name="flag_label" value="$INFO[ListItem.Duration]" /> </include> <include content="MediaFlag"> - <param name="texture" value="$PARAM[resolution_var]" /> - <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.VideoResolution)" /> + <param name="flag_visible" value="ListItem.IsStereoscopic" /> + <param name="flag_label" value="3D" /> </include> <include content="MediaFlag"> - <param name="texture" value="$INFO[ListItem.HdrType,flags/videohdr/,.png]" /> - <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.HdrType)" /> + <param name="flag_visible" value="!String.IsEmpty(ListItem.VideoCodec)" /> + <param name="flag_label" value="$VAR[VideoListCodecVar]" /> </include> <include content="MediaFlag"> - <param name="texture" value="$INFO[ListItem.VideoAspect,flags/aspectratio/,.png]" /> - <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.VideoAspect)" /> + <param name="flag_visible" value="!String.IsEmpty(ListItem.VideoResolution)" /> + <param name="flag_label" value="$INFO[ListItem.VideoResolution]$VAR[VideoListResolutionTypeVar, ]" /> </include> <include content="MediaFlag"> - <param name="texture" value="$INFO[$PARAM[infolabel_prefix]ListItem.AudioCodec,flags/audiocodec/,.png]" /> - <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.AudioCodec)" /> + <param name="flag_visible" value="!String.IsEmpty(ListItem.HdrType)" /> + <param name="flag_label" value="$VAR[VideoListHDRVar]" /> </include> <include content="MediaFlag"> - <param name="texture" value="$INFO[$PARAM[infolabel_prefix]ListItem.AudioChannels,flags/audiochannel/,.png]" /> - <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.AudioChannels)" /> + <param name="flag_visible" value="!String.IsEmpty(ListItem.VideoAspect)" /> + <param name="flag_label" value="$INFO[ListItem.VideoAspect,,:1]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(ListItem.AudioCodec)" /> + <param name="flag_label" value="$VAR[AudioListCodecVar]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(ListItem.AudioChannels)" /> + <param name="flag_label" value="$VAR[AudioListChannelsVar]" /> + </include> + </control> + <control type="grouplist"> + <visible>[Player.ShowInfo | Window.IsActive(fullscreeninfo)] + !Player.ChannelPreviewActive + Window.IsActive(fullscreenvideo)</visible> + <orientation>horizontal</orientation> + <right>20</right> + <bottom>0</bottom> + <height>60</height> + <align>right</align> + <itemgap>10</itemgap> + <width>1900</width> + <usecontrolcoords>true</usecontrolcoords> + <include content="MediaFlag"> + <param name="flag_visible" value="VideoPlayer.IsStereoscopic" /> + <param name="flag_label" value="3D" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(VideoPlayer.VideoCodec)" /> + <param name="flag_label" value="$VAR[VideoPlayerCodecVar]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(VideoPlayer.VideoResolution)" /> + <param name="flag_label" value="$INFO[VideoPlayer.VideoResolution]$VAR[VideoPlayerResolutionTypeVar, ]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(VideoPlayer.HdrType)" /> + <param name="flag_label" value="$VAR[VideoPlayerHDRVar]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(VideoPlayer.VideoAspect)" /> + <param name="flag_label" value="$INFO[VideoPlayer.VideoAspect,,:1]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(VideoPlayer.AudioCodec)" /> + <param name="flag_label" value="$VAR[VideoPlayerAudioCodecVar]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(VideoPlayer.AudioChannels)" /> + <param name="flag_label" value="$VAR[VideoPlayerAudioChannelsVar]" /> </include> </control> <control type="grouplist"> <visible>Container.Content(albums) | Window.IsActive(10502)</visible> <orientation>horizontal</orientation> <right>20</right> - <top>0</top> - <height>70</height> + <bottom>10</bottom> + <height>60</height> <align>right</align> <itemgap>10</itemgap> <width>1900</width> <usecontrolcoords>true</usecontrolcoords> - <control type="group"> - <width>115</width> - <visible>!String.IsEmpty($PARAM[infolabel_prefix]ListItem.Duration)</visible> - <visible>Container.Content(albums) + ![Window.IsVisible(songinformation) | Window.IsVisible(musicinformation)]</visible> - <control type="label"> - <width>110</width> - <height>60</height> - <align>center</align> - <aligny>center</aligny> - <label>$INFO[$PARAM[infolabel_prefix]ListItem.Duration]</label> - <font>font_flag</font> - </control> - <include content="MediaFlag"> - <param name="texture" value="flags/flag.png" /> - </include> - </control> - <control type="group"> - <width>115</width> - <visible>!String.IsEmpty($PARAM[infolabel_prefix]ListItem.FileExtension)</visible> - <control type="label"> - <width>110</width> - <height>60</height> - <align>center</align> - <aligny>center</aligny> - <label>$INFO[$PARAM[infolabel_prefix]ListItem.FileExtension]</label> - <font>font_flag</font> - </control> - <include content="MediaFlag"> - <param name="texture" value="flags/flag.png" /> - </include> - </control> <include content="MediaFlag"> - <param name="texture" value="$INFO[$PARAM[infolabel_prefix]ListItem.MusicChannels,flags/audiochannel/,.png]" /> - <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.MusicChannels)" /> + <param name="flag_visible" value="!String.IsEmpty(ListItem.Duration) + Container.Content(albums) + ![Window.IsVisible(songinformation) | Window.IsVisible(musicinformation)]" /> + <param name="flag_label" value="$INFO[ListItem.Duration]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(ListItem.FileExtension)" /> + <param name="flag_label" value="$INFO[ListItem.FileExtension]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(ListItem.MusicChannels)" /> + <param name="flag_label" value="$VAR[MusicListChannelsVar]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(ListItem.Samplerate)" /> + <param name="flag_label" value="$INFO[ListItem.Samplerate, ,kHz]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(ListItem.BitRate)" /> + <param name="flag_label" value="$INFO[ListItem.BitRate, ,kbps]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(ListItem.MusicBitsPerSample)" /> + <param name="flag_label" value="$INFO[ListItem.MusicBitsPerSample, ,bit]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(ListItem.BMP)" /> + <param name="flag_label" value="$INFO[ListItem.BMP, ,bpm]" /> + </include> + </control> + <control type="grouplist"> + <visible>Player.ShowInfo + !Player.ChannelPreviewActive + Window.IsActive(visualisation)</visible>) + <orientation>horizontal</orientation> + <right>20</right> + <bottom>10</bottom> + <height>60</height> + <align>right</align> + <itemgap>10</itemgap> + <width>1900</width> + <usecontrolcoords>true</usecontrolcoords> + <include content="MediaFlag"> + <param name="flag_visible" value="RDS.HasRDS" /> + <param name="flag_label" value="RDS" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(MusicPlayer.Codec)" /> + <param name="flag_label" value="$VAR[MusicPlayerCodecVar]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(MusicPlayer.Channels)" /> + <param name="flag_label" value="$VAR[MusicPlayerAudioChannelsVar]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(MusicPlayer.SampleRate)" /> + <param name="flag_label" value="$INFO[MusicPlayer.SampleRate, ,kHz]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(MusicPlayer.BitRate)" /> + <param name="flag_label" value="$INFO[MusicPlayer.BitRate, ,kbps]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(MusicPlayer.BitsPerSample)" /> + <param name="flag_label" value="$INFO[MusicPlayer.BitsPerSample, ,bit]" /> + </include> + <include content="MediaFlag"> + <param name="flag_visible" value="!String.IsEmpty(MusicPlayer.BMP)" /> + <param name="flag_label" value="$INFO[MusicPlayer.BMP, ,bpm]" /> </include> - <control type="group"> - <visible>!String.IsEmpty(ListItem.Samplerate)</visible> - <width>115</width> - <control type="label"> - <width>110</width> - <height>60</height> - <align>center</align> - <aligny>center</aligny> - <label>$INFO[ListItem.Samplerate, ,kHz]</label> - <font>font_flag</font> - </control> - <include content="MediaFlag"> - <param name="texture" value="flags/flag.png" /> - </include> - </control> - <control type="group"> - <visible>!String.IsEmpty(ListItem.BitRate)</visible> - <width>115</width> - <control type="label"> - <width>110</width> - <height>60</height> - <align>center</align> - <aligny>center</aligny> - <label>$INFO[ListItem.BitRate, ,kbps]</label> - <font>font_flag</font> - </control> - <include content="MediaFlag"> - <param name="texture" value="flags/flag.png" /> - </include> - </control> - <control type="group"> - <visible>!String.IsEmpty(ListItem.MusicBitsPerSample)</visible> - <width>115</width> - <control type="label"> - <width>110</width> - <height>60</height> - <align>center</align> - <aligny>center</aligny> - <label>$INFO[ListItem.MusicBitsPerSample, ,bit]</label> - <font>font_flag</font> - </control> - <include content="MediaFlag"> - <param name="texture" value="flags/flag.png" /> - </include> - </control> - <control type="group"> - <visible>!String.IsEmpty(ListItem.BMP)</visible> - <width>115</width> - <control type="label"> - <width>110</width> - <height>60</height> - <align>center</align> - <aligny>center</aligny> - <label>$INFO[ListItem.BMP, ,bpm]</label> - <font>font_flag</font> - </control> - <include content="MediaFlag"> - <param name="texture" value="flags/flag.png" /> - </include> - </control> </control> </definition> </include> diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index e5fd8b989b250..48d31556dfff6 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -708,7 +708,7 @@ <value condition="!String.IsEmpty(Container(50).ListItem.Art(thumb))">$INFO[Container(50).ListItem.Art(thumb)]</value> <value>$INFO[ListItem.Art(thumb)]</value> </variable> - <variable name="VideoResolutionTypeVar"> + <variable name="VideoListResolutionTypeVar"> <value condition="String.IsEqual(ListItem.VideoResolution,480)">SD</value> <value condition="String.IsEqual(ListItem.VideoResolution,540)">SD</value> <value condition="String.IsEqual(ListItem.VideoResolution,576)">SD</value> @@ -716,8 +716,19 @@ <value condition="String.IsEqual(ListItem.VideoResolution,1080)">HD</value> <value condition="String.IsEqual(ListItem.VideoResolution,4K)">UHD</value> <value condition="String.IsEqual(ListItem.VideoResolution,8K)">UHD</value> - </variable> - <variable name="VideoCodecVar"> + <value>$INFO[ListItem.VideoResolution]</value> + </variable> + <variable name="VideoPlayerResolutionTypeVar"> + <value condition="String.IsEqual(VideoPlayer.VideoResolution,480)">SD</value> + <value condition="String.IsEqual(VideoPlayer.VideoResolution,540)">SD</value> + <value condition="String.IsEqual(VideoPlayer.VideoResolution,576)">SD</value> + <value condition="String.IsEqual(VideoPlayer.VideoResolution,720)">HD</value> + <value condition="String.IsEqual(VideoPlayer.VideoResolution,1080)">HD</value> + <value condition="String.IsEqual(VideoPlayer.VideoResolution,4K)">UHD</value> + <value condition="String.IsEqual(VideoPlayer.VideoResolution,8K)">UHD</value> + <value>$INFO[VideoPlayer.VideoResolution]</value> + </variable> + <variable name="VideoListCodecVar"> <value condition="String.IsEqual(ListItem.VideoCodec,av1)">AV1</value> <value condition="String.IsEqual(ListItem.VideoCodec,avc1)">AVC-1</value> <value condition="String.IsEqual(ListItem.VideoCodec,div3)">DivX</value> @@ -744,13 +755,46 @@ <value condition="String.IsEqual(ListItem.VideoCodec,xvid)">XviD</value> <value>$INFO[ListItem.VideoCodec]</value> </variable> - <variable name="VideoHDRVar"> - <value condition="String.IsEqual(ListItem.HdrType,dolbyvision)">Dolby Vision</value> + <variable name="VideoPlayerCodecVar"> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,av1)">AV1</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,avc1)">AVC-1</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,div3)">DivX</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,divx)">DivX</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,dx50)">DivX</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,flv)">FLV</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,h264)">H.264</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,hev1)">H.265</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,hevc)">H.265</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,hvc1)">H.265</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,mpeg1)">MPEG-1</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,mpeg2)">MPEG-2</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,mpeg2video)">MPEG-2</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,mp4v)">MPEG-4</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,mpeg4)">MPEG-4</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,theora)">Theora</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,vc1)">VC-1</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,vc-1)">VC-1</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,vp8)">VP8</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,vp9)">VP9</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,wmv)">WMV</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,wmv3)">WMV</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,wvc1)">VC-1</value> + <value condition="String.IsEqual(VideoPlayer.VideoCodec,xvid)">XviD</value> + <value>$INFO[VideoPlayer.VideoCodec]</value> + </variable> + <variable name="VideoListHDRVar"> + <value condition="String.IsEqual(ListItem.HdrType,dolbyvision)">DoVi</value> <value condition="String.IsEqual(ListItem.HdrType,hdr10)">HDR10</value> <value condition="String.IsEqual(ListItem.HdrType,hlg)">HLG</value> <value>$INFO[ListItem.HdrType]</value> </variable> - <variable name="AudioCodecVar"> + <variable name="VideoPlayerHDRVar"> + <value condition="String.IsEqual(VideoPlayer.HdrType,dolbyvision)">DoVi</value> + <value condition="String.IsEqual(VideoPlayer.HdrType,hdr10)">HDR10</value> + <value condition="String.IsEqual(VideoPlayer.HdrType,hlg)">HLG</value> + <value>$INFO[VideoPlayer.HdrType]</value> + </variable> + <variable name="AudioListCodecVar"> <value condition="String.IsEqual(ListItem.AudioCodec,aac)">AAC</value> <value condition="String.IsEqual(ListItem.AudioCodec,aac_latm)">AAC</value> <value condition="String.IsEqual(ListItem.AudioCodec,ac3)">Dolby D</value> @@ -786,7 +830,43 @@ <value condition="String.IsEqual(ListItem.AudioCodec,wmav2)">WMA</value> <value>$INFO[ListItem.AudioCodec]</value> </variable> - <variable name="AudioChannelsVar"> + <variable name="VideoPlayerAudioCodecVar"> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,aac)">AAC</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,aac_latm)">AAC</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,ac3)">Dolby D</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,aif)">AIFF</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,aifc)">AIFF</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,aiff)">AIFF</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,alac)">ALAC</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,ape)">APE</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,avc)">AVC</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,cdda)">CDDA</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,dca)">DTS</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,dolbydigital)">Dolby D</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,dts)">DTS</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,dtshd_hra)">DTSHD-HRA</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,dtshd_ma)">DTSHD-MA</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,dtsma)">DTSHD-MA</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,eac3)">Dolby D+</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,flac)">FLAC</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,mp1)">MP1</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,mp3)">MP3</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,mp3float)">MP3</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,ogg)">OGG</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,opus)">OPUS</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,pcm)">PCM</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,pcm_bluray)">PCM</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,pcm_s16le)">PCM</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,pcm_s24le)">PCM</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,truehd)">TrueHD</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,vorbis)">Vorbis</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,wav)">WAV</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,wavpack)">WAVP</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,wmapro)">WMA-PRO</value> + <value condition="String.IsEqual(VideoPlayer.AudioCodec,wmav2)">WMA</value> + <value>$INFO[VideoPlayer.AudioCodec]</value> + </variable> + <variable name="AudioListChannelsVar"> <value condition="String.IsEqual(ListItem.AudioChannels,0)">0.0</value> <value condition="String.IsEqual(ListItem.AudioChannels,1)">1.0</value> <value condition="String.IsEqual(ListItem.AudioChannels,2)">2.0</value> @@ -799,13 +879,88 @@ <value condition="String.IsEqual(ListItem.AudioChannels,10)">9.1</value> <value>$INFO[ListItem.AudioChannels]</value> </variable> + <variable name="VideoPlayerAudioChannelsVar"> + <value condition="String.IsEqual(VideoPlayer.AudioChannels,0)">0.0</value> + <value condition="String.IsEqual(VideoPlayer.AudioChannels,1)">1.0</value> + <value condition="String.IsEqual(VideoPlayer.AudioChannels,2)">2.0</value> + <value condition="String.IsEqual(VideoPlayer.AudioChannels,3)">2.1</value> + <value condition="String.IsEqual(VideoPlayer.AudioChannels,4)">4.0</value> + <value condition="String.IsEqual(VideoPlayer.AudioChannels,5)">4.1</value> + <value condition="String.IsEqual(VideoPlayer.AudioChannels,6)">5.1</value> + <value condition="String.IsEqual(VideoPlayer.AudioChannels,7)">6.1</value> + <value condition="String.IsEqual(VideoPlayer.AudioChannels,8)">7.1</value> + <value condition="String.IsEqual(VideoPlayer.AudioChannels,10)">9.1</value> + <value>$INFO[VideoPlayer.AudioChannels]</value> + </variable> + <variable name="MusicPlayerCodecVar"> + <value condition="String.IsEqual(MusicPlayer.Codec,aac)">AAC</value> + <value condition="String.IsEqual(MusicPlayer.Codec,aac_latm)">AAC</value> + <value condition="String.IsEqual(MusicPlayer.Codec,ac3)">Dolby D</value> + <value condition="String.IsEqual(LMusicPlayer.Codec,aif)">AIFF</value> + <value condition="String.IsEqual(MusicPlayer.Codec,aifc)">AIFF</value> + <value condition="String.IsEqual(MusicPlayer.Codec,aiff)">AIFF</value> + <value condition="String.IsEqual(MusicPlayer.Codec,alac)">ALAC</value> + <value condition="String.IsEqual(LMusicPlayer.Codec,ape)">APE</value> + <value condition="String.IsEqual(MusicPlayer.Codec,avc)">AVC</value> + <value condition="String.IsEqual(MusicPlayer.Codec,cdda)">CDDA</value> + <value condition="String.IsEqual(MusicPlayer.Codec,dca)">DTS</value> + <value condition="String.IsEqual(MusicPlayer.Codec,dolbydigital)">Dolby D</value> + <value condition="String.IsEqual(MusicPlayer.Codec,dts)">DTS</value> + <value condition="String.IsEqual(MusicPlayer.Codec,dtshd_hra)">DTSHD-HRA</value> + <value condition="String.IsEqual(MusicPlayer.Codec,dtshd_ma)">DTSHD-MA</value> + <value condition="String.IsEqual(MusicPlayer.Codec,dtsma)">DTSHD-MA</value> + <value condition="String.IsEqual(MusicPlayer.Codec,eac3)">Dolby D+</value> + <value condition="String.IsEqual(MusicPlayer.Codec,flac)">FLAC</value> + <value condition="String.IsEqual(MusicPlayer.Codec,mp1)">MP1</value> + <value condition="String.IsEqual(MusicPlayer.Codec,mp3)">MP3</value> + <value condition="String.IsEqual(MusicPlayer.Codec,mp3float)">MP3</value> + <value condition="String.IsEqual(MusicPlayer.Codec,ogg)">OGG</value> + <value condition="String.IsEqual(MusicPlayer.Codec,opus)">OPUS</value> + <value condition="String.IsEqual(MusicPlayer.Codec,pcm)">PCM</value> + <value condition="String.IsEqual(MusicPlayer.Codec,pcm_bluray)">PCM</value> + <value condition="String.IsEqual(MusicPlayer.Codec,pcm_s16le)">PCM</value> + <value condition="String.IsEqual(MusicPlayer.Codec,pcm_s24le)">PCM</value> + <value condition="String.IsEqual(MusicPlayer.Codec,truehd)">TrueHD</value> + <value condition="String.IsEqual(MusicPlayer.Codec,vorbis)">Vorbis</value> + <value condition="String.IsEqual(MusicPlayer.Codec,wav)">WAV</value> + <value condition="String.IsEqual(MusicPlayer.Codec,wavpack)">WAVP</value> + <value condition="String.IsEqual(MusicPlayer.Codec,wmapro)">WMA-PRO</value> + <value condition="String.IsEqual(MusicPlayer.Codec,wmav2)">WMA</value> + <value>$INFO[MusicPlayer.Codec]</value> + </variable> + <variable name="MusicListChannelsVar"> + <value condition="String.IsEqual(ListItem.MusicChannels,0)">0.0</value> + <value condition="String.IsEqual(ListItem.MusicChannels,1)">1.0</value> + <value condition="String.IsEqual(ListItem.MusicChannels,2)">2.0</value> + <value condition="String.IsEqual(ListItem.MusicChannels,3)">2.1</value> + <value condition="String.IsEqual(ListItem.MusicChannels,4)">4.0</value> + <value condition="String.IsEqual(ListItem.MusicChannels,5)">4.1</value> + <value condition="String.IsEqual(ListItem.MusicChannels,6)">5.1</value> + <value condition="String.IsEqual(ListItem.MusicChannels,7)">6.1</value> + <value condition="String.IsEqual(ListItem.MusicChannels,8)">7.1</value> + <value condition="String.IsEqual(ListItem.MusicChannels,10)">9.1</value> + <value>$INFO[ListItem.MusicChannels]</value> + </variable> + <variable name="MusicPlayerAudioChannelsVar"> + <value condition="String.IsEqual(MusicPlayer.Channels,0)">0.0</value> + <value condition="String.IsEqual(MusicPlayer.Channels,1)">1.0</value> + <value condition="String.IsEqual(MusicPlayer.Channels,2)">2.0</value> + <value condition="String.IsEqual(MusicPlayer.Channels,3)">2.1</value> + <value condition="String.IsEqual(MusicPlayer.Channels,4)">4.0</value> + <value condition="String.IsEqual(MusicPlayer.Channels,5)">4.1</value> + <value condition="String.IsEqual(MusicPlayer.Channels,6)">5.1</value> + <value condition="String.IsEqual(MusicPlayer.Channels,7)">6.1</value> + <value condition="String.IsEqual(MusicPlayer.Channels,8)">7.1</value> + <value condition="String.IsEqual(MusicPlayer.Channels,10)">9.1</value> + <value>$INFO[MusicPlayer.Channels]</value> + </variable> <variable name="MediaInfoListLabelVar"> <value condition="Window.IsVisible(selectvideoversion)">$INFO[ListItem.VideoVersionName]</value> <value>$INFO[ListItem.Label]</value> </variable> <variable name="MediaInfoListLabel2Var"> - <value condition="ListItem.IsStereoscopic">$INFO[ListItem.Duration,$LOCALIZE[180]: ][CR]$VAR[VideoCodecVar,, ]$INFO[ListItem.VideoResolution,| , ]$VAR[VideoResolutionTypeVar,, ]$VAR[VideoHDRVar,| , ]| 3D $INFO[ListItem.VideoAspect,| ,:1 ]$VAR[AudioCodecVar,| , ]$VAR[AudioChannelsVar]</value> - <value>$INFO[ListItem.Duration,$LOCALIZE[180]: ][CR]$VAR[VideoCodecVar,, ]$INFO[ListItem.VideoResolution,| , ]$VAR[VideoResolutionTypeVar,, ]$VAR[VideoHDRVar,| , ]$INFO[ListItem.VideoAspect,| ,:1 ]$VAR[AudioCodecVar,| , ]$VAR[AudioChannelsVar]</value> + <value condition="ListItem.IsStereoscopic">$INFO[ListItem.Duration,$LOCALIZE[180]: ][CR]3D | $VAR[VideoListCodecVar]$INFO[ListItem.VideoResolution, | ]$VAR[VideoListResolutionTypeVar, ]$VAR[VideoListHDRVar, | ]$VAR[VideoListAspectVar, | ]$VAR[AudioListCodecVar, | ]$VAR[AudioListChannelsVar, ]</value> + <value>$INFO[ListItem.Duration,$LOCALIZE[180]: ][CR]$VAR[VideoListCodecVar]$INFO[ListItem.VideoResolution, | ]$VAR[VideoListResolutionTypeVar, ]$VAR[VideoListHDRVar, | ]$VAR[VideoListAspectVar, | ]$VAR[AudioListCodecVar, | ]$VAR[AudioListChannelsVar, ]</value> </variable> <variable name="PVRInstanceName"> <value condition="Integer.IsGreater(PVR.ClientCount,1)">$INFO[ListItem.PVRClientName,[COLOR grey]$LOCALIZE[31137]:[/COLOR] ,]$INFO[ListItem.PVRInstanceName, (,)]</value> From 02f8dba3d2da5bc0a766f10ffba60bd00f749061 Mon Sep 17 00:00:00 2001 From: fuzzard <fuzzard@kodi.tv> Date: Fri, 13 Sep 2024 15:01:19 +1000 Subject: [PATCH 491/651] [tools/depends][native] expat bump to 2.6.3 security fixes --- tools/depends/native/expat/EXPAT-VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/depends/native/expat/EXPAT-VERSION b/tools/depends/native/expat/EXPAT-VERSION index 3631ff3f5be15..dd337f57dc07f 100644 --- a/tools/depends/native/expat/EXPAT-VERSION +++ b/tools/depends/native/expat/EXPAT-VERSION @@ -1,5 +1,5 @@ LIBNAME=expat -VERSION=2.6.2 +VERSION=2.6.3 SOURCE=$(LIBNAME)-$(VERSION) ARCHIVE=$(SOURCE).tar.xz -SHA512=47b60967d6346d330dded87ea1a2957aa7d34dd825043386a89aa131054714f618ede57bfe97cf6caa40582a4bc67e198d2a915e7d8dbe8ee4f581857c2e3c2e +SHA512=e02c4ad88f9d539258aa1c1db71ded7770a8f12c77b5535e5b34f040ae5b1361ef23132f16d96bdb7c096a83acd637a7c907916bdfcc6d5cfb9e35d04020ca0b From 78239a80c67bdf58e8e7c0d6bc25f9baef8ddb4f Mon Sep 17 00:00:00 2001 From: fuzzard <fuzzard@kodi.tv> Date: Fri, 13 Sep 2024 15:02:17 +1000 Subject: [PATCH 492/651] [tools/depends][target] bump expat 2.6.3 security fixes --- tools/depends/target/expat/EXPAT-VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/depends/target/expat/EXPAT-VERSION b/tools/depends/target/expat/EXPAT-VERSION index 571fe9f0e69e3..ba82abe72f5a8 100644 --- a/tools/depends/target/expat/EXPAT-VERSION +++ b/tools/depends/target/expat/EXPAT-VERSION @@ -1,6 +1,6 @@ LIBNAME=expat -VERSION=2.6.2 +VERSION=2.6.3 SOURCE=$(LIBNAME)-$(VERSION) ARCHIVE=$(SOURCE).tar.xz -SHA512=47b60967d6346d330dded87ea1a2957aa7d34dd825043386a89aa131054714f618ede57bfe97cf6caa40582a4bc67e198d2a915e7d8dbe8ee4f581857c2e3c2e +SHA512=e02c4ad88f9d539258aa1c1db71ded7770a8f12c77b5535e5b34f040ae5b1361ef23132f16d96bdb7c096a83acd637a7c907916bdfcc6d5cfb9e35d04020ca0b BYPRODUCT=libexpat.a From 95eefc8c9f37ea1ad61223f9231a40df46fd2cfb Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:18:15 +0200 Subject: [PATCH 493/651] [PVR] Fixed more occurances of magic numbers for invalid client id. --- xbmc/pvr/PVRDatabase.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xbmc/pvr/PVRDatabase.cpp b/xbmc/pvr/PVRDatabase.cpp index ec4428384cb25..84e7eb99c9b78 100644 --- a/xbmc/pvr/PVRDatabase.cpp +++ b/xbmc/pvr/PVRDatabase.cpp @@ -590,7 +590,7 @@ int CPVRDatabase::GetClientID(const std::string& addonID, unsigned int instanceI if (ExecuteQuery(sql)) return static_cast<int>(m_pDS->lastinsertid()); - return -1; + return PVR_CLIENT_INVALID_UID; } /********** Channel provider methods **********/ @@ -1306,7 +1306,8 @@ std::vector<std::shared_ptr<CPVRTimerInfoTag>> CPVRDatabase::GetTimers( newTag->m_iClientIndex = -m_pDS->fv("iClientIndex").get_asInt(); newTag->m_iParentClientIndex = m_pDS->fv("iParentClientIndex").get_asInt(); newTag->m_iClientId = m_pDS->fv("iClientId").get_asInt(); - newTag->SetTimerType(CPVRTimerType::CreateFromIds(m_pDS->fv("iTimerType").get_asInt(), -1)); + newTag->SetTimerType(CPVRTimerType::CreateFromIds(m_pDS->fv("iTimerType").get_asInt(), + PVR_CLIENT_INVALID_UID)); newTag->m_state = static_cast<PVR_TIMER_STATE>(m_pDS->fv("iState").get_asInt()); newTag->m_strTitle = m_pDS->fv("sTitle").get_asString().c_str(); newTag->m_iClientChannelUid = m_pDS->fv("iClientChannelUid").get_asInt(); From f00bc30d29c6360fda0628165bfaae2e934c5bca Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:24:31 +0200 Subject: [PATCH 494/651] [PVR] clang-format EpgSearchFilter.(cpp|h). --- xbmc/pvr/epg/EpgSearchFilter.cpp | 12 +- xbmc/pvr/epg/EpgSearchFilter.h | 238 +++++++++++++++---------------- 2 files changed, 126 insertions(+), 124 deletions(-) diff --git a/xbmc/pvr/epg/EpgSearchFilter.cpp b/xbmc/pvr/epg/EpgSearchFilter.cpp index 30877550c5864..b7533ce4c513d 100644 --- a/xbmc/pvr/epg/EpgSearchFilter.cpp +++ b/xbmc/pvr/epg/EpgSearchFilter.cpp @@ -28,8 +28,7 @@ using namespace PVR; -CPVREpgSearchFilter::CPVREpgSearchFilter(bool bRadio) -: m_bIsRadio(bRadio) +CPVREpgSearchFilter::CPVREpgSearchFilter(bool bRadio) : m_bIsRadio(bRadio) { Reset(); } @@ -366,7 +365,8 @@ void CPVREpgSearchFilter::RemoveDuplicates(std::vector<std::shared_ptr<CPVREpgIn for (auto it = results.begin(); it != results.end();) { it = results.erase(std::remove_if(results.begin(), results.end(), - [&it](const std::shared_ptr<const CPVREpgInfoTag>& entry) { + [&it](const std::shared_ptr<const CPVREpgInfoTag>& entry) + { return *it != entry && (*it)->Title() == entry->Title() && (*it)->Plot() == entry->Plot() && (*it)->PlotOutline() == entry->PlotOutline(); @@ -414,10 +414,12 @@ bool CPVREpgSearchFilter::MatchFreeToAir(const std::shared_ptr<const CPVREpgInfo bool CPVREpgSearchFilter::MatchTimers(const std::shared_ptr<const CPVREpgInfoTag>& tag) const { - return (!m_bIgnorePresentTimers || !CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(tag)); + return (!m_bIgnorePresentTimers || + !CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(tag)); } bool CPVREpgSearchFilter::MatchRecordings(const std::shared_ptr<const CPVREpgInfoTag>& tag) const { - return (!m_bIgnorePresentRecordings || !CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(tag)); + return (!m_bIgnorePresentRecordings || + !CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(tag)); } diff --git a/xbmc/pvr/epg/EpgSearchFilter.h b/xbmc/pvr/epg/EpgSearchFilter.h index 6638befd6770e..5bd9e703d1459 100644 --- a/xbmc/pvr/epg/EpgSearchFilter.h +++ b/xbmc/pvr/epg/EpgSearchFilter.h @@ -18,162 +18,162 @@ namespace PVR { - class CPVREpgInfoTag; +class CPVREpgInfoTag; - class CPVREpgSearchFilter - { - public: - CPVREpgSearchFilter() = delete; +class CPVREpgSearchFilter +{ +public: + CPVREpgSearchFilter() = delete; - /*! - * @brief ctor. - * @param bRadio the type of channels to search - if true, 'radio'. 'tv', otherwise. - */ - explicit CPVREpgSearchFilter(bool bRadio); + /*! + * @brief ctor. + * @param bRadio the type of channels to search - if true, 'radio'. 'tv', otherwise. + */ + explicit CPVREpgSearchFilter(bool bRadio); - /*! - * @brief Clear this filter. - */ - void Reset(); + /*! + * @brief Clear this filter. + */ + void Reset(); - /*! - * @brief Return the path for this filter. - * @return the path. - */ - std::string GetPath() const; + /*! + * @brief Return the path for this filter. + * @return the path. + */ + std::string GetPath() const; - /*! - * @brief Check if a tag will be filtered or not. - * @param tag The tag to check. - * @return True if this tag matches the filter, false if not. - */ - bool FilterEntry(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; + /*! + * @brief Check if a tag will be filtered or not. + * @param tag The tag to check. + * @return True if this tag matches the filter, false if not. + */ + bool FilterEntry(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; - /*! - * @brief remove duplicates from a list of epg tags. - * @param results The list of epg tags. - */ - static void RemoveDuplicates(std::vector<std::shared_ptr<CPVREpgInfoTag>>& results); + /*! + * @brief remove duplicates from a list of epg tags. + * @param results The list of epg tags. + */ + static void RemoveDuplicates(std::vector<std::shared_ptr<CPVREpgInfoTag>>& results); - /*! - * @brief Get the type of channels to search. - * @return true, if 'radio'. false, otherwise. - */ - bool IsRadio() const { return m_bIsRadio; } + /*! + * @brief Get the type of channels to search. + * @return true, if 'radio'. false, otherwise. + */ + bool IsRadio() const { return m_bIsRadio; } - const std::string& GetSearchTerm() const { return m_searchData.m_strSearchTerm; } - void SetSearchTerm(const std::string& strSearchTerm); + const std::string& GetSearchTerm() const { return m_searchData.m_strSearchTerm; } + void SetSearchTerm(const std::string& strSearchTerm); - void SetSearchPhrase(const std::string& strSearchPhrase); + void SetSearchPhrase(const std::string& strSearchPhrase); - bool IsCaseSensitive() const { return m_bIsCaseSensitive; } - void SetCaseSensitive(bool bIsCaseSensitive); + bool IsCaseSensitive() const { return m_bIsCaseSensitive; } + void SetCaseSensitive(bool bIsCaseSensitive); - bool ShouldSearchInDescription() const { return m_searchData.m_bSearchInDescription; } - void SetSearchInDescription(bool bSearchInDescription); + bool ShouldSearchInDescription() const { return m_searchData.m_bSearchInDescription; } + void SetSearchInDescription(bool bSearchInDescription); - int GetGenreType() const { return m_searchData.m_iGenreType; } - void SetGenreType(int iGenreType); + int GetGenreType() const { return m_searchData.m_iGenreType; } + void SetGenreType(int iGenreType); - int GetMinimumDuration() const { return m_iMinimumDuration; } - void SetMinimumDuration(int iMinimumDuration); + int GetMinimumDuration() const { return m_iMinimumDuration; } + void SetMinimumDuration(int iMinimumDuration); - int GetMaximumDuration() const { return m_iMaximumDuration; } - void SetMaximumDuration(int iMaximumDuration); + int GetMaximumDuration() const { return m_iMaximumDuration; } + void SetMaximumDuration(int iMaximumDuration); - bool ShouldIgnoreFinishedBroadcasts() const { return m_searchData.m_bIgnoreFinishedBroadcasts; } - void SetIgnoreFinishedBroadcasts(bool bIgnoreFinishedBroadcasts); + bool ShouldIgnoreFinishedBroadcasts() const { return m_searchData.m_bIgnoreFinishedBroadcasts; } + void SetIgnoreFinishedBroadcasts(bool bIgnoreFinishedBroadcasts); - bool ShouldIgnoreFutureBroadcasts() const { return m_searchData.m_bIgnoreFutureBroadcasts; } - void SetIgnoreFutureBroadcasts(bool bIgnoreFutureBroadcasts); + bool ShouldIgnoreFutureBroadcasts() const { return m_searchData.m_bIgnoreFutureBroadcasts; } + void SetIgnoreFutureBroadcasts(bool bIgnoreFutureBroadcasts); - const CDateTime& GetStartDateTime() const { return m_searchData.m_startDateTime; } - void SetStartDateTime(const CDateTime& startDateTime); + const CDateTime& GetStartDateTime() const { return m_searchData.m_startDateTime; } + void SetStartDateTime(const CDateTime& startDateTime); - bool IsStartAnyTime() const { return m_searchData.m_startAnyTime; } - void SetStartAnyTime(bool startAnyTime); + bool IsStartAnyTime() const { return m_searchData.m_startAnyTime; } + void SetStartAnyTime(bool startAnyTime); - const CDateTime& GetEndDateTime() const { return m_searchData.m_endDateTime; } - void SetEndDateTime(const CDateTime& endDateTime); + const CDateTime& GetEndDateTime() const { return m_searchData.m_endDateTime; } + void SetEndDateTime(const CDateTime& endDateTime); - bool IsEndAnyTime() const { return m_searchData.m_endAnyTime; } - void SetEndAnyTime(bool endAnyTime); + bool IsEndAnyTime() const { return m_searchData.m_endAnyTime; } + void SetEndAnyTime(bool endAnyTime); - bool ShouldIncludeUnknownGenres() const { return m_searchData.m_bIncludeUnknownGenres; } - void SetIncludeUnknownGenres(bool bIncludeUnknownGenres); + bool ShouldIncludeUnknownGenres() const { return m_searchData.m_bIncludeUnknownGenres; } + void SetIncludeUnknownGenres(bool bIncludeUnknownGenres); - bool ShouldRemoveDuplicates() const { return m_bRemoveDuplicates; } - void SetRemoveDuplicates(bool bRemoveDuplicates); + bool ShouldRemoveDuplicates() const { return m_bRemoveDuplicates; } + void SetRemoveDuplicates(bool bRemoveDuplicates); - int GetClientID() const { return m_iClientID; } - void SetClientID(int iClientID); + int GetClientID() const { return m_iClientID; } + void SetClientID(int iClientID); - int GetChannelGroupID() const { return m_iChannelGroupID; } - void SetChannelGroupID(int iChannelGroupID); + int GetChannelGroupID() const { return m_iChannelGroupID; } + void SetChannelGroupID(int iChannelGroupID); - int GetChannelUID() const { return m_iChannelUID; } - void SetChannelUID(int iChannelUID); + int GetChannelUID() const { return m_iChannelUID; } + void SetChannelUID(int iChannelUID); - bool IsFreeToAirOnly() const { return m_bFreeToAirOnly; } - void SetFreeToAirOnly(bool bFreeToAirOnly); + bool IsFreeToAirOnly() const { return m_bFreeToAirOnly; } + void SetFreeToAirOnly(bool bFreeToAirOnly); - bool ShouldIgnorePresentTimers() const { return m_bIgnorePresentTimers; } - void SetIgnorePresentTimers(bool bIgnorePresentTimers); + bool ShouldIgnorePresentTimers() const { return m_bIgnorePresentTimers; } + void SetIgnorePresentTimers(bool bIgnorePresentTimers); - bool ShouldIgnorePresentRecordings() const { return m_bIgnorePresentRecordings; } - void SetIgnorePresentRecordings(bool bIgnorePresentRecordings); + bool ShouldIgnorePresentRecordings() const { return m_bIgnorePresentRecordings; } + void SetIgnorePresentRecordings(bool bIgnorePresentRecordings); - int GetDatabaseId() const { return m_iDatabaseId; } - void SetDatabaseId(int iDatabaseId); + int GetDatabaseId() const { return m_iDatabaseId; } + void SetDatabaseId(int iDatabaseId); - const std::string& GetTitle() const { return m_title; } - void SetTitle(const std::string& title); + const std::string& GetTitle() const { return m_title; } + void SetTitle(const std::string& title); - const std::string& GetIconPath() const { return m_iconPath; } - void SetIconPath(const std::string& iconPath); + const std::string& GetIconPath() const { return m_iconPath; } + void SetIconPath(const std::string& iconPath); - const CDateTime& GetLastExecutedDateTime() const { return m_lastExecutedDateTime; } - void SetLastExecutedDateTime(const CDateTime& lastExecutedDateTime); + const CDateTime& GetLastExecutedDateTime() const { return m_lastExecutedDateTime; } + void SetLastExecutedDateTime(const CDateTime& lastExecutedDateTime); - const PVREpgSearchData& GetEpgSearchData() const { return m_searchData; } - void SetEpgSearchDataFiltered() { m_bEpgSearchDataFiltered = true; } + const PVREpgSearchData& GetEpgSearchData() const { return m_searchData; } + void SetEpgSearchDataFiltered() { m_bEpgSearchDataFiltered = true; } - bool IsChanged() const { return m_bChanged; } - void SetChanged(bool bChanged) { m_bChanged = bChanged; } + bool IsChanged() const { return m_bChanged; } + void SetChanged(bool bChanged) { m_bChanged = bChanged; } - private: - bool MatchGenre(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; - bool MatchDuration(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; - bool MatchStartAndEndTimes(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; - bool MatchSearchTerm(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; - bool MatchChannel(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; - bool MatchChannelGroup(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; - bool MatchFreeToAir(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; - bool MatchTimers(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; - bool MatchRecordings(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; +private: + bool MatchGenre(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; + bool MatchDuration(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; + bool MatchStartAndEndTimes(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; + bool MatchSearchTerm(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; + bool MatchChannel(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; + bool MatchChannelGroup(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; + bool MatchFreeToAir(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; + bool MatchTimers(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; + bool MatchRecordings(const std::shared_ptr<const CPVREpgInfoTag>& tag) const; - bool m_bChanged = false; + bool m_bChanged = false; - PVREpgSearchData m_searchData; - bool m_bEpgSearchDataFiltered = false; + PVREpgSearchData m_searchData; + bool m_bEpgSearchDataFiltered = false; - bool m_bIsCaseSensitive; /*!< Do a case sensitive search */ - int m_iMinimumDuration; /*!< The minimum duration for an entry */ - int m_iMaximumDuration; /*!< The maximum duration for an entry */ - bool m_bRemoveDuplicates; /*!< True to remove duplicate events, false if not */ + bool m_bIsCaseSensitive; /*!< Do a case sensitive search */ + int m_iMinimumDuration; /*!< The minimum duration for an entry */ + int m_iMaximumDuration; /*!< The maximum duration for an entry */ + bool m_bRemoveDuplicates; /*!< True to remove duplicate events, false if not */ - // PVR specific filters - bool m_bIsRadio; /*!< True to filter radio channels only, false to tv only */ - int m_iClientID = PVR_CLIENT_INVALID_UID; /*!< The client id */ - int m_iChannelGroupID{-1}; /*! The channel group id */ - int m_iChannelUID = -1; /*!< The channel uid */ - bool m_bFreeToAirOnly; /*!< Include free to air channels only */ - bool m_bIgnorePresentTimers; /*!< True to ignore currently present timers (future recordings), false if not */ - bool m_bIgnorePresentRecordings; /*!< True to ignore currently active recordings, false if not */ + // PVR specific filters + bool m_bIsRadio; /*!< True to filter radio channels only, false to tv only */ + int m_iClientID = PVR_CLIENT_INVALID_UID; /*!< The client id */ + int m_iChannelGroupID{-1}; /*! The channel group id */ + int m_iChannelUID = -1; /*!< The channel uid */ + bool m_bFreeToAirOnly; /*!< Include free to air channels only */ + bool m_bIgnorePresentTimers; /*!< True to ignore currently present timers, false if not */ + bool m_bIgnorePresentRecordings; /*!< True to ignore currently active recordings, false if not */ - int m_iDatabaseId = -1; - std::string m_title; - std::string m_iconPath; - CDateTime m_lastExecutedDateTime; - }; -} + int m_iDatabaseId = -1; + std::string m_title; + std::string m_iconPath; + CDateTime m_lastExecutedDateTime; +}; +} // namespace PVR From 9921d0033af5d5a876917ec97bfaa64ee84dc42c Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 21 Sep 2024 18:30:52 +0200 Subject: [PATCH 495/651] [PVR] Get rid of magic numbers for EPG search invalid database id. --- xbmc/pvr/epg/EpgDatabase.cpp | 8 ++++---- xbmc/pvr/epg/EpgSearchFilter.h | 6 ++++-- xbmc/pvr/windows/GUIWindowPVRSearch.cpp | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/xbmc/pvr/epg/EpgDatabase.cpp b/xbmc/pvr/epg/EpgDatabase.cpp index f1e6b91e622db..17c4d4354d88e 100644 --- a/xbmc/pvr/epg/EpgDatabase.cpp +++ b/xbmc/pvr/epg/EpgDatabase.cpp @@ -1442,7 +1442,7 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch) // Insert a new entry if this is a new search, replace the existing otherwise std::string strQuery; - if (epgSearch.GetDatabaseId() == -1) + if (epgSearch.GetDatabaseId() == PVR_EPG_SEARCH_INVALID_DATABASE_ID) strQuery = PrepareSQL( "INSERT INTO savedsearches " "(sTitle, sLastExecutedDateTime, sSearchTerm, bSearchInDescription, bIsCaseSensitive, " @@ -1510,7 +1510,7 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch) if (bReturn) { // Set the database id for searches persisted for the first time - if (epgSearch.GetDatabaseId() == -1) + if (epgSearch.GetDatabaseId() == PVR_EPG_SEARCH_INVALID_DATABASE_ID) epgSearch.SetDatabaseId(static_cast<int>(m_pDS->lastinsertid())); epgSearch.SetChanged(false); @@ -1521,7 +1521,7 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch) bool CPVREpgDatabase::UpdateSavedSearchLastExecuted(const CPVREpgSearchFilter& epgSearch) { - if (epgSearch.GetDatabaseId() == -1) + if (epgSearch.GetDatabaseId() == PVR_EPG_SEARCH_INVALID_DATABASE_ID) return false; std::unique_lock<CCriticalSection> lock(m_critSection); @@ -1534,7 +1534,7 @@ bool CPVREpgDatabase::UpdateSavedSearchLastExecuted(const CPVREpgSearchFilter& e bool CPVREpgDatabase::Delete(const CPVREpgSearchFilter& epgSearch) { - if (epgSearch.GetDatabaseId() == -1) + if (epgSearch.GetDatabaseId() == PVR_EPG_SEARCH_INVALID_DATABASE_ID) return false; CLog::LogFC(LOGDEBUG, LOGEPG, "Deleting saved search '{}' from the database", diff --git a/xbmc/pvr/epg/EpgSearchFilter.h b/xbmc/pvr/epg/EpgSearchFilter.h index 5bd9e703d1459..960d89f472036 100644 --- a/xbmc/pvr/epg/EpgSearchFilter.h +++ b/xbmc/pvr/epg/EpgSearchFilter.h @@ -18,6 +18,8 @@ namespace PVR { +static constexpr int PVR_EPG_SEARCH_INVALID_DATABASE_ID{-1}; + class CPVREpgInfoTag; class CPVREpgSearchFilter @@ -165,13 +167,13 @@ class CPVREpgSearchFilter // PVR specific filters bool m_bIsRadio; /*!< True to filter radio channels only, false to tv only */ int m_iClientID = PVR_CLIENT_INVALID_UID; /*!< The client id */ - int m_iChannelGroupID{-1}; /*! The channel group id */ + int m_iChannelGroupID{-1}; /*!< The channel group id */ int m_iChannelUID = -1; /*!< The channel uid */ bool m_bFreeToAirOnly; /*!< Include free to air channels only */ bool m_bIgnorePresentTimers; /*!< True to ignore currently present timers, false if not */ bool m_bIgnorePresentRecordings; /*!< True to ignore currently active recordings, false if not */ - int m_iDatabaseId = -1; + int m_iDatabaseId{PVR_EPG_SEARCH_INVALID_DATABASE_ID}; std::string m_title; std::string m_iconPath; CDateTime m_lastExecutedDateTime; diff --git a/xbmc/pvr/windows/GUIWindowPVRSearch.cpp b/xbmc/pvr/windows/GUIWindowPVRSearch.cpp index 356e65dcc08cc..50504e4628489 100644 --- a/xbmc/pvr/windows/GUIWindowPVRSearch.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRSearch.cpp @@ -455,7 +455,7 @@ void CGUIWindowPVRSearchBase::ExecuteSearch() } // Save if not a transient search - if (m_searchfilter->GetDatabaseId() != -1) + if (m_searchfilter->GetDatabaseId() != PVR_EPG_SEARCH_INVALID_DATABASE_ID) CServiceBroker::GetPVRManager().EpgContainer().UpdateSavedSearchLastExecuted(*m_searchfilter); } From d67b07472e4789648c158d6f5fe310b0b259ab3e Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 21 Sep 2024 17:39:36 +0200 Subject: [PATCH 496/651] [PVR] Add possibility to make copies of EPG saved searches (e.g. to use an existing search as a template for a new search). --- .../resources/strings.po | 14 +++++++++++++- xbmc/pvr/PVRContextMenus.cpp | 15 +++++++++++++++ xbmc/pvr/guilib/PVRGUIActionsEPG.cpp | 19 +++++++++++++++++++ xbmc/pvr/guilib/PVRGUIActionsEPG.h | 7 +++++++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 818ac8db86299..46d1d6174f504 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -11855,7 +11855,19 @@ msgctxt "#19354" msgid "Play only this programme" msgstr "" -#empty strings from id 19355 to 19498 +#. Label of a context menu entry to create a copy of an EPG saved saerch +#: xbmc/pvr/PVRContextMenus.cpp +msgctxt "#19355" +msgid "Duplicate" +msgstr "" + +#. Initial title of a duplicated EPG saved search. +#: xbmc/pvr/PVRContextMenus.cpp +msgctxt "#19356" +msgid "Copy of '{0:s}'" +msgstr "" + +#empty strings from id 19357 to 19498 #. label for epg genre value #: xbmc/pvr/epg/Epg.cpp diff --git a/xbmc/pvr/PVRContextMenus.cpp b/xbmc/pvr/PVRContextMenus.cpp index 9ab47757f70fd..f882c9e624094 100644 --- a/xbmc/pvr/PVRContextMenus.cpp +++ b/xbmc/pvr/PVRContextMenus.cpp @@ -81,6 +81,7 @@ DECL_STATICCONTEXTMENUITEM(ExecuteSearch); DECL_STATICCONTEXTMENUITEM(EditSearch); DECL_STATICCONTEXTMENUITEM(RenameSearch); DECL_STATICCONTEXTMENUITEM(ChooseIconForSearch); +DECL_STATICCONTEXTMENUITEM(DuplicateSearch); DECL_STATICCONTEXTMENUITEM(DeleteSearch); class PVRClientMenuHook : public IContextMenuItem @@ -748,6 +749,19 @@ bool ChooseIconForSearch::Execute(const std::shared_ptr<CFileItem>& item) const return CServiceBroker::GetPVRManager().Get<PVR::GUI::EPG>().ChooseIconForSavedSearch(*item); } +/////////////////////////////////////////////////////////////////////////////// +// Duplicate a saved search + +bool DuplicateSearch::IsVisible(const CFileItem& item) const +{ + return item.HasEPGSearchFilter(); +} + +bool DuplicateSearch::Execute(const std::shared_ptr<CFileItem>& item) const +{ + return CServiceBroker::GetPVRManager().Get<PVR::GUI::EPG>().DuplicateSavedSearch(*item); +} + /////////////////////////////////////////////////////////////////////////////// // Delete saved search @@ -794,6 +808,7 @@ CPVRContextMenuManager::CPVRContextMenuManager() std::make_shared<CONTEXTMENUITEM::EditSearch>(21450), /* Edit */ std::make_shared<CONTEXTMENUITEM::RenameSearch>(118), /* Rename */ std::make_shared<CONTEXTMENUITEM::ChooseIconForSearch>(19284), /* Choose icon */ + std::make_shared<CONTEXTMENUITEM::DuplicateSearch>(19355), /* Duplicate */ std::make_shared<CONTEXTMENUITEM::DeleteSearch>(117), /* Delete */ }) { diff --git a/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp b/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp index 9b76bb7672547..866c3987e81dd 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp @@ -30,6 +30,7 @@ #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "storage/MediaManager.h" +#include "utils/StringUtils.h" #include "utils/Variant.h" #include "utils/log.h" @@ -248,6 +249,24 @@ bool CPVRGUIActionsEPG::ChooseIconForSavedSearch(const CFileItem& item) return true; } +bool CPVRGUIActionsEPG::DuplicateSavedSearch(const CFileItem& item) +{ + const auto searchFilter{item.GetEPGSearchFilter()}; + + if (!searchFilter) + { + CLog::LogF(LOGERROR, "Wrong item type. No EPG search filter present."); + return false; + } + + const auto dupedSearchFilter{std::make_shared<CPVREpgSearchFilter>(*searchFilter)}; + dupedSearchFilter->SetDatabaseId(PVR_EPG_SEARCH_INVALID_DATABASE_ID); // force new db entry + dupedSearchFilter->SetTitle(StringUtils::Format(g_localizeStrings.Get(19356), // Copy of '<title>' + searchFilter->GetTitle())); + CServiceBroker::GetPVRManager().EpgContainer().PersistSavedSearch(*dupedSearchFilter); + return true; +} + bool CPVRGUIActionsEPG::DeleteSavedSearch(const CFileItem& item) { const auto searchFilter = item.GetEPGSearchFilter(); diff --git a/xbmc/pvr/guilib/PVRGUIActionsEPG.h b/xbmc/pvr/guilib/PVRGUIActionsEPG.h index 5041b1adc37bc..3f2ce6a1b54dd 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsEPG.h +++ b/xbmc/pvr/guilib/PVRGUIActionsEPG.h @@ -75,6 +75,13 @@ class CPVRGUIActionsEPG : public IPVRComponent */ bool ChooseIconForSavedSearch(const CFileItem& item); + /*! + * @brief Duplicate a saved search. + * @param item The item containing a search filter. + * @return True on success, false otherwise. + */ + bool DuplicateSavedSearch(const CFileItem& item); + /*! * @brief Delete a saved search. Opens confirmation dialog before deleting. * @param item The item containing a search filter. From 2b9e7b7ac910df86fe1b36588b08f5b0574bac4f Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving <spiff@kodi.tv> Date: Tue, 10 Sep 2024 15:31:53 +0200 Subject: [PATCH 497/651] move GetLocalArt to ArtUtils --- xbmc/FileItem.cpp | 29 +---------- xbmc/FileItem.h | 9 ---- xbmc/test/TestFileItem.cpp | 57 ---------------------- xbmc/utils/ArtUtils.cpp | 25 ++++++++++ xbmc/utils/ArtUtils.h | 9 ++++ xbmc/utils/test/TestArtUtils.cpp | 83 ++++++++++++++++++++++++++++++++ xbmc/video/VideoDatabase.cpp | 10 ++-- 7 files changed, 124 insertions(+), 98 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 89b15d80a810f..dcc28288e4191 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -1944,44 +1944,19 @@ std::string CFileItem::FindLocalArt(const std::string &artFile, bool useFolder) std::string thumb; if (!m_bIsFolder) { - thumb = GetLocalArt(artFile, false); + thumb = ART::GetLocalArt(*this, artFile, false); if (!thumb.empty() && CFile::Exists(thumb)) return thumb; } if ((useFolder || (m_bIsFolder && !IsFileFolder())) && !artFile.empty()) { - std::string thumb2 = GetLocalArt(artFile, true); + std::string thumb2 = ART::GetLocalArt(*this, artFile, true); if (!thumb2.empty() && thumb2 != thumb && CFile::Exists(thumb2)) return thumb2; } return ""; } -std::string CFileItem::GetLocalArt(const std::string& artFile, bool useFolder) const -{ - // no retrieving of empty art files from folders - if (useFolder && artFile.empty()) - return ""; - - std::string strFile = ART::GetLocalArtBaseFilename(*this, useFolder); - if (strFile.empty()) // empty filepath -> nothing to find - return ""; - - if (useFolder) - { - if (!artFile.empty()) - return URIUtils::AddFileToFolder(strFile, artFile); - } - else - { - if (artFile.empty()) // old thumbnail matching - return URIUtils::ReplaceExtension(strFile, ".tbn"); - else - return URIUtils::ReplaceExtension(strFile, "-" + artFile); - } - return ""; -} - std::string CFileItem::GetMovieName(bool bUseFolderNames /* = false */) const { if (IsPlugin() && HasVideoInfoTag() && !GetVideoInfoTag()->m_strTitle.empty()) diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index 7766982de5f83..635c964d8d6ab 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -397,15 +397,6 @@ class CFileItem : CPictureInfoTag* GetPictureInfoTag(); - /*! \brief Assemble the filename of a particular piece of local artwork for an item. - No file existence check is typically performed. - \param artFile the art file to search for. - \param useFolder whether to look in the folder for the art file. Defaults to false. - \return the path to the local artwork. - \sa FindLocalArt - */ - std::string GetLocalArt(const std::string& artFile, bool useFolder = false) const; - /*! \brief Assemble the filename of a particular piece of local artwork for an item, and check for file existence. \param artFile the art file to search for. diff --git a/xbmc/test/TestFileItem.cpp b/xbmc/test/TestFileItem.cpp index e78ba27676dec..e5ca906ab866b 100644 --- a/xbmc/test/TestFileItem.cpp +++ b/xbmc/test/TestFileItem.cpp @@ -42,63 +42,6 @@ AdvancedSettingsResetBase::AdvancedSettingsResetBase() settings->GetAdvancedSettings()->Initialize(*settingsMgr); } -class TestFileItemSpecifiedArtJpg : public AdvancedSettingsResetBase, - public WithParamInterface<TestFileData> -{ -}; - - -TEST_P(TestFileItemSpecifiedArtJpg, GetLocalArt) -{ - CFileItem item; - item.SetPath(GetParam().file); - std::string path = CURL(item.GetLocalArt("art.jpg", GetParam().use_folder)).Get(); - std::string compare = CURL(GetParam().base).Get(); - EXPECT_EQ(compare, path); -} - -const TestFileData MovieFiles[] = {{ "c:\\dir\\filename.avi", false, "c:\\dir\\filename-art.jpg" }, - { "c:\\dir\\filename.avi", true, "c:\\dir\\art.jpg" }, - { "/dir/filename.avi", false, "/dir/filename-art.jpg" }, - { "/dir/filename.avi", true, "/dir/art.jpg" }, - { "smb://somepath/file.avi", false, "smb://somepath/file-art.jpg" }, - { "smb://somepath/file.avi", true, "smb://somepath/art.jpg" }, - { "stack:///path/to/movie-cd1.avi , /path/to/movie-cd2.avi", false, "/path/to/movie-art.jpg" }, - { "stack:///path/to/movie-cd1.avi , /path/to/movie-cd2.avi", true, "/path/to/art.jpg" }, - { "stack:///path/to/movie_name/cd1/some_file1.avi , /path/to/movie_name/cd2/some_file2.avi", true, "/path/to/movie_name/art.jpg" }, - { "/home/user/TV Shows/Dexter/S1/1x01.avi", false, "/home/user/TV Shows/Dexter/S1/1x01-art.jpg" }, - { "/home/user/TV Shows/Dexter/S1/1x01.avi", true, "/home/user/TV Shows/Dexter/S1/art.jpg" }, - { "zip://g%3a%5cmultimedia%5cmovies%5cSphere%2ezip/Sphere.avi", false, "g:\\multimedia\\movies\\Sphere-art.jpg" }, - { "zip://g%3a%5cmultimedia%5cmovies%5cSphere%2ezip/Sphere.avi", true, "g:\\multimedia\\movies\\art.jpg" }, - { "/home/user/movies/movie_name/video_ts/VIDEO_TS.IFO", false, "/home/user/movies/movie_name/art.jpg" }, - { "/home/user/movies/movie_name/video_ts/VIDEO_TS.IFO", true, "/home/user/movies/movie_name/art.jpg" }, - { "/home/user/movies/movie_name/BDMV/index.bdmv", false, "/home/user/movies/movie_name/art.jpg" }, - { "/home/user/movies/movie_name/BDMV/index.bdmv", true, "/home/user/movies/movie_name/art.jpg" }}; - -INSTANTIATE_TEST_SUITE_P(MovieFiles, TestFileItemSpecifiedArtJpg, ValuesIn(MovieFiles)); - -class TestFileItemFallbackArt : public AdvancedSettingsResetBase, - public WithParamInterface<TestFileData> -{ -}; - -TEST_P(TestFileItemFallbackArt, GetLocalArt) -{ - CFileItem item; - item.SetPath(GetParam().file); - std::string path = CURL(item.GetLocalArt("", GetParam().use_folder)).Get(); - std::string compare = CURL(GetParam().base).Get(); - EXPECT_EQ(compare, path); -} - -const TestFileData NoArtFiles[] = {{ "c:\\dir\\filename.avi", false, "c:\\dir\\filename.tbn" }, - { "/dir/filename.avi", false, "/dir/filename.tbn" }, - { "smb://somepath/file.avi", false, "smb://somepath/file.tbn" }, - { "/home/user/TV Shows/Dexter/S1/1x01.avi", false, "/home/user/TV Shows/Dexter/S1/1x01.tbn" }, - { "zip://g%3a%5cmultimedia%5cmovies%5cSphere%2ezip/Sphere.avi", false, "g:\\multimedia\\movies\\Sphere.tbn" }}; - -INSTANTIATE_TEST_SUITE_P(NoArt, TestFileItemFallbackArt, ValuesIn(NoArtFiles)); - class TestFileItemBasePath : public AdvancedSettingsResetBase, public WithParamInterface<TestFileData> { diff --git a/xbmc/utils/ArtUtils.cpp b/xbmc/utils/ArtUtils.cpp index 386a0f1183b61..6394f6c7c89f1 100644 --- a/xbmc/utils/ArtUtils.cpp +++ b/xbmc/utils/ArtUtils.cpp @@ -178,6 +178,31 @@ std::string GetFolderThumb(const CFileItem& item, const std::string& folderJPG / return URIUtils::AddFileToFolder(strFolder, folderJPG); } +std::string GetLocalArt(const CFileItem& item, const std::string& artFile, bool useFolder) +{ + // no retrieving of empty art files from folders + if (useFolder && artFile.empty()) + return ""; + + std::string strFile = GetLocalArtBaseFilename(item, useFolder); + if (strFile.empty()) // empty filepath -> nothing to find + return ""; + + if (useFolder) + { + if (!artFile.empty()) + return URIUtils::AddFileToFolder(strFile, artFile); + } + else + { + if (artFile.empty()) // old thumbnail matching + return URIUtils::ReplaceExtension(strFile, ".tbn"); + else + return URIUtils::ReplaceExtension(strFile, "-" + artFile); + } + return ""; +} + std::string GetLocalArtBaseFilename(const CFileItem& item, bool& useFolder) { std::string strFile; diff --git a/xbmc/utils/ArtUtils.h b/xbmc/utils/ArtUtils.h index 4063f389ee707..99848308b22b2 100644 --- a/xbmc/utils/ArtUtils.h +++ b/xbmc/utils/ArtUtils.h @@ -26,6 +26,15 @@ void FillInDefaultIcon(CFileItem& item); */ std::string GetFolderThumb(const CFileItem& item, const std::string& folderJPG = "folder.jpg"); +/*! \brief Assemble the filename of a particular piece of local artwork for an item. + No file existence check is typically performed. + \param artFile the art file to search for. + \param useFolder whether to look in the folder for the art file. Defaults to false. + \return the path to the local artwork. + \sa FindLocalArt + */ +std::string GetLocalArt(const CFileItem& item, const std::string& artFile, bool useFolder = false); + /*! \brief Assemble the base filename of local artwork for an item, accounting for archives, stacks and multi-paths, and BDMV/VIDEO_TS folders. diff --git a/xbmc/utils/test/TestArtUtils.cpp b/xbmc/utils/test/TestArtUtils.cpp index 59d827e322d32..6f9f1967db17d 100644 --- a/xbmc/utils/test/TestArtUtils.cpp +++ b/xbmc/utils/test/TestArtUtils.cpp @@ -7,9 +7,14 @@ */ #include "FileItem.h" +#include "ServiceBroker.h" #include "URL.h" #include "filesystem/Directory.h" #include "platform/Filesystem.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/SettingsManager.h" #include "utils/ArtUtils.h" #include "utils/FileUtils.h" #include "utils/StringUtils.h" @@ -47,6 +52,21 @@ std::string unique_path(const std::string& input) return ret; } +class AdvancedSettingsResetBase : public testing::Test +{ +public: + AdvancedSettingsResetBase(); +}; + +AdvancedSettingsResetBase::AdvancedSettingsResetBase() +{ + // Force all advanced settings to be reset to defaults + const auto settings = CServiceBroker::GetSettingsComponent(); + CSettingsManager* settingsMgr = settings->GetSettings()->GetSettingsManager(); + settings->GetAdvancedSettings()->Uninitialize(*settingsMgr); + settings->GetAdvancedSettings()->Initialize(*settingsMgr); +} + struct ArtFilenameTest { std::string path; @@ -173,6 +193,58 @@ class FolderThumbTest : public testing::WithParamInterface<FolderTest>, public t { }; +struct LocalArtTest +{ + std::string file; + std::string art; + bool use_folder; + std::string base; +}; + +const auto local_art_tests = std::array{ + LocalArtTest{"c:\\dir\\filename.avi", "art.jpg", false, "c:\\dir\\filename-art.jpg"}, + LocalArtTest{"c:\\dir\\filename.avi", "art.jpg", true, "c:\\dir\\art.jpg"}, + LocalArtTest{"/dir/filename.avi", "art.jpg", false, "/dir/filename-art.jpg"}, + LocalArtTest{"/dir/filename.avi", "art.jpg", true, "/dir/art.jpg"}, + LocalArtTest{"smb://somepath/file.avi", "art.jpg", false, "smb://somepath/file-art.jpg"}, + LocalArtTest{"smb://somepath/file.avi", "art.jpg", true, "smb://somepath/art.jpg"}, + LocalArtTest{"stack:///path/to/movie-cd1.avi , /path/to/movie-cd2.avi", "art.jpg", false, + "/path/to/movie-art.jpg"}, + LocalArtTest{"stack:///path/to/movie-cd1.avi , /path/to/movie-cd2.avi", "art.jpg", true, + "/path/to/art.jpg"}, + LocalArtTest{ + "stack:///path/to/movie_name/cd1/some_file1.avi , /path/to/movie_name/cd2/some_file2.avi", + "art.jpg", true, "/path/to/movie_name/art.jpg"}, + LocalArtTest{"/home/user/TV Shows/Dexter/S1/1x01.avi", "art.jpg", false, + "/home/user/TV Shows/Dexter/S1/1x01-art.jpg"}, + LocalArtTest{"/home/user/TV Shows/Dexter/S1/1x01.avi", "art.jpg", true, + "/home/user/TV Shows/Dexter/S1/art.jpg"}, + LocalArtTest{"zip://g%3a%5cmultimedia%5cmovies%5cSphere%2ezip/Sphere.avi", "art.jpg", false, + "g:\\multimedia\\movies\\Sphere-art.jpg"}, + LocalArtTest{"zip://g%3a%5cmultimedia%5cmovies%5cSphere%2ezip/Sphere.avi", "art.jpg", true, + "g:\\multimedia\\movies\\art.jpg"}, + LocalArtTest{"/home/user/movies/movie_name/video_ts/VIDEO_TS.IFO", "art.jpg", false, + "/home/user/movies/movie_name/art.jpg"}, + LocalArtTest{"/home/user/movies/movie_name/video_ts/VIDEO_TS.IFO", "art.jpg", true, + "/home/user/movies/movie_name/art.jpg"}, + LocalArtTest{"/home/user/movies/movie_name/BDMV/index.bdmv", "art.jpg", false, + "/home/user/movies/movie_name/art.jpg"}, + LocalArtTest{"/home/user/movies/movie_name/BDMV/index.bdmv", "art.jpg", true, + "/home/user/movies/movie_name/art.jpg"}, + LocalArtTest{"c:\\dir\\filename.avi", "", false, "c:\\dir\\filename.tbn"}, + LocalArtTest{"/dir/filename.avi", "", false, "/dir/filename.tbn"}, + LocalArtTest{"smb://somepath/file.avi", "", false, "smb://somepath/file.tbn"}, + LocalArtTest{"/home/user/TV Shows/Dexter/S1/1x01.avi", "", false, + "/home/user/TV Shows/Dexter/S1/1x01.tbn"}, + LocalArtTest{"zip://g%3a%5cmultimedia%5cmovies%5cSphere%2ezip/Sphere.avi", "", false, + "g:\\multimedia\\movies\\Sphere.tbn"}, +}; + +class TestLocalArt : public AdvancedSettingsResetBase, + public testing::WithParamInterface<LocalArtTest> +{ +}; + } // namespace TEST_P(FillInDefaultIconTest, FillInDefaultIcon) @@ -199,6 +271,17 @@ TEST_P(FolderThumbTest, GetFolderThumb) INSTANTIATE_TEST_SUITE_P(TestArtUtils, FolderThumbTest, testing::ValuesIn(folder_thumb_tests)); +TEST_P(TestLocalArt, GetLocalArt) +{ + CFileItem item; + item.SetPath(GetParam().file); + std::string path = CURL(ART::GetLocalArt(item, GetParam().art, GetParam().use_folder)).Get(); + std::string compare = CURL(GetParam().base).Get(); + EXPECT_EQ(compare, path); +} + +INSTANTIATE_TEST_SUITE_P(TestArtUtils, TestLocalArt, testing::ValuesIn(local_art_tests)); + TEST_P(GetLocalArtBaseFilenameTest, GetLocalArtBaseFilename) { CFileItem item(GetParam().path, GetParam().isFolder); diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index e4e51a7f86b44..f333bb334bf5e 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -10748,7 +10748,7 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t } for (const auto &i : artwork) { - std::string savedThumb = item.GetLocalArt(i.first, false); + std::string savedThumb = ART::GetLocalArt(item, i.first, false); CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite); } if (actorThumbs) @@ -10894,7 +10894,7 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t } for (const auto &i : artwork) { - std::string savedThumb = item.GetLocalArt(i.first, false); + std::string savedThumb = ART::GetLocalArt(item, i.first, false); CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite); } } @@ -10992,7 +10992,7 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t for (const auto &i : artwork) { - std::string savedThumb = item.GetLocalArt(i.first, true); + std::string savedThumb = ART::GetLocalArt(item, i.first, true); CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite); } @@ -11011,7 +11011,7 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t seasonThumb = StringUtils::Format("season{:02}", i.first); for (const auto &j : i.second) { - std::string savedThumb(item.GetLocalArt(seasonThumb + "-" + j.first, true)); + std::string savedThumb(ART::GetLocalArt(item, seasonThumb + "-" + j.first, true)); if (!i.second.empty()) CServiceBroker::GetTextureCache()->Export(j.second, savedThumb, overwrite); } @@ -11094,7 +11094,7 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t } for (const auto &i : artwork) { - std::string savedThumb = item.GetLocalArt(i.first, false); + std::string savedThumb = ART::GetLocalArt(item, i.first, false); CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite); } if (actorThumbs) From 21892e9bc10ccfe0b7be15981256588248c8d3f7 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Sat, 11 May 2024 11:09:53 +0200 Subject: [PATCH 498/651] [XAudio2] share Xbox audio device enumeration with desktop and Windows 8.1 compatibility --- xbmc/cores/AudioEngine/CMakeLists.txt | 3 +- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 99 ++++++++++---- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h | 4 - .../Sinks/windows/AESinkFactoryWin.h | 7 +- .../Sinks/windows/AESinkFactoryWin10.cpp | 98 +------------- .../Sinks/windows/AESinkFactoryWin32.cpp | 1 - .../Sinks/windows/AESinkFactoryWinRT.cpp | 124 ++++++++++++++++++ xbmc/platform/win10/AsyncHelpers.h | 4 + 8 files changed, 213 insertions(+), 127 deletions(-) create mode 100644 xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWinRT.cpp diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt index 4a2531753e221..d3a5acc973a64 100644 --- a/xbmc/cores/AudioEngine/CMakeLists.txt +++ b/xbmc/cores/AudioEngine/CMakeLists.txt @@ -106,7 +106,8 @@ endif() if(CORE_SYSTEM_NAME MATCHES windows) list(APPEND SOURCES Sinks/AESinkWASAPI.cpp Sinks/AESinkXAudio.cpp - Sinks/windows/AESinkFactoryWin.cpp) + Sinks/windows/AESinkFactoryWin.cpp + Sinks/windows/AESinkFactoryWinRT.cpp) list(APPEND HEADERS Sinks/AESinkWASAPI.h Sinks/AESinkXAudio.h Sinks/windows/AESinkFactoryWin.h) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 1c6e3a2f36de1..84e5e601c727e 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -13,27 +13,62 @@ #include "cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h" #include "cores/AudioEngine/Utils/AEDeviceInfo.h" #include "cores/AudioEngine/Utils/AEUtil.h" +#include "utils/SystemInfo.h" #include "utils/log.h" -#ifdef TARGET_WINDOWS_STORE -#include "platform/win10/AsyncHelpers.h" -#endif #include "platform/win32/CharsetConverter.h" #include <algorithm> #include <stdint.h> #include <ksmedia.h> -#include <mfapi.h> -#include <mmdeviceapi.h> -#include <mmreg.h> -#include <wrl/implements.h> using namespace Microsoft::WRL; namespace { constexpr int XAUDIO_BUFFERS_IN_QUEUE = 2; + +HRESULT KXAudio2Create(IXAudio2** ppXAudio2, + UINT32 Flags X2DEFAULT(0), + XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) +{ + typedef HRESULT(__stdcall * XAudio2CreateInfoFunc)(_Outptr_ IXAudio2**, UINT32, + XAUDIO2_PROCESSOR); + static HMODULE dll = NULL; + static XAudio2CreateInfoFunc XAudio2CreateFn = nullptr; + + if (dll == NULL) + { + dll = LoadLibraryEx(L"xaudio2_9redist.dll", NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + + if (dll == NULL) + { + dll = LoadLibraryEx(L"xaudio2_9.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + + if (dll == NULL) + { + // Windows 8 compatibility + dll = LoadLibraryEx(L"xaudio2_8.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + + if (dll == NULL) + return HRESULT_FROM_WIN32(GetLastError()); + } + } + + XAudio2CreateFn = (XAudio2CreateInfoFunc)(void*)GetProcAddress(dll, "XAudio2Create"); + if (!XAudio2CreateFn) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + } + + if (XAudio2CreateFn) + return (*XAudio2CreateFn)(ppXAudio2, Flags, XAudio2Processor); + else + return E_FAIL; +} + } // namespace extern const char* WASAPIErrToStr(HRESULT err); @@ -50,7 +85,7 @@ inline void SafeDestroyVoice(TVoice **ppVoice) CAESinkXAudio::CAESinkXAudio() { - HRESULT hr = XAudio2Create(m_xAudio2.ReleaseAndGetAddressOf(), 0); + HRESULT hr = KXAudio2Create(m_xAudio2.ReleaseAndGetAddressOf(), 0); if (FAILED(hr)) { CLog::LogF(LOGERROR, "XAudio initialization failed."); @@ -283,7 +318,13 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo IXAudio2SourceVoice* mSourceVoice = nullptr; Microsoft::WRL::ComPtr<IXAudio2> xaudio2; - hr = XAudio2Create(xaudio2.ReleaseAndGetAddressOf(), eflags); + // ForegroundOnlyMedia/BackgroundCapableMedia replaced in Windows 10 by Movie/Media + const AUDIO_STREAM_CATEGORY streamCategory{ + CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10) + ? AudioCategory_Media + : AudioCategory_ForegroundOnlyMedia}; + + hr = KXAudio2Create(xaudio2.ReleaseAndGetAddressOf(), eflags); if (FAILED(hr)) { CLog::LogF(LOGERROR, "failed to activate XAudio for capability testing ({})", @@ -291,7 +332,7 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo return; } - for(RendererDetail& details : CAESinkFactoryWin::GetRendererDetails()) + for (RendererDetail& details : CAESinkFactoryWin::GetRendererDetailsWinRT()) { deviceInfo.m_channels.Reset(); deviceInfo.m_dataFormats.clear(); @@ -304,7 +345,7 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo deviceChannels += AEChannelNames[c]; } - const std::wstring deviceId = KODI::PLATFORM::WINDOWS::ToW(details.strDevicePath); + const std::wstring deviceId = KODI::PLATFORM::WINDOWS::ToW(details.strDeviceId); /* Test format for PCM format iteration */ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); @@ -314,9 +355,15 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, - wfxex.Format.nSamplesPerSec, 0, deviceId.c_str(), nullptr, - AudioCategory_Media); + hr = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, + wfxex.Format.nSamplesPerSec, 0, deviceId.c_str(), nullptr, + streamCategory); + + if (FAILED(hr)) + { + CLog::LogF(LOGERROR, "failed to create mastering voice (:X)", hr); + return; + } for (int p = AE_FMT_FLOAT; p > AE_FMT_INVALID; p--) { @@ -367,7 +414,7 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec, 0, deviceId.c_str(), nullptr, - AudioCategory_Media); + streamCategory); hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format); if (SUCCEEDED(hr)) @@ -377,7 +424,7 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo SafeDestroyVoice(&mSourceVoice); SafeDestroyVoice(&mMasterVoice); - deviceInfo.m_deviceName = details.strDevicePath; + deviceInfo.m_deviceName = details.strDeviceId; deviceInfo.m_displayName = details.strWinDevType.append(details.strDescription); deviceInfo.m_displayNameExtra = std::string("XAudio: ").append(details.strDescription); deviceInfo.m_deviceType = details.eDeviceType; @@ -442,11 +489,17 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form HRESULT hr; IXAudio2MasteringVoice* pMasterVoice = nullptr; + // ForegroundOnlyMedia/BackgroundCapableMedia replaced in Windows 10 by Movie/Media + const AUDIO_STREAM_CATEGORY streamCategory{ + CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10) + ? AudioCategory_Media + : AudioCategory_ForegroundOnlyMedia}; if (!bdefault) { - hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec, - 0, device.c_str(), nullptr, AudioCategory_Media); + hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels, + wfxex.Format.nSamplesPerSec, 0, device.c_str(), nullptr, + streamCategory); } if (!pMasterVoice) @@ -461,8 +514,9 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form // smartphone issue: providing device ID (even default ID) causes E_NOINTERFACE result // workaround: device = nullptr will initialize default audio endpoint - hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec, - 0, 0, nullptr, AudioCategory_Media); + hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels, + wfxex.Format.nSamplesPerSec, 0, nullptr, nullptr, + streamCategory); if (FAILED(hr) || !pMasterVoice) { CLog::LogF(LOGINFO, "Could not retrieve the default XAudio audio endpoint ({}).", @@ -528,8 +582,9 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form wfxex.Format.nSamplesPerSec = WASAPISampleRates[i]; wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign; - hr = m_xAudio2->CreateMasteringVoice(&m_masterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec, - 0, device.c_str(), nullptr, AudioCategory_Media); + hr = m_xAudio2->CreateMasteringVoice(&m_masterVoice, wfxex.Format.nChannels, + wfxex.Format.nSamplesPerSec, 0, device.c_str(), + nullptr, streamCategory); if (SUCCEEDED(hr)) { hr = m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback); diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h index f398ca9b0e32f..1335714d09d74 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h @@ -13,14 +13,10 @@ #include <stdint.h> -#include <mmdeviceapi.h> -#include <ppltasks.h> -#include <wrl/implements.h> #include <x3daudio.h> #include <xapofx.h> #include <xaudio2.h> #include <xaudio2fx.h> -#pragma comment(lib,"xaudio2.lib") class CAESinkXAudio : public IAESink { diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h index d308673b91510..68a8bd4a5ce89 100644 --- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h @@ -175,7 +175,6 @@ static const sampleFormat testFormats[] = { {KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 32 struct RendererDetail { - std::string strDevicePath; std::string strDeviceId; std::string strDescription; std::string strWinDevType; @@ -198,9 +197,13 @@ class CAESinkFactoryWin { public: /* - Gets list of audio renderers available on platform + Gets list of available audio renderers - using MMDevice */ static std::vector<RendererDetail> GetRendererDetails(); + /* + Gets list of available audio renderers - using WinRT + */ + static std::vector<RendererDetail> GetRendererDetailsWinRT(); /* Gets default device id */ diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp index 2492f02279495..ea3eccf56f69e 100644 --- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp @@ -6,117 +6,21 @@ * See LICENSES/README.md for more information. */ #include "AESinkFactoryWin.h" -#include "utils/log.h" -#include "platform/win10/AsyncHelpers.h" #include "platform/win32/CharsetConverter.h" #include <mmdeviceapi.h> #include <mmreg.h> #include <winrt/Windows.Devices.Enumeration.h> -#include <winrt/Windows.Media.Devices.Core.h> #include <winrt/Windows.Media.Devices.h> using namespace winrt::Windows::Devices::Enumeration; using namespace winrt::Windows::Media::Devices; -using namespace winrt::Windows::Media::Devices::Core; using namespace Microsoft::WRL; -static winrt::hstring PKEY_Device_FriendlyName = L"System.ItemNameDisplay"; -static winrt::hstring PKEY_AudioEndpoint_FormFactor = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 0"; -static winrt::hstring PKEY_AudioEndpoint_ControlPanelPageProvider = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 1"; -static winrt::hstring PKEY_AudioEndpoint_Association = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 2"; -static winrt::hstring PKEY_AudioEndpoint_PhysicalSpeakers = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 3"; -static winrt::hstring PKEY_AudioEndpoint_GUID = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 4"; -static winrt::hstring PKEY_AudioEndpoint_Disable_SysFx = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 5"; -static winrt::hstring PKEY_AudioEndpoint_FullRangeSpeakers = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 6"; -static winrt::hstring PKEY_AudioEndpoint_Supports_EventDriven_Mode = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 7"; -static winrt::hstring PKEY_AudioEndpoint_JackSubType = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 8"; -static winrt::hstring PKEY_AudioEndpoint_Default_VolumeInDb = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 9"; -static winrt::hstring PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0"; -static winrt::hstring PKEY_Device_EnumeratorName = L"{a45c254e-df1c-4efd-8020-67d146a850e0} 24"; - std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails() { - std::vector<RendererDetail> list; - try - { - // Get the string identifier of the audio renderer - auto defaultId = MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default); - auto audioSelector = MediaDevice::GetAudioRenderSelector(); - - // Add custom properties to the query - DeviceInformationCollection devInfocollection = Wait(DeviceInformation::FindAllAsync(audioSelector, - { - PKEY_AudioEndpoint_FormFactor, - PKEY_AudioEndpoint_GUID, - PKEY_AudioEndpoint_PhysicalSpeakers, - PKEY_AudioEngine_DeviceFormat, - PKEY_Device_EnumeratorName - })); - if (devInfocollection == nullptr || devInfocollection.Size() == 0) - goto failed; - - for (unsigned int i = 0; i < devInfocollection.Size(); i++) - { - RendererDetail details; - - DeviceInformation devInfo = devInfocollection.GetAt(i); - if (devInfo.Properties().Size() == 0) - goto failed; - - winrt::IInspectable propObj = nullptr; - - propObj = devInfo.Properties().Lookup(PKEY_AudioEndpoint_FormFactor); - if (!propObj) - goto failed; - - details.strWinDevType = winEndpoints[propObj.as<winrt::IPropertyValue>().GetUInt32()].winEndpointType; - details.eDeviceType = winEndpoints[propObj.as<winrt::IPropertyValue>().GetUInt32()].aeDeviceType; - - unsigned long ulChannelMask = 0; - unsigned int nChannels = 0; - - propObj = devInfo.Properties().Lookup(PKEY_AudioEngine_DeviceFormat); - if (propObj) - { - winrt::com_array<uint8_t> com_arr; - propObj.as<winrt::IPropertyValue>().GetUInt8Array(com_arr); - - WAVEFORMATEXTENSIBLE* smpwfxex = (WAVEFORMATEXTENSIBLE*)com_arr.data(); - nChannels = std::max(std::min(smpwfxex->Format.nChannels, (WORD)8), (WORD)2); - ulChannelMask = smpwfxex->dwChannelMask; - } - else - { - // suppose stereo - nChannels = 2; - ulChannelMask = 3; - } - - propObj = devInfo.Properties().Lookup(PKEY_AudioEndpoint_PhysicalSpeakers); - details.uiChannelMask = propObj ? propObj.as<winrt::IPropertyValue>().GetUInt32() : ulChannelMask; - details.nChannels = nChannels; - - details.strDescription = KODI::PLATFORM::WINDOWS::FromW(devInfo.Name().c_str()); - details.strDeviceId = KODI::PLATFORM::WINDOWS::FromW(devInfo.Id().c_str()); - - // on Windows UWP device Id is same as Path - details.strDevicePath = details.strDeviceId; - - details.bDefault = (devInfo.Id() == defaultId); - - list.push_back(details); - } - return list; - } - catch (...) - { - } - -failed: - CLog::Log(LOGERROR, __FUNCTION__": Failed to enumerate audio renderer devices."); - return list; + return GetRendererDetailsWinRT(); } class CAudioInterfaceActivator : public winrt::implements<CAudioInterfaceActivator, IActivateAudioInterfaceCompletionHandler> diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp index e7d81282ae8af..d5869c5e2e312 100644 --- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp @@ -147,7 +147,6 @@ std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails() if (wstrDDID.compare(pwszID) == 0) details.bDefault = true; - details.strDevicePath = KODI::PLATFORM::WINDOWS::FromW(pwszID); CoTaskMemFree(pwszID); } diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWinRT.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWinRT.cpp new file mode 100644 index 0000000000000..d69c64f69ae8e --- /dev/null +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWinRT.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2024 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 "AESinkFactoryWin.h" +#include "utils/log.h" + +#include "platform/win10/AsyncHelpers.h" +#include "platform/win32/CharsetConverter.h" + +#include <winrt/windows.devices.enumeration.h> +#include <winrt/windows.foundation.collections.h> +#include <winrt/windows.foundation.h> +#include <winrt/windows.media.devices.core.h> +#include <winrt/windows.media.devices.h> + +namespace winrt +{ +using namespace Windows::Foundation; +} + +using namespace winrt::Windows::Devices::Enumeration; +using namespace winrt::Windows::Media::Devices; +using namespace winrt::Windows::Media::Devices::Core; + +// clang-format off +static winrt::hstring PKEY_Device_FriendlyName = L"System.ItemNameDisplay"; +static winrt::hstring PKEY_AudioEndpoint_FormFactor = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 0"; +static winrt::hstring PKEY_AudioEndpoint_ControlPanelPageProvider = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 1"; +static winrt::hstring PKEY_AudioEndpoint_Association = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 2"; +static winrt::hstring PKEY_AudioEndpoint_PhysicalSpeakers = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 3"; +static winrt::hstring PKEY_AudioEndpoint_GUID = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 4"; +static winrt::hstring PKEY_AudioEndpoint_Disable_SysFx = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 5"; +static winrt::hstring PKEY_AudioEndpoint_FullRangeSpeakers = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 6"; +static winrt::hstring PKEY_AudioEndpoint_Supports_EventDriven_Mode = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 7"; +static winrt::hstring PKEY_AudioEndpoint_JackSubType = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 8"; +static winrt::hstring PKEY_AudioEndpoint_Default_VolumeInDb = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 9"; +static winrt::hstring PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0"; +static winrt::hstring PKEY_Device_EnumeratorName = L"{a45c254e-df1c-4efd-8020-67d146a850e0} 24"; +// clang-format on + +std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetailsWinRT() +{ + std::vector<RendererDetail> list; + try + { + // Get the string identifier of the audio renderer + auto defaultId = MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default); + auto audioSelector = MediaDevice::GetAudioRenderSelector(); + + // Add custom properties to the query + DeviceInformationCollection devInfoCollection = Wait(DeviceInformation::FindAllAsync( + audioSelector, {PKEY_AudioEndpoint_FormFactor, PKEY_AudioEndpoint_GUID, + PKEY_AudioEndpoint_PhysicalSpeakers, PKEY_AudioEngine_DeviceFormat, + PKEY_Device_EnumeratorName})); + + if (devInfoCollection == nullptr || devInfoCollection.Size() == 0) + goto failed; + + for (const DeviceInformation& devInfo : devInfoCollection) + { + RendererDetail details; + + if (devInfo.Properties().Size() == 0) + goto failed; + + winrt::IInspectable propObj = nullptr; + + propObj = devInfo.Properties().Lookup(PKEY_AudioEndpoint_FormFactor); + if (!propObj) + goto failed; + + const uint32_t indexFF{propObj.as<winrt::IPropertyValue>().GetUInt32()}; + details.strWinDevType = winEndpoints[indexFF].winEndpointType; + details.eDeviceType = winEndpoints[indexFF].aeDeviceType; + + DWORD ulChannelMask = 0; + unsigned int nChannels = 0; + + propObj = devInfo.Properties().Lookup(PKEY_AudioEngine_DeviceFormat); + if (propObj) + { + winrt::com_array<uint8_t> com_arr; + propObj.as<winrt::IPropertyValue>().GetUInt8Array(com_arr); + + WAVEFORMATEXTENSIBLE* smpwfxex = (WAVEFORMATEXTENSIBLE*)com_arr.data(); + nChannels = std::max(std::min(smpwfxex->Format.nChannels, (WORD)8), (WORD)2); + ulChannelMask = smpwfxex->dwChannelMask; + } + else + { + // suppose stereo + nChannels = 2; + ulChannelMask = 3; + } + + propObj = devInfo.Properties().Lookup(PKEY_AudioEndpoint_PhysicalSpeakers); + + details.uiChannelMask = propObj ? propObj.as<winrt::IPropertyValue>().GetUInt32() + : static_cast<unsigned int>(ulChannelMask); + + details.nChannels = nChannels; + + details.strDescription = KODI::PLATFORM::WINDOWS::FromW(devInfo.Name().c_str()); + details.strDeviceId = KODI::PLATFORM::WINDOWS::FromW(devInfo.Id().c_str()); + + details.bDefault = (devInfo.Id() == defaultId); + + list.push_back(details); + } + return list; + } + catch (...) + { + } + +failed: + CLog::LogF(LOGERROR, "Failed to enumerate audio renderer devices."); + return list; +} diff --git a/xbmc/platform/win10/AsyncHelpers.h b/xbmc/platform/win10/AsyncHelpers.h index dd2a290b9f688..7ad9320dd532b 100644 --- a/xbmc/platform/win10/AsyncHelpers.h +++ b/xbmc/platform/win10/AsyncHelpers.h @@ -12,6 +12,10 @@ #include <ppltasks.h> #include <sdkddkver.h> +#ifndef TARGET_WINDOWS_STORE +#include <winrt/windows.foundation.h> +#endif + namespace winrt { using namespace Windows::Foundation; From 4c454eff32de876e86f60435bf36a6d0528dab40 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Thu, 19 Sep 2024 12:47:28 -0400 Subject: [PATCH 499/651] [XAudio2] error handling improvements --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 84e5e601c727e..2bbc09d29c122 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -88,7 +88,7 @@ CAESinkXAudio::CAESinkXAudio() HRESULT hr = KXAudio2Create(m_xAudio2.ReleaseAndGetAddressOf(), 0); if (FAILED(hr)) { - CLog::LogF(LOGERROR, "XAudio initialization failed."); + CLog::LogF(LOGERROR, "XAudio initialization failed, error {:X}.", hr); } #ifdef _DEBUG else @@ -124,6 +124,13 @@ void CAESinkXAudio::Register() std::unique_ptr<IAESink> CAESinkXAudio::Create(std::string& device, AEAudioFormat& desiredFormat) { auto sink = std::make_unique<CAESinkXAudio>(); + + if (!sink->m_xAudio2) + { + CLog::LogF(LOGERROR, "XAudio2 not loaded."); + return {}; + } + if (sink->Initialize(desiredFormat, device)) return sink; @@ -412,13 +419,15 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo wfxex.Format.nSamplesPerSec = WASAPISampleRates[j]; wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign; - xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, - wfxex.Format.nSamplesPerSec, 0, deviceId.c_str(), nullptr, - streamCategory); - hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format); + if (SUCCEEDED(xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, + wfxex.Format.nSamplesPerSec, 0, deviceId.c_str(), + nullptr, streamCategory))) + { + hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format); - if (SUCCEEDED(hr)) - deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]); + if (SUCCEEDED(hr)) + deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]); + } } SafeDestroyVoice(&mSourceVoice); From 9353df3cdb1c2f2b60bee73782fae4ad8306c275 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Thu, 19 Sep 2024 13:35:58 -0400 Subject: [PATCH 500/651] [XAudio2] skip testing of sampling rates not supported by XAudio2 --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 2bbc09d29c122..e250d55f7012e 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -413,6 +413,10 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo for (int j = 0; j < WASAPISampleRateCount; j++) { + if (WASAPISampleRates[j] < XAUDIO2_MIN_SAMPLE_RATE || + WASAPISampleRates[j] > XAUDIO2_MAX_SAMPLE_RATE) + continue; + SafeDestroyVoice(&mSourceVoice); SafeDestroyVoice(&mMasterVoice); @@ -588,6 +592,10 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form for (int i = 0 ; i < WASAPISampleRateCount; i++) { + if (WASAPISampleRates[j] < XAUDIO2_MIN_SAMPLE_RATE || + WASAPISampleRates[j] > XAUDIO2_MAX_SAMPLE_RATE) + continue; + wfxex.Format.nSamplesPerSec = WASAPISampleRates[i]; wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign; From ef8d4123db4f17cb1750d715e7ed2b4ab7f64e03 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Thu, 19 Sep 2024 13:37:42 -0400 Subject: [PATCH 501/651] [XAudio2] avoid leak + fix voice creation for closest match --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index e250d55f7012e..add7388a70a78 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -596,6 +596,9 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form WASAPISampleRates[j] > XAUDIO2_MAX_SAMPLE_RATE) continue; + SafeDestroyVoice(&m_sourceVoice); + SafeDestroyVoice(&m_masterVoice); + wfxex.Format.nSamplesPerSec = WASAPISampleRates[i]; wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign; @@ -622,9 +625,19 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form if (closestMatch >= 0) { + // Closest match may be different from the last successful sample rate tested + SafeDestroyVoice(&m_sourceVoice); + SafeDestroyVoice(&m_masterVoice); + wfxex.Format.nSamplesPerSec = WASAPISampleRates[closestMatch]; wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign; - goto initialize; + + if (SUCCEEDED(m_xAudio2->CreateMasteringVoice(&m_masterVoice, wfxex.Format.nChannels, + wfxex.Format.nSamplesPerSec, 0, + device.c_str(), nullptr, streamCategory)) && + SUCCEEDED(m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0, + XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback))) + goto initialize; } } } From 2a1bf30034e613b9980ee77d2196a0a8b379d29b Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Mon, 23 Sep 2024 20:02:13 -0400 Subject: [PATCH 502/651] [XAudio2] fix device name used to find fallback format in sink initialization --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index add7388a70a78..792c8ffc6a501 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -502,6 +502,7 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form HRESULT hr; IXAudio2MasteringVoice* pMasterVoice = nullptr; + const wchar_t* pDevice = device.c_str(); // ForegroundOnlyMedia/BackgroundCapableMedia replaced in Windows 10 by Movie/Media const AUDIO_STREAM_CATEGORY streamCategory{ CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10) @@ -511,7 +512,7 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form if (!bdefault) { hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels, - wfxex.Format.nSamplesPerSec, 0, device.c_str(), nullptr, + wfxex.Format.nSamplesPerSec, 0, pDevice, nullptr, streamCategory); } @@ -524,11 +525,11 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form "Trying the default device...", KODI::PLATFORM::WINDOWS::FromW(device)); } - // smartphone issue: providing device ID (even default ID) causes E_NOINTERFACE result // workaround: device = nullptr will initialize default audio endpoint + pDevice = nullptr; hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels, - wfxex.Format.nSamplesPerSec, 0, nullptr, nullptr, + wfxex.Format.nSamplesPerSec, 0, pDevice, nullptr, streamCategory); if (FAILED(hr) || !pMasterVoice) { @@ -603,8 +604,8 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign; hr = m_xAudio2->CreateMasteringVoice(&m_masterVoice, wfxex.Format.nChannels, - wfxex.Format.nSamplesPerSec, 0, device.c_str(), - nullptr, streamCategory); + wfxex.Format.nSamplesPerSec, 0, pDevice, nullptr, + streamCategory); if (SUCCEEDED(hr)) { hr = m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback); @@ -633,8 +634,8 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign; if (SUCCEEDED(m_xAudio2->CreateMasteringVoice(&m_masterVoice, wfxex.Format.nChannels, - wfxex.Format.nSamplesPerSec, 0, - device.c_str(), nullptr, streamCategory)) && + wfxex.Format.nSamplesPerSec, 0, pDevice, + nullptr, streamCategory)) && SUCCEEDED(m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback))) goto initialize; From 4e4eb6b3a90a18e078f3319d00c8435b29a7eac9 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Fri, 20 Sep 2024 23:54:47 -0400 Subject: [PATCH 503/651] [WASAPI] fix stream types and frequencies enumeration --- xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp index 5877e9f319589..32893d62dfa05 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp @@ -396,7 +396,10 @@ void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo deviceInfo.m_channels.Reset(); deviceInfo.m_dataFormats.clear(); deviceInfo.m_sampleRates.clear(); + deviceInfo.m_streamTypes.clear(); deviceChannels.Reset(); + add192 = false; + add48 = false; for (unsigned int c = 0; c < WASAPI_SPEAKER_COUNT; c++) { From 5f753a18fdfed02617a25b341acfb7836e81ead7 Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Wed, 25 Sep 2024 16:05:43 +0200 Subject: [PATCH 504/651] [DirectoryFactory] If FileItem has mimetype store to CURL This to be restored when the FileItem will be reconstructed later to prevent make HTTP HEAD requests --- xbmc/filesystem/DirectoryFactory.cpp | 9 ++++++++- xbmc/filesystem/PlaylistFileDirectory.cpp | 10 ++++------ xbmc/playlists/PlayListFactory.cpp | 14 ++++++++++++++ xbmc/playlists/PlayListFactory.h | 1 + 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/xbmc/filesystem/DirectoryFactory.cpp b/xbmc/filesystem/DirectoryFactory.cpp index 465816c4b9002..e9c76cba97569 100644 --- a/xbmc/filesystem/DirectoryFactory.cpp +++ b/xbmc/filesystem/DirectoryFactory.cpp @@ -97,7 +97,14 @@ using namespace XFILE; */ IDirectory* CDirectoryFactory::Create(const CFileItem& item) { - return Create(CURL{item.GetDynPath()}); + CURL curl{item.GetDynPath()}; + + // Store the mimetype, allowing the PlayListFactory to set it on the created FileItem + const std::string& mimeType = item.GetMimeType(); + if (!mimeType.empty()) + curl.SetOption("mimetype", mimeType); + + return Create(curl); } /*! diff --git a/xbmc/filesystem/PlaylistFileDirectory.cpp b/xbmc/filesystem/PlaylistFileDirectory.cpp index d95989067f0f3..18e5d7affa0cb 100644 --- a/xbmc/filesystem/PlaylistFileDirectory.cpp +++ b/xbmc/filesystem/PlaylistFileDirectory.cpp @@ -25,12 +25,11 @@ namespace XFILE bool CPlaylistFileDirectory::GetDirectory(const CURL& url, CFileItemList& items) { - const std::string pathToUrl = url.Get(); - std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(pathToUrl)); + std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(url)); if (nullptr != pPlayList) { // load it - if (!pPlayList->Load(pathToUrl)) + if (!pPlayList->Load(url.Get())) return false; //hmmm unable to load playlist? PLAYLIST::CPlayList playlist = *pPlayList; @@ -47,12 +46,11 @@ namespace XFILE bool CPlaylistFileDirectory::ContainsFiles(const CURL& url) { - const std::string pathToUrl = url.Get(); - std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(pathToUrl)); + std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(url)); if (nullptr != pPlayList) { // load it - if (!pPlayList->Load(pathToUrl)) + if (!pPlayList->Load(url.Get())) return false; //hmmm unable to load playlist? return (pPlayList->size() > 1); diff --git a/xbmc/playlists/PlayListFactory.cpp b/xbmc/playlists/PlayListFactory.cpp index 7fe5ff6fcb9fa..63b9bd401d471 100644 --- a/xbmc/playlists/PlayListFactory.cpp +++ b/xbmc/playlists/PlayListFactory.cpp @@ -9,6 +9,7 @@ #include "PlayListFactory.h" #include "FileItem.h" +#include "URL.h" #include "network/NetworkFileItemClassify.h" #include "playlists/PlayListASX.h" #include "playlists/PlayListB4S.h" @@ -25,6 +26,19 @@ namespace KODI::PLAYLIST { +CPlayList* CPlayListFactory::Create(const CURL& url) +{ + CFileItem item{url.Get(), false}; + + if (url.HasOption("mimetype")) + { + item.SetContentLookup(false); + item.SetMimeType(url.GetOption("mimetype")); + } + + return Create(item); +} + CPlayList* CPlayListFactory::Create(const std::string& filename) { CFileItem item(filename,false); diff --git a/xbmc/playlists/PlayListFactory.h b/xbmc/playlists/PlayListFactory.h index 5161150ea5588..410c1e392dff9 100644 --- a/xbmc/playlists/PlayListFactory.h +++ b/xbmc/playlists/PlayListFactory.h @@ -20,6 +20,7 @@ namespace KODI::PLAYLIST class CPlayListFactory { public: + static CPlayList* Create(const CURL& url); static CPlayList* Create(const std::string& filename); static CPlayList* Create(const CFileItem& item); static bool IsPlaylist(const CURL& url); From 469ea33744fc71973db43513af186a13d74c3e3d Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Sat, 21 Sep 2024 02:26:36 -0400 Subject: [PATCH 505/651] [WASAPI] fix supported sample rates enumeration for devices without 16bit formats --- xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp index 32893d62dfa05..c2ea453e35d52 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp @@ -595,8 +595,23 @@ void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO; wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - wfxex.Format.wBitsPerSample = 16; - wfxex.Samples.wValidBitsPerSample = 16; + + // 16 bits is most widely supported and likely to have the widest range of sample rates + if (deviceInfo.m_dataFormats.empty() || + std::find(deviceInfo.m_dataFormats.cbegin(), deviceInfo.m_dataFormats.cend(), + AE_FMT_S16NE) != deviceInfo.m_dataFormats.cend()) + { + wfxex.Format.wBitsPerSample = 16; + wfxex.Samples.wValidBitsPerSample = 16; + } + else + { + const AEDataFormat fmt = deviceInfo.m_dataFormats.front(); + wfxex.Format.wBitsPerSample = CAEUtil::DataFormatToBits(fmt); + wfxex.Samples.wValidBitsPerSample = + (fmt == AE_FMT_S24NE4MSB ? 24 : wfxex.Format.wBitsPerSample); + } + wfxex.Format.nChannels = 2; wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3); wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign; From 92870b7e37593de99ce338e1ebbc69d43d2ae744 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Tue, 24 Sep 2024 04:02:48 -0400 Subject: [PATCH 506/651] [WASAPI] clearer log message for unsupported AE provided format --- xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp index c2ea453e35d52..1f6216d3c1aff 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp @@ -763,17 +763,17 @@ bool CAESinkWASAPI::InitializeExclusive(AEAudioFormat &format) else if (format.m_dataFormat == AE_FMT_RAW) //No sense in trying other formats for passthrough. return false; - CLog::Log(LOGWARNING, - "AESinkWASAPI: IsFormatSupported failed ({}) - trying to find a compatible format", - WASAPIErrToStr(hr)); + CLog::LogF(LOGWARNING, + "format {} not supported by the device - trying to find a compatible format", + CAEUtil::DataFormatToStr(format.m_dataFormat)); requestedChannels = wfxex.Format.nChannels; desired_map = CAESinkFactoryWin::SpeakerMaskFromAEChannels(format.m_channelLayout); /* The requested format is not supported by the device. Find something that works */ - CLog::Log(LOGWARNING, - "AESinkWASAPI: Input channels are [{}] - Trying to find a matching output layout", - std::string(format.m_channelLayout)); + CLog::LogF(LOGWARNING, "Input channels are [{}] - Trying to find a matching output layout", + std::string(format.m_channelLayout)); + for (int layout = -1; layout <= (int)ARRAYSIZE(layoutsList); layout++) { // if requested layout is not supported, try standard layouts which contain From 56215938f7e494dbb6543507a8a8199a78fed6a8 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Tue, 24 Sep 2024 04:04:50 -0400 Subject: [PATCH 507/651] [WASAPI] set stream audio category --- xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp index 1f6216d3c1aff..0c0e1bc7e7f36 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp @@ -904,6 +904,20 @@ bool CAESinkWASAPI::InitializeExclusive(AEAudioFormat &format) format.m_sampleRate = wfxex.Format.nSamplesPerSec; //PCM: Sample rate. RAW: Link speed format.m_frameSize = (wfxex.Format.wBitsPerSample >> 3) * wfxex.Format.nChannels; + ComPtr<IAudioClient2> audioClient2; + if (SUCCEEDED(m_pAudioClient.As(&audioClient2))) + { + AudioClientProperties props = {}; + props.cbSize = sizeof(props); + // ForegroundOnlyMedia/BackgroundCapableMedia replaced in Windows 10 by Movie/Media + props.eCategory = CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10) + ? AudioCategory_Media + : AudioCategory_ForegroundOnlyMedia; + + if (FAILED(hr = audioClient2->SetClientProperties(&props))) + CLog::LogF(LOGERROR, "unable to set audio category, {}", WASAPIErrToStr(hr)); + } + REFERENCE_TIME audioSinkBufferDurationMsec, hnsLatency; audioSinkBufferDurationMsec = (REFERENCE_TIME)500000; From c1864c86b533e84cc9f1ab769a71499d346eead5 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Tue, 24 Sep 2024 23:16:28 -0400 Subject: [PATCH 508/651] [WASAPI] use silent buffer flag for initial silent frames. --- xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp index 0c0e1bc7e7f36..02357c123d5e4 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp @@ -254,8 +254,7 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t **data, unsigned int frames, unsi return 0; HRESULT hr; - BYTE *buf; - DWORD flags = 0; + BYTE* buf; #ifndef _DEBUG LARGE_INTEGER timerStart; @@ -293,9 +292,8 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t **data, unsigned int frames, unsi return INT_MAX; } - memset(buf, 0, NumFramesRequested * m_format.m_frameSize); //fill buffer with silence - - hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested, flags); //pass back to audio driver + hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested, + AUDCLNT_BUFFERFLAGS_SILENT); //pass back to audio driver if (FAILED(hr)) { #ifdef _DEBUG @@ -359,7 +357,7 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t **data, unsigned int frames, unsi NumFramesRequested * m_format.m_frameSize); m_bufferPtr = 0; - hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested, flags); //pass back to audio driver + hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested, 0); //pass back to audio driver if (FAILED(hr)) { #ifdef _DEBUG From 62d2c13eb7a3d84f2ab21a797ed4efde5f250691 Mon Sep 17 00:00:00 2001 From: fuzzard <fuzzard@kodi.tv> Date: Fri, 13 Sep 2024 15:14:31 +1000 Subject: [PATCH 509/651] [tools/depends][native] Bump openssl 3.0.15 --- tools/depends/native/openssl/OPENSSL-VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/depends/native/openssl/OPENSSL-VERSION b/tools/depends/native/openssl/OPENSSL-VERSION index 424c6d4036e14..a7b9b2c7177de 100644 --- a/tools/depends/native/openssl/OPENSSL-VERSION +++ b/tools/depends/native/openssl/OPENSSL-VERSION @@ -1,4 +1,4 @@ LIBNAME=openssl -VERSION=3.0.13 +VERSION=3.0.15 ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz -SHA512=22f4096781f0b075f5bf81bd39a0f97e111760dfa73b6f858f6bb54968a7847944d74969ae10f9a51cc21a2f4af20d9a4c463649dc824f5e439e196d6764c4f9 +SHA512=acd80f2f7924d90c1416946a5c61eff461926ad60f4821bb6b08845ea18f8452fd5e88a2c2c5bd0d7590a792cb8341a3f3be042fd0a5b6c9c1b84a497c347bbf From 4c7fb88b83bec484ffff0a5db8405d3e355f3ac5 Mon Sep 17 00:00:00 2001 From: fuzzard <fuzzard@kodi.tv> Date: Fri, 13 Sep 2024 15:14:46 +1000 Subject: [PATCH 510/651] [tools/depends][target] Bump openssl 3.0.15 --- tools/depends/target/openssl/OPENSSL-VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/depends/target/openssl/OPENSSL-VERSION b/tools/depends/target/openssl/OPENSSL-VERSION index 424c6d4036e14..a7b9b2c7177de 100644 --- a/tools/depends/target/openssl/OPENSSL-VERSION +++ b/tools/depends/target/openssl/OPENSSL-VERSION @@ -1,4 +1,4 @@ LIBNAME=openssl -VERSION=3.0.13 +VERSION=3.0.15 ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz -SHA512=22f4096781f0b075f5bf81bd39a0f97e111760dfa73b6f858f6bb54968a7847944d74969ae10f9a51cc21a2f4af20d9a4c463649dc824f5e439e196d6764c4f9 +SHA512=acd80f2f7924d90c1416946a5c61eff461926ad60f4821bb6b08845ea18f8452fd5e88a2c2c5bd0d7590a792cb8341a3f3be042fd0a5b6c9c1b84a497c347bbf From 62985e739c4b4827a5151c39816f85ed35210b64 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 27 Sep 2024 08:42:02 +0200 Subject: [PATCH 511/651] [PVR][video] Fix loading of recording folder resume information. --- xbmc/pvr/filesystem/PVRGUIDirectory.cpp | 48 +++++++++++++++++++++++++ xbmc/pvr/filesystem/PVRGUIDirectory.h | 8 +++++ xbmc/video/VideoUtils.cpp | 18 +++++----- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp index d53e8279a9b8d..1fadc5d6d278c 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp @@ -429,6 +429,54 @@ void GetGetRecordingsSubDirectories(const CPVRRecordingsPath& recParentPath, } // unnamed namespace +bool CPVRGUIDirectory::GetRecordingsDirectoryInfo(CFileItem& item) +{ + CFileItemList results; + const CPVRGUIDirectory dir{item.GetPath()}; + if (dir.GetRecordingsDirectory(results)) + { + item.SetLabelPreformatted(true); + item.SetProperty("totalepisodes", 0); + item.SetProperty("watchedepisodes", 0); + item.SetProperty("unwatchedepisodes", 0); + item.SetProperty("inprogressepisodes", 0); + + int64_t sizeInBytes{0}; + + for (const auto& result : results.GetList()) + { + const auto recording{result->GetPVRRecordingInfoTag()}; + if (!recording) + continue; + + if (item.m_dateTime.IsValid() || (item.m_dateTime < recording->RecordingTimeAsLocalTime())) + item.m_dateTime = recording->RecordingTimeAsLocalTime(); + + item.IncrementProperty("totalepisodes", 1); + + if (recording->GetPlayCount() == 0) + item.IncrementProperty("unwatchedepisodes", 1); + else + item.IncrementProperty("watchedepisodes", 1); + + if (recording->GetResumePoint().IsPartWay()) + item.IncrementProperty("inprogressepisodes", 1); + + sizeInBytes += recording->GetSizeInBytes(); + } + + item.SetProperty("recordingsize", StringUtils::SizeToString(sizeInBytes)); + + if (item.GetProperty("unwatchedepisodes").asInteger() > 0) + item.SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED); + else + item.SetOverlayImage(CGUIListItem::ICON_OVERLAY_WATCHED); + + return true; + } + return false; +} + bool CPVRGUIDirectory::GetRecordingsDirectory(CFileItemList& results) const { results.SetContent("recordings"); diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.h b/xbmc/pvr/filesystem/PVRGUIDirectory.h index 132b0a227debf..925d97d0b3a34 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.h +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.h @@ -12,6 +12,7 @@ #include <string> +class CFileItem; class CFileItemList; namespace PVR @@ -103,6 +104,13 @@ class CPVRGUIDirectory */ bool GetProvidersDirectory(CFileItemList& results) const; + /*! + * @brief Get info for a recording folder. + * @param item The folder. + * @return True on success, false otherwise.. + */ + static bool GetRecordingsDirectoryInfo(CFileItem& item); + private: bool GetTimersDirectory(CFileItemList& results) const; bool GetRecordingsDirectory(CFileItemList& results) const; diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index eb6ab9674d3f3..16f6c0cd38454 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -15,6 +15,7 @@ #include "filesystem/Directory.h" #include "filesystem/VideoDatabaseDirectory/QueryParams.h" #include "playlists/PlayListFileItemClassify.h" +#include "pvr/filesystem/PVRGUIDirectory.h" #include "settings/SettingUtils.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" @@ -41,15 +42,17 @@ KODI::VIDEO::UTILS::ResumeInformation GetFolderItemResumeInformation(const CFile return {}; CFileItem folderItem(item); - if ((!folderItem.HasProperty("inprogressepisodes") || // season/show - (folderItem.GetProperty("inprogressepisodes").asInteger() == 0)) && - (!folderItem.HasProperty("inprogress") || // movie set - (folderItem.GetProperty("inprogress").asInteger() == 0))) + if (!folderItem.HasProperty("inprogressepisodes") && // season/show/recordings + !folderItem.HasProperty("inprogress")) // movie set { - CVideoDatabase db; - if (db.Open()) + if (URIUtils::IsPVRRecordingFileOrFolder(folderItem.GetPath())) { - if (!folderItem.HasProperty("inprogressepisodes") && !folderItem.HasProperty("inprogress")) + PVR::CPVRGUIDirectory::GetRecordingsDirectoryInfo(folderItem); + } + else + { + CVideoDatabase db; + if (db.Open()) { XFILE::VIDEODATABASEDIRECTORY::CQueryParams params; XFILE::VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo(item.GetPath(), params); @@ -79,7 +82,6 @@ KODI::VIDEO::UTILS::ResumeInformation GetFolderItemResumeInformation(const CFile db.GetSetInfo(static_cast<int>(params.GetSetId()), details, &folderItem); } } - db.Close(); } } From 54b9ccdb4fd8c6397846cd35887292dbb31cf3b1 Mon Sep 17 00:00:00 2001 From: boogie <boogiepop@gmx.com> Date: Thu, 8 Aug 2024 00:26:21 +0200 Subject: [PATCH 512/651] CEGLFence: Ignore improper atomic drmrequest when async rendering and dont go into infinite loop --- xbmc/utils/EGLFence.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/xbmc/utils/EGLFence.cpp b/xbmc/utils/EGLFence.cpp index 9d0065bdaf07a..58a9ec1683be0 100644 --- a/xbmc/utils/EGLFence.cpp +++ b/xbmc/utils/EGLFence.cpp @@ -133,11 +133,7 @@ void CEGLFence::WaitSyncCPU() if (!m_kmsFence) return; - EGLint status{EGL_FALSE}; - - while (status != EGL_CONDITION_SATISFIED_KHR) - status = m_eglClientWaitSyncKHR(m_display, m_kmsFence, 0, EGL_FOREVER_KHR); - - m_eglDestroySyncKHR(m_display, m_kmsFence); + if (m_eglClientWaitSyncKHR(m_display, m_kmsFence, 0, EGL_FOREVER_KHR) != EGL_FALSE) + m_eglDestroySyncKHR(m_display, m_kmsFence); } #endif From 7d3c7b6c6ef1936a78bab8ef5dbb06926e32dbf6 Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Fri, 27 Sep 2024 09:23:12 +0200 Subject: [PATCH 513/651] [GUI] Add videostreamcount infolabel --- xbmc/GUIInfoManager.cpp | 9 +++++++++ xbmc/guilib/guiinfo/GUIInfoLabels.h | 1 + xbmc/guilib/guiinfo/VideoGUIInfo.cpp | 3 +++ 3 files changed, 13 insertions(+) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 4bfc07d3d138c..ee12786f60a66 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -3976,6 +3976,14 @@ const infomap musicplayer[] = {{ "title", MUSICPLAYER_TITLE }, /// @skinning_v20 **[New Infolabel]** \link VideoPlayer_AudioStreamCount `VideoPlayer.AudioStreamCount`\endlink /// <p> /// } +/// \table_row3{ <b>`VideoPlayer.VideoStreamCount`</b>, +/// \anchor VideoPlayer_VideoStreamCount +/// _integer_, +/// @return The number of video streams of the currently playing video. +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link VideoPlayer_VideoStreamCount `VideoPlayer.VideoStreamCount`\endlink +/// <p> +/// } /// \table_row3{ <b>`VideoPlayer.HdrType`</b>, /// \anchor VideoPlayer_HdrType /// _string_, @@ -4086,6 +4094,7 @@ const infomap videoplayer[] = {{ "title", VIDEOPLAYER_TITLE }, { "uniqueid", VIDEOPLAYER_UNIQUEID }, { "tvshowdbid", VIDEOPLAYER_TVSHOWDBID }, { "audiostreamcount", VIDEOPLAYER_AUDIOSTREAMCOUNT }, + { "videostreamcount", VIDEOPLAYER_VIDEOSTREAMCOUNT }, { "hdrtype", VIDEOPLAYER_HDR_TYPE }, { "art", VIDEOPLAYER_ART}, { "videoversionname", VIDEOPLAYER_VIDEOVERSION_NAME}, diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index 613aff347f7a4..3138987c6a4b3 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -281,6 +281,7 @@ #define VIDEOPLAYER_UNIQUEID 294 #define VIDEOPLAYER_AUDIOSTREAMCOUNT 295 #define VIDEOPLAYER_VIDEOVERSION_NAME 296 +#define VIDEOPLAYER_VIDEOSTREAMCOUNT 297 // Videoplayer infobools #define VIDEOPLAYER_HASSUBTITLES 300 diff --git a/xbmc/guilib/guiinfo/VideoGUIInfo.cpp b/xbmc/guilib/guiinfo/VideoGUIInfo.cpp index 0a112ea3ef57f..406cc06793adf 100644 --- a/xbmc/guilib/guiinfo/VideoGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/VideoGUIInfo.cpp @@ -759,6 +759,9 @@ bool CVideoGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWin /////////////////////////////////////////////////////////////////////////////////////////////// // VIDEOPLAYER_* /////////////////////////////////////////////////////////////////////////////////////////////// + case VIDEOPLAYER_VIDEOSTREAMCOUNT: + value = m_appPlayer->GetVideoStreamCount(); + return true; case VIDEOPLAYER_AUDIOSTREAMCOUNT: value = m_appPlayer->GetAudioStreamCount(); return true; From a810cb175af585e000436c8821d9c227cfe16c14 Mon Sep 17 00:00:00 2001 From: jjd-uk <jjd-uk@kodi.tv> Date: Sat, 28 Sep 2024 15:18:16 +0100 Subject: [PATCH 514/651] Add poster fallback --- addons/skin.estuary/xml/Variables.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 48d31556dfff6..fb320882e8861 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -703,8 +703,10 @@ </variable> <variable name="VideoListThumbVar"> <value condition="!String.IsEmpty(Container(6).ListItem.Art(landscape))">$INFO[Container(6).ListItem.Art(landscape)]</value> + <value condition="!String.IsEmpty(Container(6).ListItem.Art(poster))">$INFO[Container(6).ListItem.Art(poster)]</value> <value condition="!String.IsEmpty(Container(6).ListItem.Art(thumb))">$INFO[Container(6).ListItem.Art(thumb)]</value> <value condition="!String.IsEmpty(Container(50).ListItem.Art(landscape))">$INFO[Container(50).ListItem.Art(landscape)]</value> + <value condition="!String.IsEmpty(Container(50).ListItem.Art(poster))">$INFO[Container(50).ListItem.Art(poster)]</value> <value condition="!String.IsEmpty(Container(50).ListItem.Art(thumb))">$INFO[Container(50).ListItem.Art(thumb)]</value> <value>$INFO[ListItem.Art(thumb)]</value> </variable> From 7965060b2a1c6de4a9504e7aa14c1bead7283891 Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Sun, 29 Sep 2024 09:51:27 +0200 Subject: [PATCH 515/651] [VideoPlayer][StreamInfo] Add fps info to VideoStreamInfo --- xbmc/cores/VideoPlayer/Interface/StreamInfo.h | 2 ++ xbmc/cores/VideoPlayer/VideoPlayer.cpp | 4 ++++ xbmc/cores/VideoPlayer/VideoPlayer.h | 2 ++ 3 files changed, 8 insertions(+) diff --git a/xbmc/cores/VideoPlayer/Interface/StreamInfo.h b/xbmc/cores/VideoPlayer/Interface/StreamInfo.h index df67662e9bb3c..4fc610a613359 100644 --- a/xbmc/cores/VideoPlayer/Interface/StreamInfo.h +++ b/xbmc/cores/VideoPlayer/Interface/StreamInfo.h @@ -73,6 +73,8 @@ struct VideoStreamInfo : StreamInfo std::string stereoMode; int angles = 0; StreamHdrType hdrType = StreamHdrType::HDR_TYPE_NONE; + uint32_t fpsRate{0}; + uint32_t fpsScale{0}; }; struct ProgramInfo diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index 1deb7d2ba7dd5..78623671b90db 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -535,6 +535,8 @@ void CSelectionStreams::Update(const std::shared_ptr<CDVDInputStream>& input, s.stereo_mode = vstream->stereo_mode; s.bitrate = vstream->iBitRate; s.hdrType = vstream->hdr_type; + s.fpsRate = static_cast<uint32_t>(vstream->iFpsRate); + s.fpsScale = static_cast<uint32_t>(vstream->iFpsScale); } if(stream->type == STREAM_AUDIO) { @@ -5297,6 +5299,8 @@ void CVideoPlayer::GetVideoStreamInfo(int streamId, VideoStreamInfo& info) const info.stereoMode = s.stereo_mode; info.flags = s.flags; info.hdrType = s.hdrType; + info.fpsRate = s.fpsRate; + info.fpsScale = s.fpsScale; } int CVideoPlayer::GetVideoStreamCount() const diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.h b/xbmc/cores/VideoPlayer/VideoPlayer.h index f340b85e831e4..d67f08d6b225f 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.h +++ b/xbmc/cores/VideoPlayer/VideoPlayer.h @@ -197,6 +197,8 @@ struct SelectionStream std::string stereo_mode; float aspect_ratio = 0.0f; StreamHdrType hdrType = StreamHdrType::HDR_TYPE_NONE; + uint32_t fpsScale{0}; + uint32_t fpsRate{0}; }; class CSelectionStreams From a859f59bc99a851096e50c89f66aea1f3055078e Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Sat, 28 Sep 2024 10:51:02 +0200 Subject: [PATCH 516/651] [VideoPlayer] Copy codecName to SubtitleStreamInfo --- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index 78623671b90db..12117a1d5f2a9 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -472,6 +472,7 @@ void CSelectionStreams::Update(const std::shared_ptr<CDVDInputStream>& input, SubtitleStreamInfo info = nav->GetSubtitleStreamInfo(i); s.name = info.name; + s.codec = info.codecName; s.flags = info.flags; s.language = g_LangCodeExpander.ConvertToISO6392B(info.language); Update(s); @@ -5390,6 +5391,7 @@ void CVideoPlayer::GetSubtitleStreamInfo(int index, SubtitleStreamInfo& info) co info.name += "(Invalid)"; info.language = s.language; + info.codecName = s.codec; info.flags = s.flags; } From a840b9ccb3cfc158ddc8e366268d8c3c4f169139 Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Sat, 28 Sep 2024 10:46:44 +0200 Subject: [PATCH 517/651] [VideoPlayer] Decouple codec description from codec name Our custom codec descriptions should not be mixed with codec name defined by ffmpeg this will allow also skins to get the real codec name --- xbmc/cores/VideoPlayer/Interface/StreamInfo.h | 3 ++- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 11 +++----- xbmc/cores/VideoPlayer/VideoPlayer.h | 3 ++- xbmc/video/PlayerController.cpp | 16 +++++++----- xbmc/video/dialogs/GUIDialogAudioSettings.cpp | 25 +++++++++++-------- 5 files changed, 31 insertions(+), 27 deletions(-) diff --git a/xbmc/cores/VideoPlayer/Interface/StreamInfo.h b/xbmc/cores/VideoPlayer/Interface/StreamInfo.h index 4fc610a613359..c3791862e4601 100644 --- a/xbmc/cores/VideoPlayer/Interface/StreamInfo.h +++ b/xbmc/cores/VideoPlayer/Interface/StreamInfo.h @@ -44,7 +44,8 @@ struct StreamInfo int bitrate = 0; std::string language; std::string name; - std::string codecName; + std::string codecName; // Codec name (name definition from ffmpeg) + std::string codecDesc; // Codec description StreamFlags flags = StreamFlags::FLAG_NONE; protected: diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index 12117a1d5f2a9..f8e3de0287759 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -454,6 +454,7 @@ void CSelectionStreams::Update(const std::shared_ptr<CDVDInputStream>& input, AudioStreamInfo info = nav->GetAudioStreamInfo(i); s.name = info.name; s.codec = info.codecName; + s.codecDesc = info.codecDesc; s.language = g_LangCodeExpander.ConvertToISO6392B(info.language); s.channels = info.channels; s.flags = info.flags; @@ -541,14 +542,7 @@ void CSelectionStreams::Update(const std::shared_ptr<CDVDInputStream>& input, } if(stream->type == STREAM_AUDIO) { - std::string type; - type = static_cast<CDemuxStreamAudio*>(stream)->GetStreamType(); - if(type.length() > 0) - { - if(s.name.length() > 0) - s.name += " - "; - s.name += type; - } + s.codecDesc = static_cast<CDemuxStreamAudio*>(stream)->GetStreamType(); s.channels = static_cast<CDemuxStreamAudio*>(stream)->iChannels; s.bitrate = static_cast<CDemuxStreamAudio*>(stream)->iBitRate; } @@ -5347,6 +5341,7 @@ void CVideoPlayer::GetAudioStreamInfo(int index, AudioStreamInfo& info) const info.bitrate = s.bitrate; info.channels = s.channels; info.codecName = s.codec; + info.codecDesc = s.codecDesc; info.flags = s.flags; } diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.h b/xbmc/cores/VideoPlayer/VideoPlayer.h index d67f08d6b225f..86c783015f096 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.h +++ b/xbmc/cores/VideoPlayer/VideoPlayer.h @@ -186,7 +186,8 @@ struct SelectionStream int source = 0; int id = 0; int64_t demuxerId = -1; - std::string codec; + std::string codec; // Codec name (name definition from ffmpeg) + std::string codecDesc; // Codec description int channels = 0; int bitrate = 0; int width = 0; diff --git a/xbmc/video/PlayerController.cpp b/xbmc/video/PlayerController.cpp index 35025c517bc37..44a79b44204be 100644 --- a/xbmc/video/PlayerController.cpp +++ b/xbmc/video/PlayerController.cpp @@ -224,19 +224,23 @@ bool CPlayerController::OnAction(const CAction &action) if (++currentAudio >= audioStreamCount) currentAudio = 0; appPlayer->SetAudioStream(currentAudio); // Set the audio stream to the one selected - std::string aud; + std::string lan; AudioStreamInfo info; appPlayer->GetAudioStreamInfo(currentAudio, info); if (!g_LangCodeExpander.Lookup(info.language, lan)) lan = g_localizeStrings.Get(13205); // Unknown - if (info.name.empty()) - aud = lan; - else - aud = StringUtils::Format("{} - {}", lan, info.name); + + std::string textInfo = lan; + if (!info.name.empty()) + textInfo += " - " + info.name; + if (!info.codecDesc.empty()) + textInfo += " (" + info.codecDesc + ")"; + std::string caption = g_localizeStrings.Get(460); caption += StringUtils::Format(" ({}/{})", currentAudio + 1, audioStreamCount); - CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, caption, aud, DisplTime, false, MsgTime); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, caption, textInfo, + DisplTime, false, MsgTime); return true; } diff --git a/xbmc/video/dialogs/GUIDialogAudioSettings.cpp b/xbmc/video/dialogs/GUIDialogAudioSettings.cpp index 9c8b1b10ffb0e..5fccdb7807838 100644 --- a/xbmc/video/dialogs/GUIDialogAudioSettings.cpp +++ b/xbmc/video/dialogs/GUIDialogAudioSettings.cpp @@ -346,31 +346,34 @@ void CGUIDialogAudioSettings::AudioStreamsOptionFiller(const SettingConstPtr& se { const auto& components = CServiceBroker::GetAppComponents(); const auto appPlayer = components.GetComponent<CApplicationPlayer>(); - int audioStreamCount = appPlayer->GetAudioStreamCount(); + const int audioStreamCount = appPlayer->GetAudioStreamCount(); - std::string strFormat = "{:s} - {:s} - {:d} " + g_localizeStrings.Get(10127); + std::string strChannels = 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); + std::string strLanguage; if (!g_LangCodeExpander.Lookup(info.language, strLanguage)) strLanguage = strUnknown; - if (info.name.length() == 0) - info.name = strUnknown; + std::string textInfo = strLanguage; + if (!info.name.empty()) + textInfo += " - " + info.name; + + textInfo += " ("; + if (!info.codecDesc.empty()) + textInfo += info.codecDesc + ", "; - strItem = StringUtils::Format(strFormat, strLanguage, info.name, info.channels); + textInfo += std::to_string(info.channels) + " " + strChannels + ")"; - strItem += FormatFlags(info.flags); - strItem += StringUtils::Format(" ({}/{})", i + 1, audioStreamCount); - list.emplace_back(strItem, i); + textInfo += FormatFlags(info.flags); + textInfo += StringUtils::Format(" ({}/{})", i + 1, audioStreamCount); + list.emplace_back(textInfo, i); } if (list.empty()) From 061c62fc766050f8a25a743dcf4aa1ee605f748a Mon Sep 17 00:00:00 2001 From: Tobias Markus <tobbi.bugs@googlemail.com> Date: Mon, 22 Jul 2024 21:49:54 +0200 Subject: [PATCH 518/651] xbmc/addons: cppcheck performance fixes ``` xbmc/addons/AddonInstaller.cpp:629:53: performance: Searching before insertion is not necessary. Instead of 'result[pack]=std::make_unique<CFileItemList>()' consider using 'result.try_emplace(pack, std::make_unique<CFileItemList>());'. [stlFindInsert] result[pack] = std::make_unique<CFileItemList>(); ^ xbmc/addons/AddonVersion.cpp:178:13: performance: Ineffective call of function 'substr' because a prefix of the string is assigned to itself. Use resize() or pop_back() instead. [uselessCallsSubstr] version = version.substr(0, version.size() - 4); ^ xbmc/addons/ImageResource.cpp:23:3: performance: Variable 'm_type' is assigned in constructor body. Consider performing initialization in initialization list. [useInitializationList] m_type = Type(AddonType::RESOURCE_IMAGES)->GetValue("@type").asString(); ^ xbmc/addons/LanguageResource.cpp:35:3: performance: Variable 'm_locale' is assigned in constructor body. Consider performing initialization in initialization list. [useInitializationList] m_locale = ^ xbmc/addons/addoninfo/AddonInfo.cpp:303:32: performance: Constructing a std::string_view from the result of c_str() is slow and redundant. [stlcstrConstructor] const std::string_view uid(filename.data() + startName.length()); xbmc/addons/addoninfo/AddonInfoBuilder.cpp:798:10: error: Uninitialized variable: libraryName [uninitvar] return libraryName; ^ ``` --- xbmc/addons/AddonInstaller.cpp | 3 +-- xbmc/addons/AddonVersion.cpp | 2 +- xbmc/addons/ImageResource.cpp | 4 ++-- xbmc/addons/LanguageResource.cpp | 9 ++++----- xbmc/addons/addoninfo/AddonInfoBuilder.cpp | 2 +- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/xbmc/addons/AddonInstaller.cpp b/xbmc/addons/AddonInstaller.cpp index 3343bf1170e85..25ff2991645d2 100644 --- a/xbmc/addons/AddonInstaller.cpp +++ b/xbmc/addons/AddonInstaller.cpp @@ -625,8 +625,7 @@ int64_t CAddonInstaller::EnumeratePackageFolder( size += items[i]->m_dwSize; std::string pack,dummy; CAddonVersion::SplitFileName(pack, dummy, items[i]->GetLabel()); - if (result.find(pack) == result.end()) - result[pack] = std::make_unique<CFileItemList>(); + result.try_emplace(pack, std::make_unique<CFileItemList>()); result[pack]->Add(std::make_shared<CFileItem>(*items[i])); } diff --git a/xbmc/addons/AddonVersion.cpp b/xbmc/addons/AddonVersion.cpp index 10b314e51ce8b..678fca5ba3161 100644 --- a/xbmc/addons/AddonVersion.cpp +++ b/xbmc/addons/AddonVersion.cpp @@ -175,7 +175,7 @@ bool CAddonVersion::SplitFileName(std::string& ID, return false; ID = filename.substr(0, dpos); version = filename.substr(dpos + 1); - version = version.substr(0, version.size() - 4); + version.resize(version.size() - 4); return true; } diff --git a/xbmc/addons/ImageResource.cpp b/xbmc/addons/ImageResource.cpp index 06eada3b1aabb..609002ea73fc8 100644 --- a/xbmc/addons/ImageResource.cpp +++ b/xbmc/addons/ImageResource.cpp @@ -18,9 +18,9 @@ namespace ADDON { CImageResource::CImageResource(const AddonInfoPtr& addonInfo) - : CResource(addonInfo, AddonType::RESOURCE_IMAGES) + : CResource(addonInfo, AddonType::RESOURCE_IMAGES), + m_type(Type(AddonType::RESOURCE_IMAGES)->GetValue("@type").asString()) { - m_type = Type(AddonType::RESOURCE_IMAGES)->GetValue("@type").asString(); } void CImageResource::OnPreUnInstall() diff --git a/xbmc/addons/LanguageResource.cpp b/xbmc/addons/LanguageResource.cpp index c9ec0c8afc558..e8c94c7cd600b 100644 --- a/xbmc/addons/LanguageResource.cpp +++ b/xbmc/addons/LanguageResource.cpp @@ -29,12 +29,11 @@ namespace ADDON { CLanguageResource::CLanguageResource(const AddonInfoPtr& addonInfo) - : CResource(addonInfo, AddonType::RESOURCE_LANGUAGE) + : CResource(addonInfo, AddonType::RESOURCE_LANGUAGE), + // parse <extension> attributes + m_locale( + CLocale::FromString(Type(AddonType::RESOURCE_LANGUAGE)->GetValue("@locale").asString())) { - // parse <extension> attributes - m_locale = - CLocale::FromString(Type(AddonType::RESOURCE_LANGUAGE)->GetValue("@locale").asString()); - // parse <charsets> const CAddonExtensions* charsetsElement = Type(AddonType::RESOURCE_LANGUAGE)->GetElement("charsets"); diff --git a/xbmc/addons/addoninfo/AddonInfoBuilder.cpp b/xbmc/addons/addoninfo/AddonInfoBuilder.cpp index 1a23a9e368c52..14301e43e857e 100644 --- a/xbmc/addons/addoninfo/AddonInfoBuilder.cpp +++ b/xbmc/addons/addoninfo/AddonInfoBuilder.cpp @@ -772,7 +772,7 @@ bool CAddonInfoBuilder::GetTextList(const tinyxml2::XMLElement* element, const char* CAddonInfoBuilder::GetPlatformLibraryName(const tinyxml2::XMLElement* element) { - const char* libraryName; + const char* libraryName = nullptr; #if defined(TARGET_ANDROID) libraryName = element->Attribute("library_android"); #elif defined(TARGET_LINUX) || defined(TARGET_FREEBSD) From 5f1b5ba77026d167ea86ec368a2c9302a630ad87 Mon Sep 17 00:00:00 2001 From: Tobias Markus <tobbi.bugs@googlemail.com> Date: Mon, 22 Jul 2024 22:16:46 +0200 Subject: [PATCH 519/651] xbmc/application: cppcheck performance fixes --- xbmc/application/AppParams.h | 4 ++-- xbmc/application/Application.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/xbmc/application/AppParams.h b/xbmc/application/AppParams.h index 12d7aba2b9f6f..9fee48b887dbf 100644 --- a/xbmc/application/AppParams.h +++ b/xbmc/application/AppParams.h @@ -50,10 +50,10 @@ class CAppParams const std::string& GetLogTarget() const { return m_logTarget; } void SetLogTarget(const std::string& logTarget) { m_logTarget = logTarget; } - std::string_view GetAudioBackend() const { return m_audioBackend; } + const std::string& GetAudioBackend() const { return m_audioBackend; } void SetAudioBackend(std::string_view audioBackend) { m_audioBackend = audioBackend; } - std::string_view GetGlInterface() const { return m_glInterface; } + const std::string& GetGlInterface() const { return m_glInterface; } void SetGlInterface(const std::string& glInterface) { m_glInterface = glInterface; } CFileItemList& GetPlaylist() const { return *m_playlist; } diff --git a/xbmc/application/Application.cpp b/xbmc/application/Application.cpp index fe3967edcfbbb..01f1c39ad0feb 100644 --- a/xbmc/application/Application.cpp +++ b/xbmc/application/Application.cpp @@ -2362,7 +2362,7 @@ bool CApplication::PlayFile(CFileItem item, std::string path = item.GetPath(); std::string videoInfoTagPath(item.GetVideoInfoTag()->m_strFileNameAndPath); - if (videoInfoTagPath.find("removable://") == 0 || VIDEO::IsVideoDb(item)) + if (videoInfoTagPath.starts_with("removable://") || VIDEO::IsVideoDb(item)) path = videoInfoTagPath; // Note that we need to load the tag from database also if the item already has a tag, From bb1a955b7b057e50b7512904631d6bfb69350b40 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:01:37 +0200 Subject: [PATCH 520/651] [PVR] Async EPG update: Fix removal of EPG events notified as 'deleted'. --- xbmc/pvr/epg/Epg.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xbmc/pvr/epg/Epg.cpp b/xbmc/pvr/epg/Epg.cpp index d1c39edcd3b24..bf1fd4a2ada7d 100644 --- a/xbmc/pvr/epg/Epg.cpp +++ b/xbmc/pvr/epg/Epg.cpp @@ -223,7 +223,8 @@ bool CPVREpg::UpdateEntry(const std::shared_ptr<CPVREpgInfoTag>& tag, EPG_EVENT_ } else { - if (IsTagExpired(existingTag)) + // Delete running/future events and past events if they are older than epg linger time setting + if ((existingTag->EndAsUTC() >= CDateTime::GetUTCDateTime()) || IsTagExpired(existingTag)) { m_tags.DeleteEntry(existingTag); } From b57c953bdb192f3a41fc642c0379983e69f64952 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:27:11 +0200 Subject: [PATCH 521/651] [PVR] Context menu item 'Stop recording': Fix visibility condition for EPG gap tags. --- xbmc/pvr/PVRContextMenus.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/xbmc/pvr/PVRContextMenus.cpp b/xbmc/pvr/PVRContextMenus.cpp index f882c9e624094..f8a64bcb81d55 100644 --- a/xbmc/pvr/PVRContextMenus.cpp +++ b/xbmc/pvr/PVRContextMenus.cpp @@ -337,9 +337,13 @@ bool StopRecording::IsVisible(const CFileItem& item) const const std::shared_ptr<const CPVREpgInfoTag> epg = item.GetEPGInfoTag(); if (epg && epg->IsGapTag()) { - channel = CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(epg); - if (channel) - return CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel); + const CDateTime now{CDateTime::GetUTCDateTime()}; + if (epg->StartAsUTC() <= now && epg->EndAsUTC() >= now) + { + channel = CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(epg); + if (channel) + return CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel); + } } return false; @@ -350,12 +354,16 @@ bool StopRecording::Execute(const CFileItemPtr& item) const const std::shared_ptr<const CPVREpgInfoTag> epgTag = item->GetEPGInfoTag(); if (epgTag && epgTag->IsGapTag()) { - // instance recording - const std::shared_ptr<CPVRChannel> channel = - CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(epgTag); - if (channel) - return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().SetRecordingOnChannel(channel, - false); + const CDateTime now{CDateTime::GetUTCDateTime()}; + if (epgTag->StartAsUTC() <= now && epgTag->EndAsUTC() >= now) + { + const std::shared_ptr<CPVRChannel> channel{ + CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(epgTag)}; + if (channel) + return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().SetRecordingOnChannel( + channel, false); + } + return false; } return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().StopRecording(*item); From 14d2545bac5aff765e322065dd105474b3c12997 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving <spiff@kodi.tv> Date: Thu, 26 Sep 2024 21:20:11 +0200 Subject: [PATCH 522/651] move CFileItem::FindTrailer to VideoUtils --- xbmc/FileItem.cpp | 83 ---------------------- xbmc/FileItem.h | 3 - xbmc/video/VideoInfoScanner.cpp | 5 +- xbmc/video/VideoUtils.cpp | 85 +++++++++++++++++++++++ xbmc/video/VideoUtils.h | 7 ++ xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 3 +- xbmc/video/test/TestVideoUtils.cpp | 62 +++++++++++++++++ 7 files changed, 159 insertions(+), 89 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index dcc28288e4191..a3fa357232aac 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -2354,89 +2354,6 @@ const std::shared_ptr<PVR::CPVRChannel> CFileItem::GetPVRChannelInfoTag() const : std::shared_ptr<CPVRChannel>(); } -std::string CFileItem::FindTrailer() const -{ - std::string strFile2; - std::string strFile = m_strPath; - if (IsStack()) - { - std::string strPath; - URIUtils::GetParentPath(m_strPath,strPath); - CStackDirectory dir; - std::string strPath2; - strPath2 = dir.GetStackedTitlePath(strFile); - strFile = URIUtils::AddFileToFolder(strPath,URIUtils::GetFileName(strPath2)); - CFileItem item(dir.GetFirstStackedFile(m_strPath),false); - std::string strTBNFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), "-trailer")); - strFile2 = URIUtils::AddFileToFolder(strPath,URIUtils::GetFileName(strTBNFile)); - } - if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) - { - std::string strPath = URIUtils::GetDirectory(strFile); - std::string strParent; - URIUtils::GetParentPath(strPath,strParent); - strFile = URIUtils::AddFileToFolder(strParent,URIUtils::GetFileName(m_strPath)); - } - - // no local trailer available for these - if (NETWORK::IsInternetStream(*this) || URIUtils::IsUPnP(strFile) || - URIUtils::IsBluray(strFile) || IsLiveTV() || IsPlugin() || IsDVD()) - return ""; - - std::string strDir = URIUtils::GetDirectory(strFile); - CFileItemList items; - CDirectory::GetDirectory(strDir, items, CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(), DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO | DIR_FLAG_NO_FILE_DIRS); - URIUtils::RemoveExtension(strFile); - strFile += "-trailer"; - std::string strFile3 = URIUtils::AddFileToFolder(strDir, "movie-trailer"); - - // Precompile our REs - VECCREGEXP matchRegExps; - CRegExp tmpRegExp(true, CRegExp::autoUtf8); - const std::vector<std::string>& strMatchRegExps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_trailerMatchRegExps; - - std::vector<std::string>::const_iterator strRegExp = strMatchRegExps.begin(); - while (strRegExp != strMatchRegExps.end()) - { - if (tmpRegExp.RegComp(*strRegExp)) - { - matchRegExps.push_back(tmpRegExp); - } - ++strRegExp; - } - - std::string strTrailer; - for (int i = 0; i < items.Size(); i++) - { - std::string strCandidate = items[i]->m_strPath; - URIUtils::RemoveExtension(strCandidate); - if (StringUtils::EqualsNoCase(strCandidate, strFile) || - StringUtils::EqualsNoCase(strCandidate, strFile2) || - StringUtils::EqualsNoCase(strCandidate, strFile3)) - { - strTrailer = items[i]->m_strPath; - break; - } - else - { - VECCREGEXP::iterator expr = matchRegExps.begin(); - - while (expr != matchRegExps.end()) - { - if (expr->RegFind(strCandidate) != -1) - { - strTrailer = items[i]->m_strPath; - i = items.Size(); - break; - } - ++expr; - } - } - } - - return strTrailer; -} - VideoDbContentType CFileItem::GetVideoContentType() const { VideoDbContentType type = VideoDbContentType::MOVIES; diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index 635c964d8d6ab..ffdac95d495ec 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -448,9 +448,6 @@ class CFileItem : */ std::string GetLocalMetadataPath() const; - // finds a matching local trailer file - std::string FindTrailer() const; - bool LoadMusicTag(); bool LoadGameTag(); diff --git a/xbmc/video/VideoInfoScanner.cpp b/xbmc/video/VideoInfoScanner.cpp index c02ee60282917..10cc30a6c901b 100644 --- a/xbmc/video/VideoInfoScanner.cpp +++ b/xbmc/video/VideoInfoScanner.cpp @@ -49,6 +49,7 @@ #include "video/VideoFileItemClassify.h" #include "video/VideoManagerTypes.h" #include "video/VideoThumbLoader.h" +#include "video/VideoUtils.h" #include "video/dialogs/GUIDialogVideoManagerExtras.h" #include "video/dialogs/GUIDialogVideoManagerVersions.h" @@ -59,7 +60,7 @@ using namespace XFILE; using namespace ADDON; using namespace KODI::MESSAGING; -using namespace KODI::VIDEO; +using namespace KODI; using KODI::MESSAGING::HELPERS::DialogResponse; using KODI::UTILITY::CDigest; @@ -1527,7 +1528,7 @@ namespace KODI::VIDEO if (content == CONTENT_MOVIES) { // find local trailer first - std::string strTrailer = pItem->FindTrailer(); + std::string strTrailer = UTILS::FindTrailer(*pItem); if (!strTrailer.empty()) movieDetails.m_strTrailer = strTrailer; diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index 16f6c0cd38454..73f38e4eadbf6 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -13,13 +13,18 @@ #include "ServiceBroker.h" #include "Util.h" #include "filesystem/Directory.h" +#include "filesystem/StackDirectory.h" #include "filesystem/VideoDatabaseDirectory/QueryParams.h" +#include "network/NetworkFileItemClassify.h" #include "playlists/PlayListFileItemClassify.h" #include "pvr/filesystem/PVRGUIDirectory.h" +#include "settings/AdvancedSettings.h" #include "settings/SettingUtils.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "settings/lib/Setting.h" +#include "utils/ArtUtils.h" +#include "utils/FileExtensionProvider.h" #include "utils/FileUtils.h" #include "utils/URIUtils.h" #include "utils/log.h" @@ -187,6 +192,86 @@ KODI::VIDEO::UTILS::ResumeInformation GetNonFolderItemResumeInformation(const CF namespace KODI::VIDEO::UTILS { + +std::string FindTrailer(const CFileItem& item) +{ + std::string strFile2; + std::string strFile = item.GetPath(); + if (item.IsStack()) + { + std::string strPath; + URIUtils::GetParentPath(item.GetPath(), strPath); + XFILE::CStackDirectory dir; + std::string strPath2; + strPath2 = dir.GetStackedTitlePath(strFile); + strFile = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strPath2)); + CFileItem sitem(dir.GetFirstStackedFile(item.GetPath()), false); + std::string strTBNFile(URIUtils::ReplaceExtension(ART::GetTBNFile(sitem), "-trailer")); + strFile2 = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strTBNFile)); + } + if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) + { + std::string strPath = URIUtils::GetDirectory(strFile); + std::string strParent; + URIUtils::GetParentPath(strPath, strParent); + strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(item.GetPath())); + } + + // no local trailer available for these + if (NETWORK::IsInternetStream(item) || URIUtils::IsUPnP(strFile) || URIUtils::IsBluray(strFile) || + item.IsLiveTV() || item.IsPlugin() || item.IsDVD()) + return ""; + + std::string strDir = URIUtils::GetDirectory(strFile); + CFileItemList items; + XFILE::CDirectory::GetDirectory( + strDir, items, CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(), + XFILE::DIR_FLAG_READ_CACHE | XFILE::DIR_FLAG_NO_FILE_INFO | XFILE::DIR_FLAG_NO_FILE_DIRS); + URIUtils::RemoveExtension(strFile); + strFile += "-trailer"; + std::string strFile3 = URIUtils::AddFileToFolder(strDir, "movie-trailer"); + + // Precompile our REs + VECCREGEXP matchRegExps; + CRegExp tmpRegExp(true, CRegExp::autoUtf8); + const std::vector<std::string>& strMatchRegExps = + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_trailerMatchRegExps; + + for (const auto& strRegExp : strMatchRegExps) + { + if (tmpRegExp.RegComp(strRegExp)) + matchRegExps.push_back(tmpRegExp); + } + + std::string strTrailer; + for (int i = 0; i < items.Size(); i++) + { + std::string strCandidate = items[i]->GetPath(); + URIUtils::RemoveExtension(strCandidate); + if (StringUtils::EqualsNoCase(strCandidate, strFile) || + StringUtils::EqualsNoCase(strCandidate, strFile2) || + StringUtils::EqualsNoCase(strCandidate, strFile3)) + { + strTrailer = items[i]->GetPath(); + break; + } + else + { + for (auto& expr : matchRegExps) + { + if (expr.RegFind(strCandidate) != -1) + { + strTrailer = items[i]->GetPath(); + i = items.Size(); + break; + } + } + } + } + + return strTrailer; +} + std::string GetOpticalMediaPath(const CFileItem& item) { auto exists = [&item](const std::string& file) diff --git a/xbmc/video/VideoUtils.h b/xbmc/video/VideoUtils.h index c344072ffe4f9..2ee611c4e81e3 100644 --- a/xbmc/video/VideoUtils.h +++ b/xbmc/video/VideoUtils.h @@ -14,6 +14,13 @@ class CFileItem; namespace KODI::VIDEO::UTILS { + +/*! \brief + * Find a local trailer file for a given file item + * \return non-empty string with path of trailer if found + */ +std::string FindTrailer(const CFileItem& item); + /*! \brief Check whether an item is an optical media folder or its parent. This will return the non-empty path to the playable entry point of the media diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 7d93996ba5bf4..f7c931e4b051e 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -59,6 +59,7 @@ #include "video/VideoItemArtworkHandler.h" #include "video/VideoLibraryQueue.h" #include "video/VideoThumbLoader.h" +#include "video/VideoUtils.h" #include "video/dialogs/GUIDialogVideoManagerExtras.h" #include "video/dialogs/GUIDialogVideoManagerVersions.h" #include "video/guilib/VideoGUIUtils.h" @@ -453,7 +454,7 @@ void CGUIDialogVideoInfo::SetMovie(const CFileItem *item) if (m_movieItem->GetVideoInfoTag()->m_strTrailer.empty() || URIUtils::IsInternetStream(m_movieItem->GetVideoInfoTag()->m_strTrailer)) { - std::string localTrailer = m_movieItem->FindTrailer(); + std::string localTrailer = VIDEO::UTILS::FindTrailer(*m_movieItem); if (!localTrailer.empty()) { m_movieItem->GetVideoInfoTag()->m_strTrailer = localTrailer; diff --git a/xbmc/video/test/TestVideoUtils.cpp b/xbmc/video/test/TestVideoUtils.cpp index 579fff0180f50..9feb832e5b8f2 100644 --- a/xbmc/video/test/TestVideoUtils.cpp +++ b/xbmc/video/test/TestVideoUtils.cpp @@ -7,6 +7,7 @@ */ #include "FileItem.h" +#include "URL.h" #include "Util.h" #include "filesystem/Directory.h" #include "platform/Filesystem.h" @@ -22,12 +23,23 @@ using namespace KODI; namespace fs = KODI::PLATFORM::FILESYSTEM; +namespace +{ + using OptDef = std::pair<std::string, bool>; class OpticalMediaPathTest : public testing::WithParamInterface<OptDef>, public testing::Test { }; +using TrailerDef = std::pair<std::string, std::string>; + +class TrailerTest : public testing::WithParamInterface<TrailerDef>, public testing::Test +{ +}; + +} // namespace + TEST_P(OpticalMediaPathTest, GetOpticalMediaPath) { std::error_code ec; @@ -57,3 +69,53 @@ const auto mediapath_tests = std::array{ }; INSTANTIATE_TEST_SUITE_P(TestVideoUtils, OpticalMediaPathTest, testing::ValuesIn(mediapath_tests)); + +TEST_P(TrailerTest, FindTrailer) +{ + std::string temp_path; + if (!GetParam().second.empty()) + { + std::error_code ec; + temp_path = fs::create_temp_directory(ec); + EXPECT_FALSE(ec); + XFILE::CDirectory::Create(temp_path); + const std::string file_path = URIUtils::AddFileToFolder(temp_path, GetParam().second); + { + std::ofstream of(file_path); + } + URIUtils::AddSlashAtEnd(temp_path); + } + + std::string input_path = GetParam().first; + if (!temp_path.empty()) + { + StringUtils::Replace(input_path, "#DIRECTORY#", temp_path); + StringUtils::Replace(input_path, "#URLENCODED_DIRECTORY#", CURL::Encode(temp_path)); + } + + CFileItem item(input_path, false); + EXPECT_EQ(VIDEO::UTILS::FindTrailer(item), + GetParam().second.empty() ? "" + : URIUtils::AddFileToFolder(temp_path, GetParam().second)); + + if (!temp_path.empty()) + XFILE::CDirectory::RemoveRecursive(temp_path); +} + +const auto trailer_tests = std::array{ + TrailerDef{"https://some.where/foo", ""}, + TrailerDef{"upnp://1/2/3", ""}, + TrailerDef{"bluray://1", ""}, + TrailerDef{"pvr://foobar.pvr", ""}, + TrailerDef{"plugin://plugin.video.foo/foo?param=1", ""}, + TrailerDef{"dvd://1", ""}, + TrailerDef{"stack://#DIRECTORY#foo-cd1.avi , #DIRECTORY#foo-cd2.avi", "foo-trailer.mkv"}, + TrailerDef{"stack://#DIRECTORY#foo-cd1.avi , #DIRECTORY#foo-cd2.avi", "foo-cd1-trailer.avi"}, + TrailerDef{"stack://#DIRECTORY#foo-cd1.avi , #DIRECTORY#foo-cd2.avi", "movie-trailer.mp4"}, + TrailerDef{"zip://#URLENCODED_DIRECTORY#bar.zip/bar.avi", "bar-trailer.mov"}, + TrailerDef{"zip://#URLENCODED_DIRECTORY#bar.zip/bar.mkv", "movie-trailer.ogm"}, + TrailerDef{"#DIRECTORY#bar.mkv", "bar-trailer.mkv"}, + TrailerDef{"#DIRECTORY#bar.mkv", "movie-trailer.avi"}, +}; + +INSTANTIATE_TEST_SUITE_P(TestVideoUtils, TrailerTest, testing::ValuesIn(trailer_tests)); From 9331e95276232b92b6f573f61aae14ccb9aa7e60 Mon Sep 17 00:00:00 2001 From: Tobias Markus <tobbi.bugs@googlemail.com> Date: Mon, 22 Jul 2024 22:47:45 +0200 Subject: [PATCH 523/651] xbmc/profiles: cppcheck performance fixes --- xbmc/profiles/Profile.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/xbmc/profiles/Profile.cpp b/xbmc/profiles/Profile.cpp index 5665d251981bb..f0614f2630dde 100644 --- a/xbmc/profiles/Profile.cpp +++ b/xbmc/profiles/Profile.cpp @@ -11,18 +11,16 @@ #include "XBDateTime.h" #include "utils/XMLUtils.h" -CProfile::CLock::CLock(LockType type, const std::string &password): - code(password) +CProfile::CLock::CLock(LockType type, const std::string& password) + : mode(type), code(password), settings(LOCK_LEVEL::NONE) { programs = false; pictures = false; files = false; video = false; music = false; - settings = LOCK_LEVEL::NONE; addonManager = false; games = false; - mode = type; } void CProfile::CLock::Validate() From bb68e2fa6e280dbb9a9d9d79cc4cd9f327e016f0 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 3 Oct 2024 20:29:10 +0200 Subject: [PATCH 524/651] [PVR] Context menu item 'Start recording': Fix to respect other running recording on same channel. --- xbmc/pvr/PVRContextMenus.cpp | 52 +++++++++++++----------------------- 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/xbmc/pvr/PVRContextMenus.cpp b/xbmc/pvr/PVRContextMenus.cpp index f8a64bcb81d55..9faa27d1e83db 100644 --- a/xbmc/pvr/PVRContextMenus.cpp +++ b/xbmc/pvr/PVRContextMenus.cpp @@ -268,53 +268,37 @@ bool FindSimilar::Execute(const CFileItemPtr& item) const bool StartRecording::IsVisible(const CFileItem& item) const { - const std::shared_ptr<const CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(item); - - std::shared_ptr<CPVRChannel> channel = item.GetPVRChannelInfoTag(); + const std::shared_ptr<CPVRChannel> channel{item.GetPVRChannelInfoTag()}; if (channel) + { + const std::shared_ptr<const CPVRClient> client{CServiceBroker::GetPVRManager().GetClient(item)}; return client && client->GetClientCapabilities().SupportsTimers() && !CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel); + } - const std::shared_ptr<const CPVREpgInfoTag> epg = item.GetEPGInfoTag(); - if (epg && epg->IsRecordable()) + const std::shared_ptr<const CPVREpgInfoTag> epgTag{item.GetEPGInfoTag()}; + if (epgTag && epgTag->IsRecordable()) { - if (epg->IsGapTag()) - { - channel = CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(epg); - if (channel) - { - return client && client->GetClientCapabilities().SupportsTimers() && - !CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel); - } - } - else - { - return client && client->GetClientCapabilities().SupportsTimers() && - !CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epg); - } + const std::shared_ptr<const CPVRClient> client{CServiceBroker::GetPVRManager().GetClient(item)}; + return client && client->GetClientCapabilities().SupportsTimers() && + !CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag); } + return false; } bool StartRecording::Execute(const CFileItemPtr& item) const { - const std::shared_ptr<const CPVREpgInfoTag> epgTag = item->GetEPGInfoTag(); - if (!epgTag || epgTag->IsActive()) - { - // instant recording - std::shared_ptr<CPVRChannel> channel; - if (epgTag) - channel = CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(epgTag); + const std::shared_ptr<CPVRChannel> channel{item->GetPVRChannelInfoTag()}; + if (channel) + return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().SetRecordingOnChannel(channel, + true); - if (!channel) - channel = item->GetPVRChannelInfoTag(); + const std::shared_ptr<const CPVREpgInfoTag> epgTag{item->GetEPGInfoTag()}; + if (epgTag) + return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().AddTimer(*item, false); - if (channel) - return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().SetRecordingOnChannel(channel, - true); - } - - return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().AddTimer(*item, false); + return false; } /////////////////////////////////////////////////////////////////////////////// From cfb130d42a8fce7fc6a12015e34a9df8388e47de Mon Sep 17 00:00:00 2001 From: boogie <boogiepop@gmx.com> Date: Sat, 13 Jan 2024 19:39:52 +0100 Subject: [PATCH 525/651] windowing/gbm: Dynamic plane selection This commit allows kodi to select gui and video planes according to format and modifier combination and sorts to gui plane on top of video plane if zpos is supported and not immutable by the planes. --- .../HwDecRender/RendererDRMPRIME.cpp | 2 +- xbmc/windowing/gbm/drm/DRMObject.cpp | 30 ++++ xbmc/windowing/gbm/drm/DRMObject.h | 3 + xbmc/windowing/gbm/drm/DRMUtils.cpp | 169 ++++++++++++------ xbmc/windowing/gbm/drm/DRMUtils.h | 5 +- 5 files changed, 149 insertions(+), 60 deletions(-) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp index 66df0e49c7355..2ef7f4d521a94 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp @@ -77,7 +77,7 @@ CBaseRenderer* CRendererDRMPRIME::Create(CVideoBuffer* buffer) if (!plane) return nullptr; - if (!plane->SupportsFormatAndModifier(format, modifier)) + if (!drm->FindVideoPlane(format, modifier)) return nullptr; return new CRendererDRMPRIME(); diff --git a/xbmc/windowing/gbm/drm/DRMObject.cpp b/xbmc/windowing/gbm/drm/DRMObject.cpp index 5ffce40fa31df..99dda24490a66 100644 --- a/xbmc/windowing/gbm/drm/DRMObject.cpp +++ b/xbmc/windowing/gbm/drm/DRMObject.cpp @@ -105,6 +105,25 @@ std::optional<uint64_t> CDRMObject::GetPropertyValue(std::string_view name, return {}; } +std::optional<std::span<uint64_t, 2>> CDRMObject::GetRangePropertyLimits(std::string_view name) +{ + auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(), + [&name](const auto& prop) { return prop->name == name; }); + + if (property == m_propsInfo.end()) + return {}; + + auto prop = property->get(); + + if (!static_cast<bool>(drm_property_type_is(prop, DRM_MODE_PROP_RANGE))) + return {}; + + if (prop->count_values != 2) + return {}; + + return std::make_optional<std::span<uint64_t, 2>>(prop->values, 2); +} + bool CDRMObject::SetProperty(const std::string& name, uint64_t value) { auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(), @@ -130,3 +149,14 @@ bool CDRMObject::SupportsProperty(const std::string& name) return false; } + +std::optional<bool> CDRMObject::IsPropertyImmutable(std::string_view name) +{ + auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(), + [&name](const auto& prop) { return prop->name == name; }); + + if (property == m_propsInfo.end()) + return {}; + + return static_cast<bool>(drm_property_type_is(property->get(), DRM_MODE_PROP_IMMUTABLE)); +} diff --git a/xbmc/windowing/gbm/drm/DRMObject.h b/xbmc/windowing/gbm/drm/DRMObject.h index c4200b1a864dc..39ba28a004d7f 100644 --- a/xbmc/windowing/gbm/drm/DRMObject.h +++ b/xbmc/windowing/gbm/drm/DRMObject.h @@ -12,6 +12,7 @@ #include <cstdint> #include <memory> #include <optional> +#include <span> #include <string_view> #include <vector> @@ -40,6 +41,8 @@ class CDRMObject bool SetProperty(const std::string& name, uint64_t value); bool SupportsProperty(const std::string& name); + std::optional<bool> IsPropertyImmutable(std::string_view name); + std::optional<std::span<uint64_t, 2>> GetRangePropertyLimits(std::string_view name); protected: explicit CDRMObject(int fd); diff --git a/xbmc/windowing/gbm/drm/DRMUtils.cpp b/xbmc/windowing/gbm/drm/DRMUtils.cpp index 3dd4ee97831d3..22db758ab6bb3 100644 --- a/xbmc/windowing/gbm/drm/DRMUtils.cpp +++ b/xbmc/windowing/gbm/drm/DRMUtils.cpp @@ -181,77 +181,130 @@ bool CDRMUtils::FindPreferredMode() return true; } -bool CDRMUtils::FindPlanes() +bool CDRMUtils::FindGuiPlane() { - for (size_t i = 0; i < m_crtcs.size(); i++) - { - if (!(m_encoder->GetPossibleCrtcs() & (1 << i))) + /* find the gui plane which support ARGB and 8bit or 10 bit XRGB + * prefer the one which does not support NV12, because it can be re-used in future for video + * prefer the highest id number because they are listed on top where zpos is not available + * and use the gui plane crtc as the crtc + * */ + CDRMPlane* gui_plane_nv12{nullptr}; + CDRMPlane* gui_plane{nullptr}; + CDRMCrtc* gui_crtc_nv12{nullptr}; + CDRMCrtc* gui_crtc{nullptr}; + + for (size_t crtc_offset = 0; crtc_offset < m_crtcs.size(); crtc_offset++) + { + if (!(m_encoder->GetPossibleCrtcs() & (1 << crtc_offset))) continue; - auto videoPlane = std::find_if(m_planes.begin(), m_planes.end(), [&i](auto& plane) { - if (plane->GetPossibleCrtcs() & (1 << i)) - { - return plane->SupportsFormat(DRM_FORMAT_NV12); - } - return false; - }); - - uint32_t videoPlaneId{0}; - - if (videoPlane != m_planes.end()) - videoPlaneId = videoPlane->get()->GetPlaneId(); - - auto guiPlane = - std::find_if(m_planes.begin(), m_planes.end(), [&i, &videoPlaneId](auto& plane) { - if (plane->GetPossibleCrtcs() & (1 << i)) - { - return (plane->GetPlaneId() != videoPlaneId && - (videoPlaneId == 0 || plane->SupportsFormat(DRM_FORMAT_ARGB8888)) && - (plane->SupportsFormat(DRM_FORMAT_XRGB2101010) || - plane->SupportsFormat(DRM_FORMAT_XRGB8888))); - } - return false; - }); - - if (videoPlane != m_planes.end() && guiPlane != m_planes.end()) + for (auto& plane : m_planes) { - m_crtc = m_crtcs[i].get(); - m_video_plane = videoPlane->get(); - m_gui_plane = guiPlane->get(); - break; - } + if (!(plane.get()->GetPossibleCrtcs() & (1 << crtc_offset))) + continue; - if (guiPlane != m_planes.end()) - { - if (!m_crtc && m_encoder->GetCrtcId() == m_crtcs[i]->GetCrtcId()) + if (plane.get()->SupportsFormat(DRM_FORMAT_ARGB8888) && + (plane.get()->SupportsFormat(DRM_FORMAT_XRGB2101010) || + plane.get()->SupportsFormat(DRM_FORMAT_XRGB8888))) { - m_crtc = m_crtcs[i].get(); - m_gui_plane = guiPlane->get(); - m_video_plane = nullptr; + if (plane.get()->SupportsFormat(DRM_FORMAT_NV12) && + (gui_plane_nv12 == nullptr || gui_plane_nv12->GetId() < plane.get()->GetId())) + { + gui_plane_nv12 = plane.get(); + gui_crtc_nv12 = m_crtcs[crtc_offset].get(); + } + else if (!plane.get()->SupportsFormat(DRM_FORMAT_NV12) && + (gui_plane == nullptr || gui_plane->GetId() < plane.get()->GetId())) + { + gui_plane = plane.get(); + gui_crtc = m_crtcs[crtc_offset].get(); + } } } } - CLog::Log(LOGINFO, "CDRMUtils::{} - using crtc: {}", __FUNCTION__, m_crtc->GetCrtcId()); - - // video plane may not be available - if (m_video_plane) - CLog::Log(LOGDEBUG, "CDRMUtils::{} - using video plane {}", __FUNCTION__, - m_video_plane->GetPlaneId()); - - if (m_gui_plane->SupportsFormat(DRM_FORMAT_XRGB2101010)) + // fallback to NV12 supporting plane + if (gui_plane == nullptr) { - m_gui_plane->SetFormat(DRM_FORMAT_XRGB2101010); - CLog::Log(LOGDEBUG, "CDRMUtils::{} - using 10bit gui plane {}", __FUNCTION__, - m_gui_plane->GetPlaneId()); + gui_crtc = gui_crtc_nv12; + gui_plane = gui_plane_nv12; } - else + + if (gui_plane != nullptr) { - m_gui_plane->SetFormat(DRM_FORMAT_XRGB8888); - CLog::Log(LOGDEBUG, "CDRMUtils::{} - using gui plane {}", __FUNCTION__, - m_gui_plane->GetPlaneId()); + m_crtc = gui_crtc; + m_gui_plane = gui_plane; + + CLog::Log(LOGINFO, "CDRMUtils::{} - using crtc: {}", __FUNCTION__, m_crtc->GetCrtcId()); + if (m_gui_plane->SupportsFormat(DRM_FORMAT_XRGB2101010)) + { + m_gui_plane->SetFormat(DRM_FORMAT_XRGB2101010); + CLog::Log(LOGDEBUG, "CDRMUtils::{} - using 10bit gui plane {}", __FUNCTION__, + m_gui_plane->GetPlaneId()); + } + else + { + m_gui_plane->SetFormat(DRM_FORMAT_XRGB8888); + CLog::Log(LOGDEBUG, "CDRMUtils::{} - using gui plane {}", __FUNCTION__, + m_gui_plane->GetPlaneId()); + } + return true; } + CLog::Log(LOGERROR, "CDRMUtils::{} - Can not find a GUI plane", __FUNCTION__); + return false; +} + +bool CDRMUtils::FindVideoPlane(uint32_t format, uint64_t modifier) +{ + bool supports_zpos = m_gui_plane->SupportsProperty("zpos"); + bool zpos_immutable = supports_zpos && m_gui_plane->IsPropertyImmutable("zpos").value(); + + auto crtc_offset = std::distance( + m_crtcs.begin(), + std::find_if(m_crtcs.begin(), m_crtcs.end(), + [this](auto& crtc) { return crtc->GetCrtcId() == m_crtc->GetCrtcId(); })); + + auto guiplane_id = m_gui_plane->GetId(); + auto videoPlane = std::find_if(m_planes.begin(), m_planes.end(), + [&crtc_offset, &format, &modifier, &guiplane_id](auto& plane) + { + if (plane->GetPossibleCrtcs() & (1 << crtc_offset)) + { + return (guiplane_id != plane->GetPlaneId() && + plane->SupportsFormatAndModifier(format, modifier)); + } + return false; + }); + + if (videoPlane == m_planes.end()) + { + CLog::Log(LOGERROR, + "CDRMUtils::{} - Can not find a Video Plane plane for format {}, modifier {}", + __FUNCTION__, format, modifier); + return false; + } + + m_video_plane = videoPlane->get(); + CLog::Log(LOGDEBUG, "CDRMUtils::{} - using video plane {}", __FUNCTION__, + m_video_plane->GetPlaneId()); + + if (!supports_zpos || zpos_immutable) + return true; + + // re-sort the video and gui planes + auto limits = m_gui_plane->GetRangePropertyLimits("zpos"); + + if (!limits) + return true; + + m_gui_plane->SetProperty("zpos", limits.value()[1]); + m_video_plane->SetProperty("zpos", limits.value()[0]); + CLog::Log(LOGDEBUG, "CDRMUtils::{} - gui plane id,zpos: {}, {}", __FUNCTION__, + m_gui_plane->GetId(), limits.value()[1]); + CLog::Log(LOGDEBUG, "CDRMUtils::{} - video plane id,zpos: {}, {}", __FUNCTION__, + m_video_plane->GetId(), limits.value()[0]); + return true; } @@ -467,9 +520,11 @@ bool CDRMUtils::InitDrm() if (!FindCrtc()) return false; - if (!FindPlanes()) + if (!FindGuiPlane()) return false; + FindVideoPlane(DRM_FORMAT_NV12, DRM_FORMAT_MOD_LINEAR); + if (!FindPreferredMode()) return false; diff --git a/xbmc/windowing/gbm/drm/DRMUtils.h b/xbmc/windowing/gbm/drm/DRMUtils.h index f92f716fc4f3b..b99a6dc4fe4ad 100644 --- a/xbmc/windowing/gbm/drm/DRMUtils.h +++ b/xbmc/windowing/gbm/drm/DRMUtils.h @@ -64,6 +64,8 @@ class CDRMUtils static uint32_t FourCCWithoutAlpha(uint32_t fourcc); void SetInFenceFd(int fd) { m_inFenceFd = fd; } + bool FindVideoPlane(uint32_t format, uint64_t modifier); + bool FindGuiPlane(); int TakeOutFenceFd() { int fd{-1}; @@ -89,13 +91,13 @@ class CDRMUtils int m_inFenceFd{-1}; int m_outFenceFd{-1}; + std::vector<std::unique_ptr<CDRMCrtc>> m_crtcs; std::vector<std::unique_ptr<CDRMPlane>> m_planes; private: bool FindConnector(); bool FindEncoder(); bool FindCrtc(); - bool FindPlanes(); bool FindPreferredMode(); bool RestoreOriginalMode(); RESOLUTION_INFO GetResolutionInfo(drmModeModeInfoPtr mode); @@ -106,7 +108,6 @@ class CDRMUtils std::vector<std::unique_ptr<CDRMConnector>> m_connectors; std::vector<std::unique_ptr<CDRMEncoder>> m_encoders; - std::vector<std::unique_ptr<CDRMCrtc>> m_crtcs; }; } From deb59d83f653da318521628518a4f1d752cb766f Mon Sep 17 00:00:00 2001 From: Jose Luis Marti <joseluis.marti@gmail.com> Date: Sat, 5 Oct 2024 14:11:08 +0200 Subject: [PATCH 526/651] Move CXBMCApp::GetExternalStorage to CAndroidStorageProvider --- xbmc/dialogs/GUIDialogMediaSource.cpp | 14 +++--- xbmc/platform/android/PlatformAndroid.cpp | 3 +- xbmc/platform/android/activity/XBMCApp.cpp | 37 --------------- xbmc/platform/android/activity/XBMCApp.h | 7 --- .../storage/AndroidStorageProvider.cpp | 45 ++++++++++++++++--- .../android/storage/AndroidStorageProvider.h | 8 ++++ 6 files changed, 59 insertions(+), 55 deletions(-) diff --git a/xbmc/dialogs/GUIDialogMediaSource.cpp b/xbmc/dialogs/GUIDialogMediaSource.cpp index 0afbc2d060952..1109157c63477 100644 --- a/xbmc/dialogs/GUIDialogMediaSource.cpp +++ b/xbmc/dialogs/GUIDialogMediaSource.cpp @@ -36,7 +36,7 @@ #if defined(TARGET_ANDROID) #include "utils/FileUtils.h" -#include "platform/android/activity/XBMCApp.h" +#include "platform/android/storage/AndroidStorageProvider.h" #endif #ifdef TARGET_WINDOWS_STORE @@ -249,7 +249,8 @@ void CGUIDialogMediaSource::OnPathBrowse(int item) #if defined(TARGET_ANDROID) // add the default android music directory std::string path; - if (CXBMCApp::GetExternalStorage(path, "music") && !path.empty() && CDirectory::Exists(path)) + if (CAndroidStorageProvider::GetExternalStorage(path, "music") && !path.empty() && + CDirectory::Exists(path)) { share1.strPath = path; share1.strName = g_localizeStrings.Get(20240); @@ -303,7 +304,8 @@ void CGUIDialogMediaSource::OnPathBrowse(int item) #if defined(TARGET_ANDROID) // add the default android video directory std::string path; - if (CXBMCApp::GetExternalStorage(path, "videos") && !path.empty() && CFileUtils::Exists(path)) + if (CAndroidStorageProvider::GetExternalStorage(path, "videos") && !path.empty() && + CFileUtils::Exists(path)) { share1.strPath = path; share1.strName = g_localizeStrings.Get(20241); @@ -349,7 +351,8 @@ void CGUIDialogMediaSource::OnPathBrowse(int item) #if defined(TARGET_ANDROID) // add the default android music directory std::string path; - if (CXBMCApp::GetExternalStorage(path, "pictures") && !path.empty() && CFileUtils::Exists(path)) + if (CAndroidStorageProvider::GetExternalStorage(path, "pictures") && !path.empty() && + CFileUtils::Exists(path)) { share1.strPath = path; share1.strName = g_localizeStrings.Get(20242); @@ -358,7 +361,8 @@ void CGUIDialogMediaSource::OnPathBrowse(int item) } path.clear(); - if (CXBMCApp::GetExternalStorage(path, "photos") && !path.empty() && CFileUtils::Exists(path)) + if (CAndroidStorageProvider::GetExternalStorage(path, "photos") && !path.empty() && + CFileUtils::Exists(path)) { share1.strPath = path; share1.strName = g_localizeStrings.Get(20243); diff --git a/xbmc/platform/android/PlatformAndroid.cpp b/xbmc/platform/android/PlatformAndroid.cpp index ba9208f6f5f46..d35896cd149aa 100644 --- a/xbmc/platform/android/PlatformAndroid.cpp +++ b/xbmc/platform/android/PlatformAndroid.cpp @@ -17,6 +17,7 @@ #include "platform/android/activity/XBMCApp.h" #include "platform/android/powermanagement/AndroidPowerSyscall.h" +#include "platform/android/storage/AndroidStorageProvider.h" #include <stdlib.h> @@ -66,7 +67,7 @@ void CPlatformAndroid::PlatformSyslog() CJNIBuild::BRAND, CJNIBuild::MODEL, CJNIBuild::HARDWARE); std::string extstorage; - bool extready = CXBMCApp::GetExternalStorage(extstorage); + const bool extready = CAndroidStorageProvider::GetExternalStorage(extstorage); CLog::Log( LOGINFO, "External storage path = {}; status = {}; Permissions = {}{}", extstorage, extready ? "ok" : "nok", diff --git a/xbmc/platform/android/activity/XBMCApp.cpp b/xbmc/platform/android/activity/XBMCApp.cpp index b4cdde669c835..10e00d014653d 100644 --- a/xbmc/platform/android/activity/XBMCApp.cpp +++ b/xbmc/platform/android/activity/XBMCApp.cpp @@ -81,7 +81,6 @@ #include <androidjni/Cursor.h> #include <androidjni/Display.h> #include <androidjni/DisplayManager.h> -#include <androidjni/Environment.h> #include <androidjni/File.h> #include <androidjni/Intent.h> #include <androidjni/IntentFilter.h> @@ -1091,42 +1090,6 @@ int CXBMCApp::GetBatteryLevel() const return m_batteryLevel; } -bool CXBMCApp::GetExternalStorage(std::string &path, const std::string &type /* = "" */) -{ - std::string sType; - std::string mountedState; - bool mounted = false; - - if(type == "files" || type.empty()) - { - CJNIFile external = CJNIEnvironment::getExternalStorageDirectory(); - if (external) - path = external.getAbsolutePath(); - } - else - { - if (type == "music") - sType = "Music"; // Environment.DIRECTORY_MUSIC - else if (type == "videos") - sType = "Movies"; // Environment.DIRECTORY_MOVIES - else if (type == "pictures") - sType = "Pictures"; // Environment.DIRECTORY_PICTURES - else if (type == "photos") - sType = "DCIM"; // Environment.DIRECTORY_DCIM - else if (type == "downloads") - sType = "Download"; // Environment.DIRECTORY_DOWNLOADS - if (!sType.empty()) - { - CJNIFile external = CJNIEnvironment::getExternalStoragePublicDirectory(sType); - if (external) - path = external.getAbsolutePath(); - } - } - mountedState = CJNIEnvironment::getExternalStorageState(); - mounted = (mountedState == "mounted" || mountedState == "mounted_ro"); - return mounted && !path.empty(); -} - // Used in Application.cpp to figure out volume steps int CXBMCApp::GetMaxSystemVolume() { diff --git a/xbmc/platform/android/activity/XBMCApp.h b/xbmc/platform/android/activity/XBMCApp.h index 6fa89cbce3c0e..73f5dcdf091d6 100644 --- a/xbmc/platform/android/activity/XBMCApp.h +++ b/xbmc/platform/android/activity/XBMCApp.h @@ -166,13 +166,6 @@ class CXBMCApp : public IActivityHandler, const std::string& className = std::string()); std::vector<androidPackage> GetApplications() const; - /*! - * \brief If external storage is available, it returns the path for the external storage (for the specified type) - * \param path will contain the path of the external storage (for the specified type) - * \param type optional type. Possible values are "", "files", "music", "videos", "pictures", "photos, "downloads" - * \return true if external storage is available and a valid path has been stored in the path parameter - */ - static bool GetExternalStorage(std::string& path, const std::string& type = ""); static int GetMaxSystemVolume(); static float GetSystemVolume(); static void SetSystemVolume(float percent); diff --git a/xbmc/platform/android/storage/AndroidStorageProvider.cpp b/xbmc/platform/android/storage/AndroidStorageProvider.cpp index 33f7ab8a23084..59fe3d6ae2741 100644 --- a/xbmc/platform/android/storage/AndroidStorageProvider.cpp +++ b/xbmc/platform/android/storage/AndroidStorageProvider.cpp @@ -17,8 +17,6 @@ #include "utils/URIUtils.h" #include "utils/log.h" -#include "platform/android/activity/XBMCApp.h" - #include <array> #include <cstdio> #include <cstdlib> @@ -131,7 +129,7 @@ void CAndroidStorageProvider::GetLocalDrives(VECSOURCES &localDrives) // external directory std::string path; - if (CXBMCApp::GetExternalStorage(path) && !path.empty() && XFILE::CDirectory::Exists(path)) + if (GetExternalStorage(path) && !path.empty() && XFILE::CDirectory::Exists(path)) { share.strPath = path; share.strName = g_localizeStrings.Get(21456); @@ -391,8 +389,7 @@ std::vector<std::string> CAndroidStorageProvider::GetDiskUsage() usage.clear(); // add external storage if available std::string path; - if (CXBMCApp::GetExternalStorage(path) && !path.empty() && GetStorageUsage(path, usage) && - !usage.empty()) + if (GetExternalStorage(path) && !path.empty() && GetStorageUsage(path, usage) && !usage.empty()) result.push_back(usage); // add removable storage @@ -451,3 +448,41 @@ bool CAndroidStorageProvider::GetStorageUsage(const std::string& path, std::stri PATH_MAXLEN, totalSize, "G", usedSize, "G", freeSize, "G", usedPercentage, "%"); return true; } + +bool CAndroidStorageProvider::GetExternalStorage(std::string& path, + const std::string& type /* = "" */) +{ + std::string sType; + std::string mountedState; + bool mounted = false; + + if (type == "files" || type.empty()) + { + CJNIFile external = CJNIEnvironment::getExternalStorageDirectory(); + if (external) + path = external.getAbsolutePath(); + } + else + { + if (type == "music") + sType = "Music"; // Environment.DIRECTORY_MUSIC + else if (type == "videos") + sType = "Movies"; // Environment.DIRECTORY_MOVIES + else if (type == "pictures") + sType = "Pictures"; // Environment.DIRECTORY_PICTURES + else if (type == "photos") + sType = "DCIM"; // Environment.DIRECTORY_DCIM + else if (type == "downloads") + sType = "Download"; // Environment.DIRECTORY_DOWNLOADS + if (!sType.empty()) + { + CJNIFile external = CJNIEnvironment::getExternalStoragePublicDirectory(sType); + if (external) + path = external.getAbsolutePath(); + } + } + + mountedState = CJNIEnvironment::getExternalStorageState(); + mounted = (mountedState == "mounted" || mountedState == "mounted_ro"); + return mounted && !path.empty(); +} diff --git a/xbmc/platform/android/storage/AndroidStorageProvider.h b/xbmc/platform/android/storage/AndroidStorageProvider.h index e39ff60dea7c0..abd7b8e3b2979 100644 --- a/xbmc/platform/android/storage/AndroidStorageProvider.h +++ b/xbmc/platform/android/storage/AndroidStorageProvider.h @@ -30,6 +30,14 @@ class CAndroidStorageProvider : public IStorageProvider bool PumpDriveChangeEvents(IStorageEventsCallback* callback) override; + /*! + * \brief If external storage is available, it returns the path for the external storage (for the specified type) + * \param path will contain the path of the external storage (for the specified type) + * \param type optional type. Possible values are "", "files", "music", "videos", "pictures", "photos, "downloads" + * \return true if external storage is available and a valid path has been stored in the path parameter + */ + static bool GetExternalStorage(std::string& path, const std::string& type = ""); + private: std::string unescape(const std::string& str); VECSOURCES m_removableDrives; From a43d301933dc142e880d9cc46c99934ab025f0ed Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 5 Oct 2024 11:23:19 +0200 Subject: [PATCH 527/651] [PVR] [PVR] Async EPG update: Fix removal of EPG events notified as 'deleted', take 2. --- xbmc/pvr/epg/Epg.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/pvr/epg/Epg.cpp b/xbmc/pvr/epg/Epg.cpp index bf1fd4a2ada7d..8bc6045d20683 100644 --- a/xbmc/pvr/epg/Epg.cpp +++ b/xbmc/pvr/epg/Epg.cpp @@ -223,8 +223,8 @@ bool CPVREpg::UpdateEntry(const std::shared_ptr<CPVREpgInfoTag>& tag, EPG_EVENT_ } else { - // Delete running/future events and past events if they are older than epg linger time setting - if ((existingTag->EndAsUTC() >= CDateTime::GetUTCDateTime()) || IsTagExpired(existingTag)) + // Delete future events and past events if they are older than epg linger time setting + if ((existingTag->StartAsUTC() > CDateTime::GetUTCDateTime()) || IsTagExpired(existingTag)) { m_tags.DeleteEntry(existingTag); } From 7c79183c36826ebcf2182561d5c48be634440b52 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Sun, 6 Oct 2024 13:01:25 +0100 Subject: [PATCH 528/651] [Libcdio-gplv3] Fix crash on macOS (upstream patch) --- .../libcdio-gplv3/{osx.patch => 01-osx.patch} | 0 ...rash-reading-CD-TOC-on-macOS-Ventura.patch | 26 +++++++++++++++++++ ...oid.patch => 03-fix-glob-on-android.patch} | 0 tools/depends/target/libcdio-gplv3/Makefile | 7 ++--- 4 files changed, 30 insertions(+), 3 deletions(-) rename tools/depends/target/libcdio-gplv3/{osx.patch => 01-osx.patch} (100%) create mode 100644 tools/depends/target/libcdio-gplv3/02-Fix-crash-reading-CD-TOC-on-macOS-Ventura.patch rename tools/depends/target/libcdio-gplv3/{01-fix-glob-on-android.patch => 03-fix-glob-on-android.patch} (100%) diff --git a/tools/depends/target/libcdio-gplv3/osx.patch b/tools/depends/target/libcdio-gplv3/01-osx.patch similarity index 100% rename from tools/depends/target/libcdio-gplv3/osx.patch rename to tools/depends/target/libcdio-gplv3/01-osx.patch diff --git a/tools/depends/target/libcdio-gplv3/02-Fix-crash-reading-CD-TOC-on-macOS-Ventura.patch b/tools/depends/target/libcdio-gplv3/02-Fix-crash-reading-CD-TOC-on-macOS-Ventura.patch new file mode 100644 index 0000000000000..0d8b2a0536762 --- /dev/null +++ b/tools/depends/target/libcdio-gplv3/02-Fix-crash-reading-CD-TOC-on-macOS-Ventura.patch @@ -0,0 +1,26 @@ +From 6f2426e8bf4dc5269ccbd9fbfa94340895f8be6e Mon Sep 17 00:00:00 2001 +From: Robert Kausch <robert.kausch@freac.org> +Date: Wed, 15 Mar 2023 00:02:10 +0100 +Subject: [PATCH] Fix crash reading CD TOC on macOS Ventura + +Remove unnecessary additional byte added to the TOC buffer length and length of the range requested from CFDataGetBytes. As of macOS Ventura, CFDataGetBytes checks the requested length and asserts if more data than available is requested. +--- + lib/driver/osx.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/driver/osx.c b/lib/driver/osx.c +index d48b5a0e..af2fe402 100644 +--- a/lib/driver/osx.c ++++ b/lib/driver/osx.c +@@ -1232,7 +1232,7 @@ read_toc_osx (void *p_user_data) + CFRange range; + CFIndex buf_len; + +- buf_len = CFDataGetLength( data ) + 1; ++ buf_len = CFDataGetLength( data ); + range = CFRangeMake( 0, buf_len ); + + if( ( p_env->pTOC = (CDTOC *)malloc( buf_len ) ) != NULL ) { +-- +2.46.2 + diff --git a/tools/depends/target/libcdio-gplv3/01-fix-glob-on-android.patch b/tools/depends/target/libcdio-gplv3/03-fix-glob-on-android.patch similarity index 100% rename from tools/depends/target/libcdio-gplv3/01-fix-glob-on-android.patch rename to tools/depends/target/libcdio-gplv3/03-fix-glob-on-android.patch diff --git a/tools/depends/target/libcdio-gplv3/Makefile b/tools/depends/target/libcdio-gplv3/Makefile index 5d1a8501b250c..3584ddbe26595 100644 --- a/tools/depends/target/libcdio-gplv3/Makefile +++ b/tools/depends/target/libcdio-gplv3/Makefile @@ -1,5 +1,5 @@ include ../../Makefile.include -DEPS = ../../Makefile.include Makefile osx.patch 01-fix-glob-on-android.patch ../../download-files.include +DEPS = ../../Makefile.include Makefile 01-osx.patch 02-Fix-crash-reading-CD-TOC-on-macOS-Ventura.patch 03-fix-glob-on-android.patch ../../download-files.include # lib name, version LIBNAME=libcdio @@ -24,9 +24,10 @@ $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM) cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) ifeq ($(TARGET_PLATFORM),macosx) - cd $(PLATFORM); patch -p1 -i ../osx.patch + cd $(PLATFORM); patch -p1 -i ../01-osx.patch + cd $(PLATFORM); patch -p1 -i ../02-Fix-crash-reading-CD-TOC-on-macOS-Ventura.patch endif - cd $(PLATFORM); patch -p1 -i ../01-fix-glob-on-android.patch + cd $(PLATFORM); patch -p1 -i ../03-fix-glob-on-android.patch cd $(PLATFORM); $(AUTORECONF) -vif cd $(PLATFORM); $(CONFIGURE) From 7f0a181f8e826bd8714b4bd761c631895fad0e45 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti <joseluis.marti@gmail.com> Date: Sun, 6 Oct 2024 23:33:27 +0200 Subject: [PATCH 529/651] [Android] searchable.xml file is not necessary, this file is configured and generated by the build system --- .../packaging/xbmc/res/xml/searchable.xml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 tools/android/packaging/xbmc/res/xml/searchable.xml diff --git a/tools/android/packaging/xbmc/res/xml/searchable.xml b/tools/android/packaging/xbmc/res/xml/searchable.xml deleted file mode 100644 index af66a877fe2cb..0000000000000 --- a/tools/android/packaging/xbmc/res/xml/searchable.xml +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<searchable xmlns:android="http://schemas.android.com/apk/res/android" - android:label="@string/app_name" - android:hint="@string/search_hint" - - android:searchSuggestAuthority="org.xbmc.kodi.media" - android:searchSuggestPath="suggestions" - - android:searchSuggestIntentAction="android.intent.action.GET_CONTENT" - android:searchSuggestIntentData="content://org.xbmc.kodi.media/intent" - - android:includeInGlobalSearch="true" - android:searchSettingsDescription="Media" - android:searchSuggestThreshold="3" - > -</searchable> \ No newline at end of file From 81c0d7228c01d7331374976dae15f7d4f145d623 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti <joseluis.marti@gmail.com> Date: Mon, 7 Oct 2024 00:11:10 +0200 Subject: [PATCH 530/651] [Android] Create res/xml/ directory --- tools/android/packaging/Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/android/packaging/Makefile.in b/tools/android/packaging/Makefile.in index d0e9d3cc7aca3..d29675783fe3a 100644 --- a/tools/android/packaging/Makefile.in +++ b/tools/android/packaging/Makefile.in @@ -57,7 +57,7 @@ python: | xbmc/assets cd xbmc/assets/python@PYTHON_VERSION@/lib/python@PYTHON_VERSION@/; rm -rf test config config-@PYTHON_VERSION@ lib-dynload ; find . -name "*.so" -exec rm -f {} \; res: - mkdir -p xbmc/res xbmc/res/values + mkdir -p xbmc/res xbmc/res/values xbmc/res/xml cp -rfp media/mipmap-* xbmc/res/ cp -fp $(CMAKE_SOURCE_DIR)/media/applaunch_screen.png xbmc/res/drawable/ cp -fp $(CMAKE_SOURCE_DIR)/media/applaunch_screen.png xbmc/res/drawable-xxxhdpi/ From 27fd1762539f1d4c67fa862e12d65869f07414b4 Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin <vyacheslav@karpukhin.com> Date: Tue, 2 Jul 2024 01:48:06 +0200 Subject: [PATCH 531/651] [Subtitles][Settings] Add a setting to make subtitles line spacing configurable - The default value is chosen to avoid the overlaps with Box background style - Some of the subtitles settings are moved from Expert to Advanced level --- .../resources/strings.po | 14 +++++++++++++- system/settings/settings.xml | 17 +++++++++++------ .../DVDSubtitles/DVDSubtitlesLibass.cpp | 10 ++++++---- .../VideoPlayer/DVDSubtitles/SubtitlesStyle.h | 1 + .../VideoRenderers/OverlayRenderer.cpp | 1 + xbmc/settings/Settings.h | 1 + xbmc/settings/SubtitlesSettings.cpp | 7 ++++++- xbmc/settings/SubtitlesSettings.h | 6 ++++++ 8 files changed, 45 insertions(+), 12 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 46d1d6174f504..5573d28eb6f8b 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -24169,7 +24169,19 @@ msgctxt "#39201" msgid "HDR10+" msgstr "" -#empty strings from id 39202 to 39999 +#. Subtitle line spacing setting +#: system/settings/settings.xml +msgctxt "#39202" +msgid "Line spacing" +msgstr "" + +#. Subtitle line spacing setting help +#: system/settings/settings.xml +msgctxt "#39203" +msgid "Adjust the space between lines of text. Setting a value too low may cause boxes to overlap when the \"Background type\" setting is used." +msgstr "" + +#empty strings from id 39204 to 39999 # 40000 to 40800 are reserved for Video Versions feature diff --git a/system/settings/settings.xml b/system/settings/settings.xml index 7434932d4a880..db2375bb0ae84 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -605,7 +605,7 @@ <control type="list" format="string" /> </setting> <setting id="subtitles.fontsize" type="integer" label="289" help="36186"> - <level>3</level> + <level>2</level> <default>42</default> <!-- in pixels --> <constraints> <minimum>12</minimum> @@ -618,7 +618,7 @@ <control type="list" format="string" /> </setting> <setting id="subtitles.style" type="integer" label="736" help="36187"> - <level>3</level> + <level>2</level> <default>0</default> <!-- FontStyle::NORMAL --> <constraints> <options> @@ -631,17 +631,17 @@ <control type="list" format="string" /> </setting> <setting id="subtitles.colorpick" type="string" label="737" help="36188"> - <level>3</level> + <level>2</level> <default>FFFFFFFF</default> <!-- White --> <control type="colorbutton" /> </setting> <setting id="subtitles.opacity" type="integer" label="752" help="36295"> - <level>3</level> + <level>2</level> <default>100</default> <control type="slider" format="percentage" range="0,100" /> </setting> <setting id="subtitles.bordersize" type="integer" label="39159" help="688"> - <level>3</level> + <level>2</level> <default>25</default> <dependencies> <dependency type="enable" setting="subtitles.backgroundtype" operator="!is">2</dependency> @@ -649,7 +649,7 @@ <control type="slider" format="percentage" range="0,100" /> </setting> <setting id="subtitles.bordercolorpick" type="string" label="39160" help="689"> - <level>3</level> + <level>2</level> <default>FF000000</default> <!-- Black --> <dependencies> <dependency type="enable" setting="subtitles.backgroundtype" operator="!is">2</dependency> @@ -661,6 +661,11 @@ <default>0</default> <control type="slider" format="percentage" range="0,100" /> </setting> + <setting id="subtitles.linespacing" type="integer" label="39202" help="39203"> + <level>3</level> + <default>12</default> + <control type="slider" format="percentage" range="0,100" /> + </setting> <setting id="subtitles.backgroundtype" type="integer" label="39165" help="39169"> <level>3</level> <default>0</default> <!-- BackgroundType::NONE --> diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp index 1858a4aa2e7e5..4fcf2dc2363af 100644 --- a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp +++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp @@ -420,7 +420,6 @@ void CDVDSubtitlesLibass::ApplyStyle(const std::shared_ptr<struct style>& subSty style->SecondaryColour = ConvColor(COLOR::BLACK); // Configure the effects - double lineSpacing = 0.0; if (subStyle->borderStyle == BorderType::OUTLINE || subStyle->borderStyle == BorderType::OUTLINE_NO_SHADOW) { @@ -450,8 +449,6 @@ void CDVDSubtitlesLibass::ApplyStyle(const std::shared_ptr<struct style>& subSty style->BackColour = ConvColor(subStyle->shadowColor, subStyle->shadowOpacity); // Set the box shadow color style->Shadow = (10.00 / 100 * subStyle->shadowSize) * scale; // Set the box shadow size - // By default a box overlaps the other, then we increase a bit the line spacing - lineSpacing = 8.0 * scaleDefault; } else if (subStyle->borderStyle == BorderType::SQUARE_BOX) { @@ -463,9 +460,14 @@ void CDVDSubtitlesLibass::ApplyStyle(const std::shared_ptr<struct style>& subSty style->Shadow = 4 * scale; // Space between the text and the box edges } + double lineSpacing = (static_cast<double>(subStyle->lineSpacing) * (playResY / 4)) / 100; + if (subStyle->assOverrideFont) + lineSpacing *= scaleDefault; + else + lineSpacing *= scale; // ass_set_line_spacing do not scale, so we have to scale to frame size ass_set_line_spacing(m_renderer, - lineSpacing / playResY * static_cast<double>(opts.frameHeight)); + lineSpacing / playResY * static_cast<double>(opts.frameHeight / 4)); style->Blur = (10.00 / 100 * subStyle->blur); diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h index f276012e93701..cf0c53d0f20dd 100644 --- a/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h +++ b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h @@ -101,6 +101,7 @@ struct style // Vertical margin value in pixels scaled for VIEWPORT_HEIGHT int marginVertical = MARGIN_VERTICAL; int blur = 0; // In % + int lineSpacing = 0; }; struct subtitleOpts diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.cpp index 246128003a680..aa776c5b5d045 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.cpp @@ -398,6 +398,7 @@ void CRenderer::CreateSubtitlesStyle() static_cast<double>(settings->GetVerticalMarginPerc())); m_overlayStyle->blur = settings->GetBlurSize(); + m_overlayStyle->lineSpacing = settings->GetLineSpacing(); } std::shared_ptr<COverlay> CRenderer::ConvertLibass( diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h index f600c315f5877..203479aaf1df2 100644 --- a/xbmc/settings/Settings.h +++ b/xbmc/settings/Settings.h @@ -160,6 +160,7 @@ class CSettings : public CSettingsBase, public CSettingCreator, public CSettingC static constexpr auto SETTING_SUBTITLES_BORDERCOLOR = "subtitles.bordercolorpick"; static constexpr auto SETTING_SUBTITLES_OPACITY = "subtitles.opacity"; static constexpr auto SETTING_SUBTITLES_BLUR = "subtitles.blur"; + static constexpr auto SETTING_SUBTITLES_LINE_SPACING = "subtitles.linespacing"; static constexpr auto SETTING_SUBTITLES_BACKGROUNDTYPE = "subtitles.backgroundtype"; static constexpr auto SETTING_SUBTITLES_SHADOWCOLOR = "subtitles.shadowcolor"; static constexpr auto SETTING_SUBTITLES_SHADOWOPACITY = "subtitles.shadowopacity"; diff --git a/xbmc/settings/SubtitlesSettings.cpp b/xbmc/settings/SubtitlesSettings.cpp index 66ef6360eb628..78c354b67ec48 100644 --- a/xbmc/settings/SubtitlesSettings.cpp +++ b/xbmc/settings/SubtitlesSettings.cpp @@ -38,7 +38,7 @@ CSubtitlesSettings::CSubtitlesSettings(const std::shared_ptr<CSettings>& setting CSettings::SETTING_SUBTITLES_LANGUAGES, CSettings::SETTING_SUBTITLES_STORAGEMODE, CSettings::SETTING_SUBTITLES_CUSTOMPATH, CSettings::SETTING_SUBTITLES_PAUSEONSEARCH, CSettings::SETTING_SUBTITLES_DOWNLOADFIRST, CSettings::SETTING_SUBTITLES_TV, - CSettings::SETTING_SUBTITLES_MOVIE}); + CSettings::SETTING_SUBTITLES_MOVIE, CSettings::SETTING_SUBTITLES_LINE_SPACING}); } CSubtitlesSettings::~CSubtitlesSettings() @@ -133,6 +133,11 @@ int CSubtitlesSettings::GetBlurSize() return m_settings->GetInt(CSettings::SETTING_SUBTITLES_BLUR); } +int CSubtitlesSettings::GetLineSpacing() +{ + return m_settings->GetInt(CSettings::SETTING_SUBTITLES_LINE_SPACING); +} + BackgroundType CSubtitlesSettings::GetBackgroundType() { return static_cast<BackgroundType>( diff --git a/xbmc/settings/SubtitlesSettings.h b/xbmc/settings/SubtitlesSettings.h index 3927de10690fd..831dfaf3741d9 100644 --- a/xbmc/settings/SubtitlesSettings.h +++ b/xbmc/settings/SubtitlesSettings.h @@ -160,6 +160,12 @@ class CSubtitlesSettings : public ISettingCallback, public Observable */ int GetBlurSize(); + /*! + * \brief Get line spacing + * \return The line spacing + */ + int GetLineSpacing(); + /*! * \brief Get background type * \return The background type From 05548762e507940a7b8b22abd1fd31387d6b56bf Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 8 Oct 2024 09:16:39 +0100 Subject: [PATCH 532/651] [Discs] Remove dead code --- xbmc/storage/DetectDVDType.cpp | 7 ------- xbmc/storage/DetectDVDType.h | 1 - 2 files changed, 8 deletions(-) diff --git a/xbmc/storage/DetectDVDType.cpp b/xbmc/storage/DetectDVDType.cpp index 3a3a3a08a87e3..91fd7deecb0fc 100644 --- a/xbmc/storage/DetectDVDType.cpp +++ b/xbmc/storage/DetectDVDType.cpp @@ -389,13 +389,6 @@ void CDetectDVDMedia::WaitMediaReady() std::unique_lock<CCriticalSection> waitLock(m_muReadingMedia); } -// Static function -// Returns status of the DVD Drive -bool CDetectDVDMedia::DriveReady() -{ - return m_DriveState == DriveState::READY; -} - DriveState CDetectDVDMedia::GetDriveState() { return m_DriveState; diff --git a/xbmc/storage/DetectDVDType.h b/xbmc/storage/DetectDVDType.h index 500a10f574e23..a97944509f970 100644 --- a/xbmc/storage/DetectDVDType.h +++ b/xbmc/storage/DetectDVDType.h @@ -42,7 +42,6 @@ class CDetectDVDMedia : public CThread static void WaitMediaReady(); static bool IsDiscInDrive(); - static bool DriveReady(); static DriveState GetDriveState(); static CCdInfo* GetCdInfo(); static CEvent m_evAutorun; From e0c228bb4492b4ad5591879d57a8a0bf672de5e3 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 8 Oct 2024 10:26:25 +0100 Subject: [PATCH 533/651] Linux/Udev: Fix optical detection logic --- xbmc/platform/linux/storage/UDevProvider.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xbmc/platform/linux/storage/UDevProvider.cpp b/xbmc/platform/linux/storage/UDevProvider.cpp index 7123e87a77cef..8896f3971cc23 100644 --- a/xbmc/platform/linux/storage/UDevProvider.cpp +++ b/xbmc/platform/linux/storage/UDevProvider.cpp @@ -271,11 +271,12 @@ bool CUDevProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback) if (strcmp(action, "change") == 0 && !(bd && strcmp(bd, "1") == 0)) { const char *optical = udev_device_get_property_value(dev, "ID_CDROM"); - bool isOptical = optical && (strcmp(optical, "1") != 0); + const bool isOptical = optical && (strcmp(optical, "1") == 0); storageDevice.type = isOptical ? MEDIA_DETECT::STORAGE::Type::OPTICAL : MEDIA_DETECT::STORAGE::Type::UNKNOWN; + storageDevice.path = devnode; - if (mountpoint && !isOptical) + if (mountpoint && isOptical) { CLog::Log(LOGINFO, "UDev: Changed / Added {}", mountpoint); if (callback) From 6ef252445cb196ffb07611df50f4e464dac15807 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 8 Oct 2024 11:26:11 +0100 Subject: [PATCH 534/651] MediaSources: Rename SOURCE_TYPE_DVD and SOURCE_TYPE_VIRTUAL_DVD --- xbmc/FileItem.cpp | 4 ++-- xbmc/MediaSource.cpp | 6 ++--- xbmc/MediaSource.h | 18 +++++++-------- xbmc/dialogs/GUIDialogFileBrowser.cpp | 2 +- xbmc/filesystem/SourcesDirectory.cpp | 3 ++- xbmc/filesystem/VirtualDirectory.cpp | 2 +- .../darwin/osx/storage/OSXStorageProvider.cpp | 2 +- xbmc/platform/linux/storage/UDevProvider.cpp | 2 +- .../linux/storage/UDisks2Provider.cpp | 2 +- .../platform/linux/storage/UDisksProvider.cpp | 2 +- .../win10/storage/Win10StorageProvider.cpp | 22 +++++++++---------- .../win32/storage/Win32StorageProvider.cpp | 14 ++++++------ xbmc/storage/MediaManager.cpp | 3 ++- xbmc/windows/GUIMediaWindow.cpp | 4 ++-- xbmc/windows/GUIWindowFileManager.cpp | 4 ++-- 15 files changed, 45 insertions(+), 45 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index a3fa357232aac..334d0e46e6117 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -1156,12 +1156,12 @@ bool CFileItem::IsBluray() const bool CFileItem::IsDVD() const { - return URIUtils::IsDVD(m_strPath) || m_iDriveType == CMediaSource::SOURCE_TYPE_DVD; + return URIUtils::IsDVD(m_strPath) || m_iDriveType == CMediaSource::SOURCE_TYPE_OPTICAL_DISC; } bool CFileItem::IsOnDVD() const { - return URIUtils::IsOnDVD(m_strPath) || m_iDriveType == CMediaSource::SOURCE_TYPE_DVD; + return URIUtils::IsOnDVD(m_strPath) || m_iDriveType == CMediaSource::SOURCE_TYPE_OPTICAL_DISC; } bool CFileItem::IsNfs() const diff --git a/xbmc/MediaSource.cpp b/xbmc/MediaSource.cpp index 6782ca3f1c082..04496a6142f8e 100644 --- a/xbmc/MediaSource.cpp +++ b/xbmc/MediaSource.cpp @@ -49,13 +49,13 @@ void CMediaSource::FromNameAndPaths(const std::string &category, const std::stri m_iDriveType = SOURCE_TYPE_VPATH; else if (StringUtils::StartsWithNoCase(strPath, "udf:")) { - m_iDriveType = SOURCE_TYPE_VIRTUAL_DVD; + m_iDriveType = SOURCE_TYPE_VIRTUAL_OPTICAL_DISC; strPath = "D:\\"; } else if (URIUtils::IsISO9660(strPath)) - m_iDriveType = SOURCE_TYPE_VIRTUAL_DVD; + m_iDriveType = SOURCE_TYPE_VIRTUAL_OPTICAL_DISC; else if (URIUtils::IsDVD(strPath)) - m_iDriveType = SOURCE_TYPE_DVD; + m_iDriveType = SOURCE_TYPE_OPTICAL_DISC; else if (URIUtils::IsRemote(strPath)) m_iDriveType = SOURCE_TYPE_REMOTE; else if (URIUtils::IsHD(strPath)) diff --git a/xbmc/MediaSource.h b/xbmc/MediaSource.h index 1afbea2ac7555..62f5d7e9c03a1 100644 --- a/xbmc/MediaSource.h +++ b/xbmc/MediaSource.h @@ -24,13 +24,13 @@ class CMediaSource final public: enum SourceType { - SOURCE_TYPE_UNKNOWN = 0, - SOURCE_TYPE_LOCAL = 1, - SOURCE_TYPE_DVD = 2, - SOURCE_TYPE_VIRTUAL_DVD = 3, - SOURCE_TYPE_REMOTE = 4, - SOURCE_TYPE_VPATH = 5, - SOURCE_TYPE_REMOVABLE = 6 + SOURCE_TYPE_UNKNOWN = 0, + SOURCE_TYPE_LOCAL = 1, + SOURCE_TYPE_OPTICAL_DISC = 2, + SOURCE_TYPE_VIRTUAL_OPTICAL_DISC = 3, + SOURCE_TYPE_REMOTE = 4, + SOURCE_TYPE_VPATH = 5, + SOURCE_TYPE_REMOVABLE = 6 }; bool operator==(const CMediaSource &right) const; @@ -50,9 +50,9 @@ class CMediaSource final Unknown source, maybe a wrong path. - SOURCE_TYPE_LOCAL \n Harddisk source. - - SOURCE_TYPE_DVD \n + - SOURCE_TYPE_OPTICAL_DISC \n DVD-ROM source of the build in drive, strPath may vary. - - SOURCE_TYPE_VIRTUAL_DVD \n + - SOURCE_TYPE_VIRTUAL_OPTICAL_DISC \n DVD-ROM source, strPath is fix. - SOURCE_TYPE_REMOTE \n Network source. diff --git a/xbmc/dialogs/GUIDialogFileBrowser.cpp b/xbmc/dialogs/GUIDialogFileBrowser.cpp index 8fecfb62b668f..28cfb3a68a7ad 100644 --- a/xbmc/dialogs/GUIDialogFileBrowser.cpp +++ b/xbmc/dialogs/GUIDialogFileBrowser.cpp @@ -561,7 +561,7 @@ void CGUIDialogFileBrowser::OnClick(int iItem) bool CGUIDialogFileBrowser::HaveDiscOrConnection( int iDriveType ) { - if ( iDriveType == CMediaSource::SOURCE_TYPE_DVD ) + if (iDriveType == CMediaSource::SOURCE_TYPE_OPTICAL_DISC) { if (!CServiceBroker::GetMediaManager().IsDiscInDrive()) { diff --git a/xbmc/filesystem/SourcesDirectory.cpp b/xbmc/filesystem/SourcesDirectory.cpp index 166861e32341c..96441c79ff264 100644 --- a/xbmc/filesystem/SourcesDirectory.cpp +++ b/xbmc/filesystem/SourcesDirectory.cpp @@ -60,7 +60,8 @@ bool CSourcesDirectory::GetDirectory(const VECSOURCES &sources, CFileItemList &i std::string strIcon; // We have the real DVD-ROM, set icon on disktype - if (share.m_iDriveType == CMediaSource::SOURCE_TYPE_DVD && share.m_strThumbnailImage.empty()) + if (share.m_iDriveType == CMediaSource::SOURCE_TYPE_OPTICAL_DISC && + share.m_strThumbnailImage.empty()) { CUtil::GetDVDDriveIcon( pItem->GetPath(), strIcon ); // CDetectDVDMedia::SetNewDVDShareUrl() caches disc thumb as special://temp/dvdicon.tbn diff --git a/xbmc/filesystem/VirtualDirectory.cpp b/xbmc/filesystem/VirtualDirectory.cpp index f764645cf9363..f8356c7a66aa4 100644 --- a/xbmc/filesystem/VirtualDirectory.cpp +++ b/xbmc/filesystem/VirtualDirectory.cpp @@ -176,7 +176,7 @@ void CVirtualDirectory::GetSources(VECSOURCES &shares) const for (unsigned int i = 0; i < shares.size(); ++i) { CMediaSource& share = shares[i]; - if (share.m_iDriveType == CMediaSource::SOURCE_TYPE_DVD) + if (share.m_iDriveType == CMediaSource::SOURCE_TYPE_OPTICAL_DISC) { if (CServiceBroker::GetMediaManager().IsAudio(share.strPath)) { diff --git a/xbmc/platform/darwin/osx/storage/OSXStorageProvider.cpp b/xbmc/platform/darwin/osx/storage/OSXStorageProvider.cpp index 47020128125a9..69c600773320b 100644 --- a/xbmc/platform/darwin/osx/storage/OSXStorageProvider.cpp +++ b/xbmc/platform/darwin/osx/storage/OSXStorageProvider.cpp @@ -131,7 +131,7 @@ void COSXStorageProvider::GetRemovableDrives(VECSOURCES& removableDrives) if (mediaKind != NULL && (CFStringCompare(mediaKind, CFSTR(kIOCDMediaClass), 0) == kCFCompareEqualTo || CFStringCompare(mediaKind, CFSTR(kIODVDMediaClass), 0) == kCFCompareEqualTo)) - share.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD; + share.m_iDriveType = CMediaSource::SOURCE_TYPE_OPTICAL_DISC; } removableDrives.push_back(share); } diff --git a/xbmc/platform/linux/storage/UDevProvider.cpp b/xbmc/platform/linux/storage/UDevProvider.cpp index 7123e87a77cef..03d58d7675231 100644 --- a/xbmc/platform/linux/storage/UDevProvider.cpp +++ b/xbmc/platform/linux/storage/UDevProvider.cpp @@ -175,7 +175,7 @@ void CUDevProvider::GetDisks(VECSOURCES& disks, bool removable) if (isRemovable) { if (optical) - share.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD; + share.m_iDriveType = CMediaSource::SOURCE_TYPE_OPTICAL_DISC; else share.m_iDriveType = CMediaSource::SOURCE_TYPE_REMOVABLE; } diff --git a/xbmc/platform/linux/storage/UDisks2Provider.cpp b/xbmc/platform/linux/storage/UDisks2Provider.cpp index ee2fc92ac9aa7..caa9040ff7dbd 100644 --- a/xbmc/platform/linux/storage/UDisks2Provider.cpp +++ b/xbmc/platform/linux/storage/UDisks2Provider.cpp @@ -184,7 +184,7 @@ CMediaSource CUDisks2Provider::Filesystem::ToMediaShare() const source.strPath = m_mountPoint; source.strName = GetDisplayName(); if (IsOptical()) - source.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD; + source.m_iDriveType = CMediaSource::SOURCE_TYPE_OPTICAL_DISC; else if (m_block->m_isSystem) source.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL; else diff --git a/xbmc/platform/linux/storage/UDisksProvider.cpp b/xbmc/platform/linux/storage/UDisksProvider.cpp index 083fb8b847fd6..efaedc2b91ce0 100644 --- a/xbmc/platform/linux/storage/UDisksProvider.cpp +++ b/xbmc/platform/linux/storage/UDisksProvider.cpp @@ -135,7 +135,7 @@ CMediaSource CUDiskDevice::ToMediaShare() const else source.strName = m_Label; if (m_isOptical) - source.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD; + source.m_iDriveType = CMediaSource::SOURCE_TYPE_OPTICAL_DISC; else if (m_isSystemInternal) source.m_iDriveType = CMediaSource::SOURCE_TYPE_LOCAL; else diff --git a/xbmc/platform/win10/storage/Win10StorageProvider.cpp b/xbmc/platform/win10/storage/Win10StorageProvider.cpp index bb3901afac67c..1d252fb8f95f3 100644 --- a/xbmc/platform/win10/storage/Win10StorageProvider.cpp +++ b/xbmc/platform/win10/storage/Win10StorageProvider.cpp @@ -107,12 +107,11 @@ void CStorageProvider::GetRemovableDrives(VECSOURCES &removableDrives) UINT uDriveType = GetDriveTypeA(driveLetter.c_str()); source.strPath = "win-lib://removable/" + driveLetter + "/"; - source.m_iDriveType = ( - (uDriveType == DRIVE_FIXED) ? CMediaSource::SOURCE_TYPE_LOCAL : - (uDriveType == DRIVE_REMOTE) ? CMediaSource::SOURCE_TYPE_REMOTE : - (uDriveType == DRIVE_CDROM) ? CMediaSource::SOURCE_TYPE_DVD : - (uDriveType == DRIVE_REMOVABLE) ? CMediaSource::SOURCE_TYPE_REMOVABLE : - CMediaSource::SOURCE_TYPE_UNKNOWN); + source.m_iDriveType = ((uDriveType == DRIVE_FIXED) ? CMediaSource::SOURCE_TYPE_LOCAL + : (uDriveType == DRIVE_REMOTE) ? CMediaSource::SOURCE_TYPE_REMOTE + : (uDriveType == DRIVE_CDROM) ? CMediaSource::SOURCE_TYPE_OPTICAL_DISC + : (uDriveType == DRIVE_REMOVABLE) ? CMediaSource::SOURCE_TYPE_REMOVABLE + : CMediaSource::SOURCE_TYPE_UNKNOWN); removableDrives.push_back(source); } @@ -262,12 +261,11 @@ void CStorageProvider::GetDrivesByType(VECSOURCES & localDrives, Drive_Types eDr share.m_ignore = true; if (!bUseDCD) { - share.m_iDriveType = ( - (uDriveType == DRIVE_FIXED) ? CMediaSource::SOURCE_TYPE_LOCAL : - (uDriveType == DRIVE_REMOTE) ? CMediaSource::SOURCE_TYPE_REMOTE : - (uDriveType == DRIVE_CDROM) ? CMediaSource::SOURCE_TYPE_DVD : - (uDriveType == DRIVE_REMOVABLE) ? CMediaSource::SOURCE_TYPE_REMOVABLE : - CMediaSource::SOURCE_TYPE_UNKNOWN); + share.m_iDriveType = ((uDriveType == DRIVE_FIXED) ? CMediaSource::SOURCE_TYPE_LOCAL + : (uDriveType == DRIVE_REMOTE) ? CMediaSource::SOURCE_TYPE_REMOTE + : (uDriveType == DRIVE_CDROM) ? CMediaSource::SOURCE_TYPE_OPTICAL_DISC + : (uDriveType == DRIVE_REMOVABLE) ? CMediaSource::SOURCE_TYPE_REMOVABLE + : CMediaSource::SOURCE_TYPE_UNKNOWN); } AddOrReplace(localDrives, share); diff --git a/xbmc/platform/win32/storage/Win32StorageProvider.cpp b/xbmc/platform/win32/storage/Win32StorageProvider.cpp index 9df7b2f48fcb1..2d1e63389e643 100644 --- a/xbmc/platform/win32/storage/Win32StorageProvider.cpp +++ b/xbmc/platform/win32/storage/Win32StorageProvider.cpp @@ -279,12 +279,12 @@ void CWin32StorageProvider::GetDrivesByType(VECSOURCES &localDrives, Drive_Types share.m_ignore= true; if( !bUseDCD ) { - share.m_iDriveType= ( - ( uDriveType == DRIVE_FIXED ) ? CMediaSource::SOURCE_TYPE_LOCAL : - ( uDriveType == DRIVE_REMOTE ) ? CMediaSource::SOURCE_TYPE_REMOTE : - ( uDriveType == DRIVE_CDROM ) ? CMediaSource::SOURCE_TYPE_DVD : - ( uDriveType == DRIVE_REMOVABLE ) ? CMediaSource::SOURCE_TYPE_REMOVABLE : - CMediaSource::SOURCE_TYPE_UNKNOWN ); + share.m_iDriveType = + ((uDriveType == DRIVE_FIXED) ? CMediaSource::SOURCE_TYPE_LOCAL + : (uDriveType == DRIVE_REMOTE) ? CMediaSource::SOURCE_TYPE_REMOTE + : (uDriveType == DRIVE_CDROM) ? CMediaSource::SOURCE_TYPE_OPTICAL_DISC + : (uDriveType == DRIVE_REMOVABLE) ? CMediaSource::SOURCE_TYPE_REMOVABLE + : CMediaSource::SOURCE_TYPE_UNKNOWN); } AddOrReplace(localDrives, share); @@ -392,7 +392,7 @@ bool CDetectDisc::DoWork() share.strStatus = g_localizeStrings.Get(446); share.strName = share.strPath; share.m_ignore = true; - share.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD; + share.m_iDriveType = CMediaSource::SOURCE_TYPE_OPTICAL_DISC; CServiceBroker::GetMediaManager().AddAutoSource(share, m_bautorun); #endif return true; diff --git a/xbmc/storage/MediaManager.cpp b/xbmc/storage/MediaManager.cpp index bf51733424802..5601536b5e184 100644 --- a/xbmc/storage/MediaManager.cpp +++ b/xbmc/storage/MediaManager.cpp @@ -613,7 +613,8 @@ std::string CMediaManager::GetDiscPath() m_platformStorage->GetRemovableDrives(drives); for(unsigned i = 0; i < drives.size(); ++i) { - if(drives[i].m_iDriveType == CMediaSource::SOURCE_TYPE_DVD && !drives[i].strPath.empty()) + if (drives[i].m_iDriveType == CMediaSource::SOURCE_TYPE_OPTICAL_DISC && + !drives[i].strPath.empty()) return drives[i].strPath; } diff --git a/xbmc/windows/GUIMediaWindow.cpp b/xbmc/windows/GUIMediaWindow.cpp index 9c1f8e48dfd47..f76a3931909ad 100644 --- a/xbmc/windows/GUIMediaWindow.cpp +++ b/xbmc/windows/GUIMediaWindow.cpp @@ -1206,7 +1206,7 @@ bool CGUIMediaWindow::OnSelect(int item) */ bool CGUIMediaWindow::HaveDiscOrConnection(const std::string& strPath, int iDriveType) { - if (iDriveType==CMediaSource::SOURCE_TYPE_DVD) + if (iDriveType == CMediaSource::SOURCE_TYPE_OPTICAL_DISC) { if (!CServiceBroker::GetMediaManager().IsDiscInDrive(strPath)) { @@ -1371,7 +1371,7 @@ void CGUIMediaWindow::GetDirectoryHistoryString(const CFileItem* pItem, std::str // History string of the DVD drive // must be handled separately - if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_DVD) + if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_OPTICAL_DISC) { // Remove disc label from item label // and use as history string, m_strPath diff --git a/xbmc/windows/GUIWindowFileManager.cpp b/xbmc/windows/GUIWindowFileManager.cpp index 5c288c0209a38..459a4eeab0dbb 100644 --- a/xbmc/windows/GUIWindowFileManager.cpp +++ b/xbmc/windows/GUIWindowFileManager.cpp @@ -697,7 +697,7 @@ void CGUIWindowFileManager::OnStart(CFileItem *pItem, const std::string &player) bool CGUIWindowFileManager::HaveDiscOrConnection( std::string& strPath, int iDriveType ) { - if ( iDriveType == CMediaSource::SOURCE_TYPE_DVD ) + if (iDriveType == CMediaSource::SOURCE_TYPE_OPTICAL_DISC) { if (!CServiceBroker::GetMediaManager().IsDiscInDrive(strPath)) { @@ -898,7 +898,7 @@ void CGUIWindowFileManager::GetDirectoryHistoryString(const CFileItem* pItem, st // History string of the DVD drive // must be handled separately - if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_DVD) + if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_OPTICAL_DISC) { // Remove disc label from item label // and use as history string, m_strPath From 8bb113bb2143ea43072ad5cd580b4de728d50925 Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Tue, 8 Oct 2024 13:22:04 +0200 Subject: [PATCH 535/651] [CURL] Preserve slashes between protocol and path --- xbmc/URL.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xbmc/URL.cpp b/xbmc/URL.cpp index 8bc0f98c7f595..b934e7569e27a 100644 --- a/xbmc/URL.cpp +++ b/xbmc/URL.cpp @@ -471,8 +471,11 @@ std::string CURL::GetWithoutOptions() const std::string strGet = GetWithoutFilename(); // Prevent double slash when concatenating host part and filename part - if (m_strFileName.size() && (m_strFileName[0] == '/' || m_strFileName[0] == '\\') && URIUtils::HasSlashAtEnd(strGet)) + if (!m_strFileName.empty() && (m_strFileName[0] == '/' || m_strFileName[0] == '\\') && + URIUtils::HasSlashAtEnd(strGet) && !(IsProtocol("http") || IsProtocol("https"))) + { URIUtils::RemoveSlashAtEnd(strGet); + } return strGet + m_strFileName; } From 791318540b83ab5446ffcd6e597f744e4e28d0ad Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 8 Oct 2024 12:30:34 +0100 Subject: [PATCH 536/651] udev: namespace get_mountpoint call --- xbmc/platform/linux/storage/UDevProvider.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xbmc/platform/linux/storage/UDevProvider.cpp b/xbmc/platform/linux/storage/UDevProvider.cpp index 7123e87a77cef..df5b9844c563a 100644 --- a/xbmc/platform/linux/storage/UDevProvider.cpp +++ b/xbmc/platform/linux/storage/UDevProvider.cpp @@ -17,7 +17,9 @@ extern "C" { #include <poll.h> } -static const char *get_mountpoint(const char *devnode) +namespace +{ +const char* get_mountpoint(const char* devnode) { static char buf[4096]; const char *delim = " "; @@ -59,6 +61,7 @@ static const char *get_mountpoint(const char *devnode) fclose(fp); return mountpoint; } +} // namespace CUDevProvider::CUDevProvider() { From 868c8eafea31220729551040c67c88381db9b278 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 8 Oct 2024 12:51:30 +0100 Subject: [PATCH 537/651] filesystem: kill VECSOURCES --- xbmc/GUIPassword.cpp | 39 +++++++------- xbmc/GUIPassword.h | 4 +- xbmc/MediaSource.cpp | 4 +- xbmc/MediaSource.h | 21 ++------ xbmc/Util.cpp | 14 ++--- xbmc/Util.h | 6 ++- xbmc/addons/gui/GUIWindowAddonBrowser.cpp | 2 +- .../interfaces/gui/dialogs/FileBrowser.cpp | 24 ++++----- .../interfaces/gui/dialogs/FileBrowser.h | 4 +- xbmc/dialogs/GUIDialogContextMenu.cpp | 6 +-- xbmc/dialogs/GUIDialogFileBrowser.cpp | 52 ++++++++++++++----- xbmc/dialogs/GUIDialogFileBrowser.h | 43 +++++++++++---- xbmc/dialogs/GUIDialogMediaSource.cpp | 6 +-- xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp | 4 +- xbmc/favourites/FavouritesUtils.cpp | 2 +- xbmc/filesystem/SourcesDirectory.cpp | 6 +-- xbmc/filesystem/SourcesDirectory.h | 3 +- xbmc/filesystem/VirtualDirectory.cpp | 22 ++++---- xbmc/filesystem/VirtualDirectory.h | 25 ++++----- .../games/windows/GUIViewStateWindowGames.cpp | 6 +-- xbmc/games/windows/GUIViewStateWindowGames.h | 2 +- xbmc/games/windows/GUIWindowGames.cpp | 2 +- xbmc/interfaces/builtins/LibraryBuiltins.cpp | 2 +- xbmc/interfaces/builtins/SkinBuiltins.cpp | 6 +-- xbmc/interfaces/json-rpc/FileOperations.cpp | 2 +- xbmc/interfaces/legacy/Dialog.cpp | 8 +-- xbmc/music/GUIViewStateMusic.cpp | 8 +-- xbmc/music/GUIViewStateMusic.h | 6 +-- xbmc/music/MusicDatabase.cpp | 6 +-- xbmc/music/MusicDatabase.h | 2 +- .../dialogs/GUIDialogInfoProviderSettings.cpp | 2 +- xbmc/music/dialogs/GUIDialogMusicInfo.cpp | 5 +- xbmc/music/dialogs/GUIDialogMusicInfo.h | 3 +- xbmc/music/dialogs/GUIDialogSongInfo.cpp | 2 +- xbmc/music/windows/GUIWindowMusicNav.cpp | 4 +- xbmc/music/windows/GUIWindowMusicNav.h | 2 +- xbmc/music/windows/GUIWindowMusicPlaylist.h | 2 +- xbmc/network/GUIDialogNetworkSetup.cpp | 2 +- xbmc/network/WakeOnAccess.cpp | 6 ++- .../httprequesthandler/HTTPVfsHandler.cpp | 4 +- xbmc/pictures/GUIViewStatePictures.cpp | 7 +-- xbmc/pictures/GUIViewStatePictures.h | 2 +- xbmc/pictures/GUIWindowPictures.cpp | 2 +- .../storage/AndroidStorageProvider.cpp | 10 ++-- .../android/storage/AndroidStorageProvider.h | 6 +-- .../ios-common/storage/IOSStorageProvider.h | 4 +- .../ios-common/storage/IOSStorageProvider.mm | 2 +- .../darwin/osx/storage/OSXStorageProvider.cpp | 4 +- .../darwin/osx/storage/OSXStorageProvider.h | 4 +- .../linux/storage/LinuxStorageProvider.cpp | 4 +- .../linux/storage/LinuxStorageProvider.h | 4 +- xbmc/platform/linux/storage/UDevProvider.cpp | 6 +-- xbmc/platform/linux/storage/UDevProvider.h | 6 +-- .../linux/storage/UDisks2Provider.cpp | 2 +- xbmc/platform/linux/storage/UDisks2Provider.h | 6 +-- .../platform/linux/storage/UDisksProvider.cpp | 2 +- xbmc/platform/linux/storage/UDisksProvider.h | 12 +++-- xbmc/platform/posix/PosixMountProvider.cpp | 4 +- xbmc/platform/posix/PosixMountProvider.h | 7 +-- .../win10/storage/Win10StorageProvider.cpp | 12 +++-- .../win10/storage/Win10StorageProvider.h | 8 +-- .../win32/storage/Win32StorageProvider.cpp | 12 +++-- .../win32/storage/Win32StorageProvider.h | 8 +-- .../dialogs/GUIDialogProfileSettings.cpp | 4 +- xbmc/programs/GUIViewStatePrograms.cpp | 5 +- xbmc/programs/GUIViewStatePrograms.h | 2 +- xbmc/programs/GUIWindowPrograms.cpp | 2 +- .../dialogs/GUIDialogPVRChannelManager.cpp | 2 +- xbmc/pvr/guilib/PVRGUIActionsEPG.cpp | 2 +- xbmc/settings/DisplaySettings.cpp | 4 +- xbmc/settings/MediaSettings.cpp | 4 +- xbmc/settings/MediaSourceSettings.cpp | 22 ++++---- xbmc/settings/MediaSourceSettings.h | 18 +++---- .../dialogs/GUIDialogLibExportSettings.cpp | 2 +- xbmc/settings/windows/GUIControlSettings.cpp | 4 +- xbmc/storage/IStorageProvider.h | 4 +- xbmc/storage/MediaManager.cpp | 8 +-- xbmc/storage/MediaManager.h | 8 +-- xbmc/utils/FileUtils.cpp | 4 +- xbmc/video/GUIViewStateVideo.cpp | 6 +-- xbmc/video/GUIViewStateVideo.h | 6 +-- xbmc/video/VideoDatabase.cpp | 5 +- .../dialogs/GUIDialogSubtitleSettings.cpp | 2 +- .../dialogs/GUIDialogVideoManagerExtras.cpp | 2 +- .../dialogs/GUIDialogVideoManagerVersions.cpp | 2 +- xbmc/video/windows/GUIWindowVideoNav.h | 2 +- xbmc/video/windows/GUIWindowVideoPlaylist.h | 2 +- xbmc/view/GUIViewState.cpp | 8 +-- xbmc/view/GUIViewState.h | 4 +- xbmc/windows/GUIMediaWindow.cpp | 2 +- xbmc/windows/GUIWindowFileManager.cpp | 2 +- 91 files changed, 347 insertions(+), 294 deletions(-) diff --git a/xbmc/GUIPassword.cpp b/xbmc/GUIPassword.cpp index 2a0d5ec38e3a5..d262dce02befc 100644 --- a/xbmc/GUIPassword.cpp +++ b/xbmc/GUIPassword.cpp @@ -481,9 +481,9 @@ bool CGUIPassword::CheckMenuLock(int iWindowID) bool CGUIPassword::LockSource(const std::string& strType, const std::string& strName, bool bState) { - VECSOURCES* pShares = CMediaSourceSettings::GetInstance().GetSources(strType); + std::vector<CMediaSource>* pShares = CMediaSourceSettings::GetInstance().GetSources(strType); bool bResult = false; - for (IVECSOURCES it=pShares->begin();it != pShares->end();++it) + for (std::vector<CMediaSource>::iterator it = pShares->begin(); it != pShares->end(); ++it) { if (it->strName == strName) { @@ -507,8 +507,8 @@ void CGUIPassword::LockSources(bool lock) const char* strTypes[] = {"programs", "music", "video", "pictures", "files", "games"}; for (const char* const strType : strTypes) { - VECSOURCES *shares = CMediaSourceSettings::GetInstance().GetSources(strType); - for (IVECSOURCES it=shares->begin();it != shares->end();++it) + std::vector<CMediaSource>* shares = CMediaSourceSettings::GetInstance().GetSources(strType); + for (std::vector<CMediaSource>::iterator it = shares->begin(); it != shares->end(); ++it) if (it->m_iLockMode != LOCK_MODE_EVERYONE) it->m_iHasLock = lock ? LOCK_STATE_LOCKED : LOCK_STATE_LOCK_BUT_UNLOCKED; } @@ -522,8 +522,8 @@ void CGUIPassword::RemoveSourceLocks() const char* strTypes[] = {"programs", "music", "video", "pictures", "files", "games"}; for (const char* const strType : strTypes) { - VECSOURCES *shares = CMediaSourceSettings::GetInstance().GetSources(strType); - for (IVECSOURCES it=shares->begin();it != shares->end();++it) + std::vector<CMediaSource>* shares = CMediaSourceSettings::GetInstance().GetSources(strType); + for (std::vector<CMediaSource>::iterator it = shares->begin(); it != shares->end(); ++it) if (it->m_iLockMode != LOCK_MODE_EVERYONE) // remove old info { it->m_iHasLock = LOCK_STATE_NO_LOCK; @@ -538,7 +538,8 @@ void CGUIPassword::RemoveSourceLocks() CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); } -bool CGUIPassword::IsDatabasePathUnlocked(const std::string& strPath, VECSOURCES& vecSources) +bool CGUIPassword::IsDatabasePathUnlocked(const std::string& strPath, + std::vector<CMediaSource>& sources) { const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); @@ -555,10 +556,10 @@ bool CGUIPassword::IsDatabasePathUnlocked(const std::string& strPath, VECSOURCES // try to find the best matching source bool bName = false; - int iIndex = CUtil::GetMatchingSource(strPath, vecSources, bName); + int iIndex = CUtil::GetMatchingSource(strPath, sources, bName); - if (iIndex > -1 && iIndex < static_cast<int>(vecSources.size())) - if (vecSources[iIndex].m_iHasLock < LOCK_STATE_LOCKED) + if (iIndex > -1 && iIndex < static_cast<int>(sources.size())) + if (sources[iIndex].m_iHasLock < LOCK_STATE_LOCKED) return true; return false; @@ -573,12 +574,12 @@ bool CGUIPassword::IsMediaPathUnlocked(const std::shared_ptr<CProfileManager>& p if (!g_passwordManager.bMasterUser && profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE) { - VECSOURCES& vecSources = *CMediaSourceSettings::GetInstance().GetSources(strType); + std::vector<CMediaSource>& sources = *CMediaSourceSettings::GetInstance().GetSources(strType); bool bName = false; - int iIndex = CUtil::GetMatchingSource(m_strMediaSourcePath, vecSources, bName); - if (iIndex > -1 && iIndex < static_cast<int>(vecSources.size())) + int iIndex = CUtil::GetMatchingSource(m_strMediaSourcePath, sources, bName); + if (iIndex > -1 && iIndex < static_cast<int>(sources.size())) { - return g_passwordManager.IsItemUnlocked(&vecSources[iIndex], strType); + return g_passwordManager.IsItemUnlocked(&sources[iIndex], strType); } } } @@ -588,9 +589,9 @@ bool CGUIPassword::IsMediaPathUnlocked(const std::shared_ptr<CProfileManager>& p bool CGUIPassword::IsMediaFileUnlocked(const std::string& type, const std::string& file) const { - std::vector<CMediaSource>* vecSources = CMediaSourceSettings::GetInstance().GetSources(type); + std::vector<CMediaSource>* sources = CMediaSourceSettings::GetInstance().GetSources(type); - if (!vecSources) + if (!sources) { CLog::Log(LOGERROR, "{}: CMediaSourceSettings::GetInstance().GetSources(\"{}\") returned nullptr.", @@ -603,10 +604,10 @@ bool CGUIPassword::IsMediaFileUnlocked(const std::string& type, const std::strin bool isSourceName{false}; const std::string fileBasePath = URIUtils::GetBasePath(file); - int iIndex = CUtil::GetMatchingSource(fileBasePath, *vecSources, isSourceName); + int iIndex = CUtil::GetMatchingSource(fileBasePath, *sources, isSourceName); - if (iIndex > -1 && iIndex < static_cast<int>(vecSources->size())) - return (*vecSources)[iIndex].m_iHasLock < LOCK_STATE_LOCKED; + if (iIndex > -1 && iIndex < static_cast<int>(sources->size())) + return (*sources)[iIndex].m_iHasLock < LOCK_STATE_LOCKED; return true; } diff --git a/xbmc/GUIPassword.h b/xbmc/GUIPassword.h index d2782485c5138..3ad50e16536ad 100644 --- a/xbmc/GUIPassword.h +++ b/xbmc/GUIPassword.h @@ -19,8 +19,6 @@ class CFileItem; class CMediaSource; class CProfileManager; -typedef std::vector<CMediaSource> VECSOURCES; - class CGUIPassword : public ISettingCallback { public: @@ -64,7 +62,7 @@ class CGUIPassword : public ISettingCallback bool LockSource(const std::string& strType, const std::string& strName, bool bState); void LockSources(bool lock); void RemoveSourceLocks(); - bool IsDatabasePathUnlocked(const std::string& strPath, VECSOURCES& vecSources); + bool IsDatabasePathUnlocked(const std::string& strPath, std::vector<CMediaSource>& sources); /*! \brief Helper function to test if a matching mediasource is currently unlocked for a given media file diff --git a/xbmc/MediaSource.cpp b/xbmc/MediaSource.cpp index 6782ca3f1c082..317849a6f9ca7 100644 --- a/xbmc/MediaSource.cpp +++ b/xbmc/MediaSource.cpp @@ -77,7 +77,7 @@ bool CMediaSource::operator==(const CMediaSource &share) const return true; } -void AddOrReplace(VECSOURCES& sources, const VECSOURCES& extras) +void AddOrReplace(std::vector<CMediaSource>& sources, const std::vector<CMediaSource>& extras) { unsigned int i; for( i=0;i<extras.size();++i ) @@ -96,7 +96,7 @@ void AddOrReplace(VECSOURCES& sources, const VECSOURCES& extras) } } -void AddOrReplace(VECSOURCES& sources, const CMediaSource& source) +void AddOrReplace(std::vector<CMediaSource>& sources, const CMediaSource& source) { unsigned int i; for( i=0;i<sources.size();++i ) diff --git a/xbmc/MediaSource.h b/xbmc/MediaSource.h index 1afbea2ac7555..2910de65648f1 100644 --- a/xbmc/MediaSource.h +++ b/xbmc/MediaSource.h @@ -17,7 +17,7 @@ /*! \ingroup windows \brief Represents a share. -\sa VECMediaSource, IVECSOURCES +\sa VECMediaSource, std::vector<CMediaSource>::iterator */ class CMediaSource final { @@ -90,20 +90,5 @@ class CMediaSource final bool m_allowSharing = true; /// <Allow browsing of source from UPnP / WebServer }; -/*! -\ingroup windows -\brief A vector to hold CMediaSource objects. -\sa CMediaSource, IVECSOURCES -*/ -typedef std::vector<CMediaSource> VECSOURCES; - -/*! -\ingroup windows -\brief Iterator of VECSOURCES. -\sa CMediaSource, VECSOURCES -*/ -typedef std::vector<CMediaSource>::iterator IVECSOURCES; -typedef std::vector<CMediaSource>::const_iterator CIVECSOURCES; - -void AddOrReplace(VECSOURCES& sources, const VECSOURCES& extras); -void AddOrReplace(VECSOURCES& sources, const CMediaSource& source); +void AddOrReplace(std::vector<CMediaSource>& sources, const std::vector<CMediaSource>& extras); +void AddOrReplace(std::vector<CMediaSource>& sources, const CMediaSource& source); diff --git a/xbmc/Util.cpp b/xbmc/Util.cpp index cd761165e3c2c..6488e4ce07105 100644 --- a/xbmc/Util.cpp +++ b/xbmc/Util.cpp @@ -1214,7 +1214,9 @@ void CUtil::SplitParams(const std::string ¶mString, std::vector<std::string> parameters.push_back(parameter); } -int CUtil::GetMatchingSource(const std::string& strPath1, VECSOURCES& VECSOURCES, bool& bIsSourceName) +int CUtil::GetMatchingSource(const std::string& strPath1, + std::vector<CMediaSource>& sources, + bool& bIsSourceName) { if (strPath1.empty()) return -1; @@ -1245,9 +1247,9 @@ int CUtil::GetMatchingSource(const std::string& strPath1, VECSOURCES& VECSOURCES int iIndex = -1; // we first test the NAME of a source - for (int i = 0; i < (int)VECSOURCES.size(); ++i) + for (int i = 0; i < static_cast<int>(sources.size()); ++i) { - const CMediaSource &share = VECSOURCES[i]; + const CMediaSource& share = sources[i]; std::string strName = share.strName; // special cases for dvds @@ -1284,9 +1286,9 @@ int CUtil::GetMatchingSource(const std::string& strPath1, VECSOURCES& VECSOURCES size_t iLength = 0; size_t iLenPath = strDest.size(); - for (int i = 0; i < (int)VECSOURCES.size(); ++i) + for (int i = 0; i < static_cast<int>(sources.size()); ++i) { - const CMediaSource &share = VECSOURCES[i]; + const CMediaSource& share = sources[i]; // does it match a source name? if (share.strPath.substr(0,8) == "shout://") @@ -1351,7 +1353,7 @@ int CUtil::GetMatchingSource(const std::string& strPath1, VECSOURCES& VECSOURCES bIsSourceName = false; bool bDummy; - return GetMatchingSource(strPath, VECSOURCES, bDummy); + return GetMatchingSource(strPath, sources, bDummy); } CLog::Log(LOGDEBUG, "CUtil::GetMatchingSource: no matching source found for [{}]", strPath1); diff --git a/xbmc/Util.h b/xbmc/Util.h index 74adf916cafd5..429cf3e87cdc9 100644 --- a/xbmc/Util.h +++ b/xbmc/Util.h @@ -8,7 +8,7 @@ #pragma once -#include "MediaSource.h" // Definition of VECSOURCES +#include "MediaSource.h" // Definition of std::vector<CMediaSource> #include "utils/Digest.h" #include <climits> @@ -154,7 +154,9 @@ class CUtil \param parameters the returned parameters */ static void SplitParams(const std::string& paramString, std::vector<std::string>& parameters); - static int GetMatchingSource(const std::string& strPath, VECSOURCES& VECSOURCES, bool& bIsSourceName); + static int GetMatchingSource(const std::string& strPath, + std::vector<CMediaSource>& sources, + bool& bIsSourceName); static std::string TranslateSpecialSource(const std::string &strSpecial); static void DeleteDirectoryCache(const std::string &prefix = ""); static void DeleteMusicDatabaseDirectoryCache(); diff --git a/xbmc/addons/gui/GUIWindowAddonBrowser.cpp b/xbmc/addons/gui/GUIWindowAddonBrowser.cpp index 880e784835597..12d9d13d3a82a 100644 --- a/xbmc/addons/gui/GUIWindowAddonBrowser.cpp +++ b/xbmc/addons/gui/GUIWindowAddonBrowser.cpp @@ -208,7 +208,7 @@ void CGUIWindowAddonBrowser::InstallFromZip() else { // pop up filebrowser to grab an installed folder - VECSOURCES shares = *CMediaSourceSettings::GetInstance().GetSources("files"); + std::vector<CMediaSource> shares = *CMediaSourceSettings::GetInstance().GetSources("files"); CServiceBroker::GetMediaManager().GetLocalDrives(shares); CServiceBroker::GetMediaManager().GetNetworkLocations(shares); std::string path; diff --git a/xbmc/addons/interfaces/gui/dialogs/FileBrowser.cpp b/xbmc/addons/interfaces/gui/dialogs/FileBrowser.cpp index 84d64432326ea..75e86bb4cd55d 100644 --- a/xbmc/addons/interfaces/gui/dialogs/FileBrowser.cpp +++ b/xbmc/addons/interfaces/gui/dialogs/FileBrowser.cpp @@ -70,7 +70,7 @@ bool Interface_GUIDialogFileBrowser::show_and_get_directory(KODI_HANDLE kodiBase std::string strPath = path_in; - VECSOURCES vecShares; + std::vector<CMediaSource> vecShares; GetVECShares(vecShares, shares, strPath); bool bRet = CGUIDialogFileBrowser::ShowAndGetDirectory(vecShares, heading, strPath, write_only); if (bRet) @@ -107,7 +107,7 @@ bool Interface_GUIDialogFileBrowser::show_and_get_file(KODI_HANDLE kodiBase, std::string strPath = path_in; - VECSOURCES vecShares; + std::vector<CMediaSource> vecShares; GetVECShares(vecShares, shares, strPath); bool bRet = CGUIDialogFileBrowser::ShowAndGetFile(vecShares, mask, heading, strPath, use_thumbs, use_file_directories); @@ -179,7 +179,7 @@ bool Interface_GUIDialogFileBrowser::show_and_get_file_list(KODI_HANDLE kodiBase return false; } - VECSOURCES vecShares; + std::vector<CMediaSource> vecShares; GetVECShares(vecShares, shares, ""); std::vector<std::string> pathsInt; @@ -224,7 +224,7 @@ bool Interface_GUIDialogFileBrowser::show_and_get_source(KODI_HANDLE kodiBase, std::string strPath = path_in; - VECSOURCES vecShares; + std::vector<CMediaSource> vecShares; if (additionalShare) GetVECShares(vecShares, additionalShare, strPath); bool bRet = @@ -259,7 +259,7 @@ bool Interface_GUIDialogFileBrowser::show_and_get_image(KODI_HANDLE kodiBase, std::string strPath = path_in; - VECSOURCES vecShares; + std::vector<CMediaSource> vecShares; GetVECShares(vecShares, shares, strPath); bool bRet = CGUIDialogFileBrowser::ShowAndGetImage(vecShares, heading, strPath); if (bRet) @@ -290,7 +290,7 @@ bool Interface_GUIDialogFileBrowser::show_and_get_image_list(KODI_HANDLE kodiBas return false; } - VECSOURCES vecShares; + std::vector<CMediaSource> vecShares; GetVECShares(vecShares, shares, ""); std::vector<std::string> pathsInt; @@ -335,7 +335,7 @@ void Interface_GUIDialogFileBrowser::clear_file_list(KODI_HANDLE kodiBase, } } -void Interface_GUIDialogFileBrowser::GetVECShares(VECSOURCES& vecShares, +void Interface_GUIDialogFileBrowser::GetVECShares(std::vector<CMediaSource>& vecShares, const std::string& strShares, const std::string& strPath) { @@ -352,35 +352,35 @@ void Interface_GUIDialogFileBrowser::GetVECShares(VECSOURCES& vecShares, found = strShares.find("programs"); if (found != std::string::npos) { - VECSOURCES* sources = CMediaSourceSettings::GetInstance().GetSources("programs"); + std::vector<CMediaSource>* sources = CMediaSourceSettings::GetInstance().GetSources("programs"); if (sources != nullptr) vecShares.insert(vecShares.end(), sources->begin(), sources->end()); } found = strShares.find("files"); if (found != std::string::npos) { - VECSOURCES* sources = CMediaSourceSettings::GetInstance().GetSources("files"); + std::vector<CMediaSource>* sources = CMediaSourceSettings::GetInstance().GetSources("files"); if (sources != nullptr) vecShares.insert(vecShares.end(), sources->begin(), sources->end()); } found = strShares.find("music"); if (found != std::string::npos) { - VECSOURCES* sources = CMediaSourceSettings::GetInstance().GetSources("music"); + std::vector<CMediaSource>* sources = CMediaSourceSettings::GetInstance().GetSources("music"); if (sources != nullptr) vecShares.insert(vecShares.end(), sources->begin(), sources->end()); } found = strShares.find("video"); if (found != std::string::npos) { - VECSOURCES* sources = CMediaSourceSettings::GetInstance().GetSources("video"); + std::vector<CMediaSource>* sources = CMediaSourceSettings::GetInstance().GetSources("video"); if (sources != nullptr) vecShares.insert(vecShares.end(), sources->begin(), sources->end()); } found = strShares.find("pictures"); if (found != std::string::npos) { - VECSOURCES* sources = CMediaSourceSettings::GetInstance().GetSources("pictures"); + std::vector<CMediaSource>* sources = CMediaSourceSettings::GetInstance().GetSources("pictures"); if (sources != nullptr) vecShares.insert(vecShares.end(), sources->begin(), sources->end()); } diff --git a/xbmc/addons/interfaces/gui/dialogs/FileBrowser.h b/xbmc/addons/interfaces/gui/dialogs/FileBrowser.h index c2193f3f72ce3..a16227dd4cdb0 100644 --- a/xbmc/addons/interfaces/gui/dialogs/FileBrowser.h +++ b/xbmc/addons/interfaces/gui/dialogs/FileBrowser.h @@ -15,8 +15,6 @@ class CMediaSource; -typedef std::vector<CMediaSource> VECSOURCES; - extern "C" { @@ -107,7 +105,7 @@ extern "C" //@} private: - static void GetVECShares(VECSOURCES& vecShares, + static void GetVECShares(std::vector<CMediaSource>& vecShares, const std::string& strShares, const std::string& strPath); }; diff --git a/xbmc/dialogs/GUIDialogContextMenu.cpp b/xbmc/dialogs/GUIDialogContextMenu.cpp index 48c3aafc1bb5c..afa2254f4b770 100644 --- a/xbmc/dialogs/GUIDialogContextMenu.cpp +++ b/xbmc/dialogs/GUIDialogContextMenu.cpp @@ -403,7 +403,7 @@ bool CGUIDialogContextMenu::OnContextButton(const std::string &type, const CFile items.Add(nothumb); std::string strThumb; - VECSOURCES shares; + std::vector<CMediaSource> shares; CServiceBroker::GetMediaManager().GetLocalDrives(shares); if (!CGUIDialogFileBrowser::ShowAndGetImage(items, shares, g_localizeStrings.Get(1030), strThumb)) return false; @@ -544,7 +544,7 @@ bool CGUIDialogContextMenu::OnContextButton(const std::string &type, const CFile CMediaSource *CGUIDialogContextMenu::GetShare(const std::string &type, const CFileItem *item) { - VECSOURCES *shares = CMediaSourceSettings::GetInstance().GetSources(type); + std::vector<CMediaSource>* shares = CMediaSourceSettings::GetInstance().GetSources(type); if (!shares || !item) return nullptr; for (unsigned int i = 0; i < shares->size(); i++) @@ -609,7 +609,7 @@ void CGUIDialogContextMenu::OnDeinitWindow(int nextWindowID) std::string CGUIDialogContextMenu::GetDefaultShareNameByType(const std::string &strType) { - VECSOURCES *pShares = CMediaSourceSettings::GetInstance().GetSources(strType); + std::vector<CMediaSource>* pShares = CMediaSourceSettings::GetInstance().GetSources(strType); std::string strDefault = CMediaSourceSettings::GetInstance().GetDefaultSource(strType); if (!pShares) return ""; diff --git a/xbmc/dialogs/GUIDialogFileBrowser.cpp b/xbmc/dialogs/GUIDialogFileBrowser.cpp index 8fecfb62b668f..e1086b7863559 100644 --- a/xbmc/dialogs/GUIDialogFileBrowser.cpp +++ b/xbmc/dialogs/GUIDialogFileBrowser.cpp @@ -606,7 +606,12 @@ void CGUIDialogFileBrowser::OnWindowUnload() m_viewControl.Reset(); } -bool CGUIDialogFileBrowser::ShowAndGetImage(const CFileItemList &items, const VECSOURCES &shares, const std::string &heading, std::string &result, bool* flip, int label) +bool CGUIDialogFileBrowser::ShowAndGetImage(const CFileItemList& items, + const std::vector<CMediaSource>& shares, + const std::string& heading, + std::string& result, + bool* flip, + int label) { CGUIDialogFileBrowser *browser = new CGUIDialogFileBrowser(); if (!browser) @@ -648,22 +653,29 @@ bool CGUIDialogFileBrowser::ShowAndGetImage(const CFileItemList &items, const VE return confirmed; } -bool CGUIDialogFileBrowser::ShowAndGetImage(const VECSOURCES &shares, const std::string &heading, std::string &path) +bool CGUIDialogFileBrowser::ShowAndGetImage(const std::vector<CMediaSource>& shares, + const std::string& heading, + std::string& path) { return ShowAndGetFile(shares, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), heading, path, true); // true for use thumbs } -bool CGUIDialogFileBrowser::ShowAndGetImageList(const VECSOURCES &shares, const std::string &heading, std::vector<std::string> &path) +bool CGUIDialogFileBrowser::ShowAndGetImageList(const std::vector<CMediaSource>& shares, + const std::string& heading, + std::vector<std::string>& path) { return ShowAndGetFileList(shares, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), heading, path, true); // true for use thumbs } -bool CGUIDialogFileBrowser::ShowAndGetDirectory(const VECSOURCES &shares, const std::string &heading, std::string &path, bool bWriteOnly) +bool CGUIDialogFileBrowser::ShowAndGetDirectory(const std::vector<CMediaSource>& shares, + const std::string& heading, + std::string& path, + bool bWriteOnly) { // an extension mask of "/" ensures that no files are shown if (bWriteOnly) { - VECSOURCES shareWritable; + std::vector<CMediaSource> shareWritable; for (unsigned int i=0;i<shares.size();++i) { if (shares[i].IsWritable()) @@ -676,7 +688,12 @@ bool CGUIDialogFileBrowser::ShowAndGetDirectory(const VECSOURCES &shares, const return ShowAndGetFile(shares, "/", heading, path); } -bool CGUIDialogFileBrowser::ShowAndGetFile(const VECSOURCES &shares, const std::string &mask, const std::string &heading, std::string &path, bool useThumbs /* = false */, bool useFileDirectories /* = false */) +bool CGUIDialogFileBrowser::ShowAndGetFile(const std::vector<CMediaSource>& shares, + const std::string& mask, + const std::string& heading, + std::string& path, + bool useThumbs /* = false */, + bool useFileDirectories /* = false */) { CGUIDialogFileBrowser *browser = new CGUIDialogFileBrowser(); if (!browser) @@ -727,7 +744,7 @@ bool CGUIDialogFileBrowser::ShowAndGetFile(const std::string &directory, const s // add a single share for this directory if (!singleList) { - VECSOURCES shares; + std::vector<CMediaSource> shares; CMediaSource share; share.strPath = directory; URIUtils::RemoveSlashAtEnd(share.strPath); // this is needed for the dodgy code in WINDOW_INIT @@ -767,7 +784,7 @@ bool CGUIDialogFileBrowser::ShowAndGetFile(const std::string &directory, const s { // "Browse for thumb" CServiceBroker::GetGUI()->GetWindowManager().Remove(browser->GetID()); delete browser; - VECSOURCES shares; + std::vector<CMediaSource> shares; CServiceBroker::GetMediaManager().GetLocalDrives(shares); return ShowAndGetFile(shares, mask, heading, path, useThumbs,useFileDirectories); @@ -777,7 +794,12 @@ bool CGUIDialogFileBrowser::ShowAndGetFile(const std::string &directory, const s return confirmed; } -bool CGUIDialogFileBrowser::ShowAndGetFileList(const VECSOURCES &shares, const std::string &mask, const std::string &heading, std::vector<std::string> &path, bool useThumbs /* = false */, bool useFileDirectories /* = false */) +bool CGUIDialogFileBrowser::ShowAndGetFileList(const std::vector<CMediaSource>& shares, + const std::string& mask, + const std::string& heading, + std::vector<std::string>& path, + bool useThumbs /* = false */, + bool useFileDirectories /* = false */) { CGUIDialogFileBrowser *browser = new CGUIDialogFileBrowser(); if (!browser) @@ -812,7 +834,11 @@ void CGUIDialogFileBrowser::SetHeading(const std::string &heading) SET_CONTROL_LABEL(CONTROL_HEADING_LABEL, heading); } -bool CGUIDialogFileBrowser::ShowAndGetSource(std::string &path, bool allowNetworkShares, VECSOURCES* additionalShare /* = NULL */, const std::string& strType /* = "" */) +bool CGUIDialogFileBrowser::ShowAndGetSource( + std::string& path, + bool allowNetworkShares, + std::vector<CMediaSource>* additionalShare /* = NULL */, + const std::string& strType /* = "" */) { // Technique is // 1. Show Filebrowser with currently defined local, and optionally the network locations. @@ -835,7 +861,7 @@ bool CGUIDialogFileBrowser::ShowAndGetSource(std::string &path, bool allowNetwor // Add it to our window manager CServiceBroker::GetGUI()->GetWindowManager().AddUniqueInstance(browser); - VECSOURCES shares; + std::vector<CMediaSource> shares; if (!strType.empty()) { if (additionalShare) @@ -877,7 +903,7 @@ bool CGUIDialogFileBrowser::ShowAndGetSource(std::string &path, bool allowNetwor return confirmed; } -void CGUIDialogFileBrowser::SetSources(const VECSOURCES &shares) +void CGUIDialogFileBrowser::SetSources(const std::vector<CMediaSource>& shares) { m_shares = shares; if (!m_shares.size() && m_addSourceType.empty()) @@ -939,7 +965,7 @@ bool CGUIDialogFileBrowser::OnPopupMenu(int iItem) if (m_addNetworkShareEnabled) { std::string strOldPath=m_selectedPath,newPath=m_selectedPath; - VECSOURCES shares=m_shares; + std::vector<CMediaSource> shares = m_shares; if (CGUIDialogNetworkSetup::ShowAndGetNetworkAddress(newPath)) { CServiceBroker::GetMediaManager().SetLocationPath(strOldPath, newPath); diff --git a/xbmc/dialogs/GUIDialogFileBrowser.h b/xbmc/dialogs/GUIDialogFileBrowser.h index 8cafaba25026c..6b52b110fae2f 100644 --- a/xbmc/dialogs/GUIDialogFileBrowser.h +++ b/xbmc/dialogs/GUIDialogFileBrowser.h @@ -34,16 +34,41 @@ class CGUIDialogFileBrowser : public CGUIDialog, public IBackgroundLoaderObserve bool IsConfirmed() { return m_bConfirmed; } void SetHeading(const std::string &heading); - static bool ShowAndGetDirectory(const VECSOURCES &shares, const std::string &heading, std::string &path, bool bWriteOnly=false); - static bool ShowAndGetFile(const VECSOURCES &shares, const std::string &mask, const std::string &heading, std::string &path, bool useThumbs = false, bool useFileDirectories = false); + static bool ShowAndGetDirectory(const std::vector<CMediaSource>& shares, + const std::string& heading, + std::string& path, + bool bWriteOnly = false); + static bool ShowAndGetFile(const std::vector<CMediaSource>& shares, + const std::string& mask, + const std::string& heading, + std::string& path, + bool useThumbs = false, + bool useFileDirectories = false); static bool ShowAndGetFile(const std::string &directory, const std::string &mask, const std::string &heading, std::string &path, bool useThumbs = false, bool useFileDirectories = false, bool singleList = false); - static bool ShowAndGetSource(std::string &path, bool allowNetworkShares, VECSOURCES* additionalShare = NULL, const std::string& strType=""); - static bool ShowAndGetFileList(const VECSOURCES &shares, const std::string &mask, const std::string &heading, std::vector<std::string> &path, bool useThumbs = false, bool useFileDirectories = false); - static bool ShowAndGetImage(const VECSOURCES &shares, const std::string &heading, std::string &path); - static bool ShowAndGetImage(const CFileItemList &items, const VECSOURCES &shares, const std::string &heading, std::string &path, bool* flip=NULL, int label=21371); - static bool ShowAndGetImageList(const VECSOURCES &shares, const std::string &heading, std::vector<std::string> &path); + static bool ShowAndGetSource(std::string& path, + bool allowNetworkShares, + std::vector<CMediaSource>* additionalShare = NULL, + const std::string& strType = ""); + static bool ShowAndGetFileList(const std::vector<CMediaSource>& shares, + const std::string& mask, + const std::string& heading, + std::vector<std::string>& path, + bool useThumbs = false, + bool useFileDirectories = false); + static bool ShowAndGetImage(const std::vector<CMediaSource>& shares, + const std::string& heading, + std::string& path); + static bool ShowAndGetImage(const CFileItemList& items, + const std::vector<CMediaSource>& shares, + const std::string& heading, + std::string& path, + bool* flip = NULL, + int label = 21371); + static bool ShowAndGetImageList(const std::vector<CMediaSource>& shares, + const std::string& heading, + std::vector<std::string>& path); - void SetSources(const VECSOURCES &shares); + void SetSources(const std::vector<CMediaSource>& shares); void OnItemLoaded(CFileItem *item) override {}; @@ -64,7 +89,7 @@ class CGUIDialogFileBrowser : public CGUIDialog, public IBackgroundLoaderObserve void OnEditMediaSource(CFileItem* pItem); CGUIControl *GetFirstFocusableControl(int id) override; - VECSOURCES m_shares; + std::vector<CMediaSource> m_shares; XFILE::CVirtualDirectory m_rootDir; CFileItemList* m_vecItems; CFileItem* m_Directory; diff --git a/xbmc/dialogs/GUIDialogMediaSource.cpp b/xbmc/dialogs/GUIDialogMediaSource.cpp index 1109157c63477..862354fd55841 100644 --- a/xbmc/dialogs/GUIDialogMediaSource.cpp +++ b/xbmc/dialogs/GUIDialogMediaSource.cpp @@ -152,7 +152,7 @@ bool CGUIDialogMediaSource::ShowAndAddMediaSource(const std::string &type) bool CGUIDialogMediaSource::ShowAndEditMediaSource(const std::string &type, const std::string&share) { - VECSOURCES* pShares = CMediaSourceSettings::GetInstance().GetSources(type); + std::vector<CMediaSource>* pShares = CMediaSourceSettings::GetInstance().GetSources(type); if (pShares) { for (unsigned int i = 0;i<pShares->size();++i) @@ -197,7 +197,7 @@ std::string CGUIDialogMediaSource::GetUniqueMediaSourceName() // Get unique source name for this media type unsigned int i, j = 2; bool bConfirmed = false; - VECSOURCES* pShares = CMediaSourceSettings::GetInstance().GetSources(m_type); + std::vector<CMediaSource>* pShares = CMediaSourceSettings::GetInstance().GetSources(m_type); std::string strName = m_name; while (!bConfirmed) { @@ -237,7 +237,7 @@ void CGUIDialogMediaSource::OnPathBrowse(int item) // Ignore current path is best at this stage?? std::string path = m_paths->Get(item)->GetPath(); bool allowNetworkShares(m_type != "programs"); - VECSOURCES extraShares; + std::vector<CMediaSource> extraShares; if (m_name != CUtil::GetTitleFromPath(path)) m_bNameChanged = true; diff --git a/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp b/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp index 396bea02cfdb3..fa8e76ee91997 100644 --- a/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp +++ b/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp @@ -312,12 +312,12 @@ void CGUIDialogSmartPlaylistRule::OnBrowse() } else if (m_rule.m_field == FieldPath) { - VECSOURCES sources; + std::vector<CMediaSource> sources; if (m_type == "songs" || m_type == "mixed") sources = *CMediaSourceSettings::GetInstance().GetSources("music"); if (PLAYLIST::CSmartPlaylist::IsVideoType(m_type)) { - VECSOURCES sources2 = *CMediaSourceSettings::GetInstance().GetSources("video"); + std::vector<CMediaSource> sources2 = *CMediaSourceSettings::GetInstance().GetSources("video"); sources.insert(sources.end(),sources2.begin(),sources2.end()); } CServiceBroker::GetMediaManager().GetLocalDrives(sources); diff --git a/xbmc/favourites/FavouritesUtils.cpp b/xbmc/favourites/FavouritesUtils.cpp index 5c1907c05853c..bb653222812d9 100644 --- a/xbmc/favourites/FavouritesUtils.cpp +++ b/xbmc/favourites/FavouritesUtils.cpp @@ -56,7 +56,7 @@ bool ChooseAndSetNewThumbnail(CFileItem& item) prefilledItems.Add(none); std::string thumb; - VECSOURCES sources; + std::vector<CMediaSource> sources; CServiceBroker::GetMediaManager().GetLocalDrives(sources); if (CGUIDialogFileBrowser::ShowAndGetImage(prefilledItems, sources, g_localizeStrings.Get(1030), thumb)) // Browse for image diff --git a/xbmc/filesystem/SourcesDirectory.cpp b/xbmc/filesystem/SourcesDirectory.cpp index 166861e32341c..6b82fe2734b0b 100644 --- a/xbmc/filesystem/SourcesDirectory.cpp +++ b/xbmc/filesystem/SourcesDirectory.cpp @@ -38,8 +38,8 @@ bool CSourcesDirectory::GetDirectory(const CURL& url, CFileItemList &items) std::string type(url.GetFileName()); URIUtils::RemoveSlashAtEnd(type); - VECSOURCES sources; - VECSOURCES *sourcesFromType = CMediaSourceSettings::GetInstance().GetSources(type); + std::vector<CMediaSource> sources; + std::vector<CMediaSource>* sourcesFromType = CMediaSourceSettings::GetInstance().GetSources(type); if (!sourcesFromType) return false; @@ -49,7 +49,7 @@ bool CSourcesDirectory::GetDirectory(const CURL& url, CFileItemList &items) return GetDirectory(sources, items); } -bool CSourcesDirectory::GetDirectory(const VECSOURCES &sources, CFileItemList &items) +bool CSourcesDirectory::GetDirectory(const std::vector<CMediaSource>& sources, CFileItemList& items) { for (unsigned int i = 0; i < sources.size(); ++i) { diff --git a/xbmc/filesystem/SourcesDirectory.h b/xbmc/filesystem/SourcesDirectory.h index 84df8f0c1a4c6..77634589356fb 100644 --- a/xbmc/filesystem/SourcesDirectory.h +++ b/xbmc/filesystem/SourcesDirectory.h @@ -13,7 +13,6 @@ #include <vector> class CMediaSource; -typedef std::vector<CMediaSource> VECSOURCES; namespace XFILE { @@ -23,7 +22,7 @@ namespace XFILE CSourcesDirectory(void); ~CSourcesDirectory(void) override; bool GetDirectory(const CURL& url, CFileItemList &items) override; - bool GetDirectory(const VECSOURCES &sources, CFileItemList &items); + bool GetDirectory(const std::vector<CMediaSource>& sources, CFileItemList& items); bool Exists(const CURL& url) override; bool AllowAll() const override { return true; } }; diff --git a/xbmc/filesystem/VirtualDirectory.cpp b/xbmc/filesystem/VirtualDirectory.cpp index f764645cf9363..4d9c9cd10b72a 100644 --- a/xbmc/filesystem/VirtualDirectory.cpp +++ b/xbmc/filesystem/VirtualDirectory.cpp @@ -35,12 +35,12 @@ CVirtualDirectory::~CVirtualDirectory(void) = default; /*! \brief Add shares to the virtual directory - \param VECSOURCES Shares to add - \sa CMediaSource, VECSOURCES + \param std::vector<CMediaSource> Shares to add + \sa CMediaSource, std::vector<CMediaSource> */ -void CVirtualDirectory::SetSources(const VECSOURCES& vecSources) +void CVirtualDirectory::SetSources(const std::vector<CMediaSource>& sources) { - m_vecSources = vecSources; + m_sources = sources; } /*! @@ -82,7 +82,7 @@ bool CVirtualDirectory::GetDirectory(const CURL& url, CFileItemList &items, bool items.SetPath(strPath); // grab our shares - VECSOURCES shares; + std::vector<CMediaSource> shares; GetSources(shares); CSourcesDirectory dir; return dir.GetDirectory(shares, items); @@ -101,7 +101,9 @@ void CVirtualDirectory::CancelDirectory() \note The parameter \e strPath can not be a share with directory. Eg. "iso9660://dir" will return \e false. It must be "iso9660://". */ -bool CVirtualDirectory::IsSource(const std::string& strPath, VECSOURCES *sources, std::string *name) const +bool CVirtualDirectory::IsSource(const std::string& strPath, + std::vector<CMediaSource>* sources, + std::string* name) const { std::string strPathCpy = strPath; StringUtils::TrimRight(strPathCpy, "/\\"); @@ -112,7 +114,7 @@ bool CVirtualDirectory::IsSource(const std::string& strPath, VECSOURCES *sources if(URIUtils::IsDOSPath(strPathCpy)) StringUtils::Replace(strPathCpy, '/', '\\'); - VECSOURCES shares; + std::vector<CMediaSource> shares; if (sources) shares = *sources; else @@ -144,7 +146,7 @@ bool CVirtualDirectory::IsSource(const std::string& strPath, VECSOURCES *sources bool CVirtualDirectory::IsInSource(const std::string &path) const { bool isSourceName; - VECSOURCES shares; + std::vector<CMediaSource> shares; GetSources(shares); int iShare = CUtil::GetMatchingSource(path, shares, isSourceName); if (URIUtils::IsOnDVD(path)) @@ -163,9 +165,9 @@ bool CVirtualDirectory::IsInSource(const std::string &path) const return (iShare > -1); } -void CVirtualDirectory::GetSources(VECSOURCES &shares) const +void CVirtualDirectory::GetSources(std::vector<CMediaSource>& shares) const { - shares = m_vecSources; + shares = m_sources; // add our plug n play shares if (m_allowNonLocalSources) diff --git a/xbmc/filesystem/VirtualDirectory.h b/xbmc/filesystem/VirtualDirectory.h index 5a463530225ab..d08eafaee02b9 100644 --- a/xbmc/filesystem/VirtualDirectory.h +++ b/xbmc/filesystem/VirtualDirectory.h @@ -29,26 +29,19 @@ namespace XFILE bool GetDirectory(const CURL& url, CFileItemList &items) override; void CancelDirectory() override; bool GetDirectory(const CURL& url, CFileItemList &items, bool bUseFileDirectories, bool keepImpl); - void SetSources(const VECSOURCES& vecSources); - inline unsigned int GetNumberOfSources() - { - return static_cast<uint32_t>(m_vecSources.size()); - } + void SetSources(const std::vector<CMediaSource>& sources); + inline unsigned int GetNumberOfSources() { return static_cast<uint32_t>(m_sources.size()); } - bool IsSource(const std::string& strPath, VECSOURCES *sources = NULL, std::string *name = NULL) const; + bool IsSource(const std::string& strPath, + std::vector<CMediaSource>* sources = NULL, + std::string* name = NULL) const; bool IsInSource(const std::string& strPath) const; - inline const CMediaSource& operator [](const int index) const - { - return m_vecSources[index]; - } + inline const CMediaSource& operator[](const int index) const { return m_sources[index]; } - inline CMediaSource& operator[](const int index) - { - return m_vecSources[index]; - } + inline CMediaSource& operator[](const int index) { return m_sources[index]; } - void GetSources(VECSOURCES &sources) const; + void GetSources(std::vector<CMediaSource>& sources) const; void AllowNonLocalSources(bool allow) { m_allowNonLocalSources = allow; } @@ -58,7 +51,7 @@ namespace XFILE protected: void CacheThumbs(CFileItemList &items); - VECSOURCES m_vecSources; + std::vector<CMediaSource> m_sources; bool m_allowNonLocalSources; std::shared_ptr<IDirectory> m_pDir; }; diff --git a/xbmc/games/windows/GUIViewStateWindowGames.cpp b/xbmc/games/windows/GUIViewStateWindowGames.cpp index 3f45122ac7f94..6855acfe862ef 100644 --- a/xbmc/games/windows/GUIViewStateWindowGames.cpp +++ b/xbmc/games/windows/GUIViewStateWindowGames.cpp @@ -70,14 +70,14 @@ std::string CGUIViewStateWindowGames::GetExtensions() return StringUtils::Join(exts, "|"); } -VECSOURCES& CGUIViewStateWindowGames::GetSources() +std::vector<CMediaSource>& CGUIViewStateWindowGames::GetSources() { - VECSOURCES* pGameSources = CMediaSourceSettings::GetInstance().GetSources("games"); + std::vector<CMediaSource>* pGameSources = CMediaSourceSettings::GetInstance().GetSources("games"); // Guard against source type not existing if (pGameSources == nullptr) { - static VECSOURCES empty; + static std::vector<CMediaSource> empty; return empty; } diff --git a/xbmc/games/windows/GUIViewStateWindowGames.h b/xbmc/games/windows/GUIViewStateWindowGames.h index 2da9fd6360b63..4e3cadf07b25f 100644 --- a/xbmc/games/windows/GUIViewStateWindowGames.h +++ b/xbmc/games/windows/GUIViewStateWindowGames.h @@ -27,7 +27,7 @@ class CGUIViewStateWindowGames : public CGUIViewState // implementation of CGUIViewState std::string GetLockType() override; std::string GetExtensions() override; - VECSOURCES& GetSources() override; + std::vector<CMediaSource>& GetSources() override; protected: // implementation of CGUIViewState diff --git a/xbmc/games/windows/GUIWindowGames.cpp b/xbmc/games/windows/GUIWindowGames.cpp index 64ef6e149edcf..5232c0f87212e 100644 --- a/xbmc/games/windows/GUIWindowGames.cpp +++ b/xbmc/games/windows/GUIWindowGames.cpp @@ -291,7 +291,7 @@ std::string CGUIWindowGames::GetStartFolder(const std::string& dir) } SetupShares(); - VECSOURCES shares; + std::vector<CMediaSource> shares; m_rootDir.GetSources(shares); bool bIsSourceName = false; int iIndex = CUtil::GetMatchingSource(dir, shares, bIsSourceName); diff --git a/xbmc/interfaces/builtins/LibraryBuiltins.cpp b/xbmc/interfaces/builtins/LibraryBuiltins.cpp index 5cb678a3c65d2..b7539eea7fd6d 100644 --- a/xbmc/interfaces/builtins/LibraryBuiltins.cpp +++ b/xbmc/interfaces/builtins/LibraryBuiltins.cpp @@ -122,7 +122,7 @@ static int ExportLibrary(const std::vector<std::string>& params) if (StringUtils::EqualsNoCase(params[0], "music")) iHeading = 20196; std::string path; - VECSOURCES shares; + std::vector<CMediaSource> shares; CServiceBroker::GetMediaManager().GetLocalDrives(shares); CServiceBroker::GetMediaManager().GetNetworkLocations(shares); CServiceBroker::GetMediaManager().GetRemovableDrives(shares); diff --git a/xbmc/interfaces/builtins/SkinBuiltins.cpp b/xbmc/interfaces/builtins/SkinBuiltins.cpp index f3f6d74449e61..ade4756caf301 100644 --- a/xbmc/interfaces/builtins/SkinBuiltins.cpp +++ b/xbmc/interfaces/builtins/SkinBuiltins.cpp @@ -191,7 +191,7 @@ static int SetPath(const std::vector<std::string>& params) { int string = CSkinSettings::GetInstance().TranslateString(params[0]); std::string value = CSkinSettings::GetInstance().GetString(string); - VECSOURCES localShares; + std::vector<CMediaSource> localShares; CServiceBroker::GetMediaManager().GetLocalDrives(localShares); CServiceBroker::GetMediaManager().GetNetworkLocations(localShares); if (params.size() > 1) @@ -227,7 +227,7 @@ static int SetFile(const std::vector<std::string>& params) { int string = CSkinSettings::GetInstance().TranslateString(params[0]); std::string value = CSkinSettings::GetInstance().GetString(string); - VECSOURCES localShares; + std::vector<CMediaSource> localShares; CServiceBroker::GetMediaManager().GetLocalDrives(localShares); // Note. can only browse one addon type from here @@ -289,7 +289,7 @@ static int SetImage(const std::vector<std::string>& params) { int string = CSkinSettings::GetInstance().TranslateString(params[0]); std::string value = CSkinSettings::GetInstance().GetString(string); - VECSOURCES localShares; + std::vector<CMediaSource> localShares; CServiceBroker::GetMediaManager().GetLocalDrives(localShares); if (params.size() > 1) { diff --git a/xbmc/interfaces/json-rpc/FileOperations.cpp b/xbmc/interfaces/json-rpc/FileOperations.cpp index c5eb4e5399adb..92d86c45ca72e 100644 --- a/xbmc/interfaces/json-rpc/FileOperations.cpp +++ b/xbmc/interfaces/json-rpc/FileOperations.cpp @@ -39,7 +39,7 @@ JSONRPC_STATUS CFileOperations::GetRootDirectory(const std::string &method, ITra std::string media = parameterObject["media"].asString(); StringUtils::ToLower(media); - VECSOURCES *sources = CMediaSourceSettings::GetInstance().GetSources(media); + std::vector<CMediaSource>* sources = CMediaSourceSettings::GetInstance().GetSources(media); if (sources) { CFileItemList items; diff --git a/xbmc/interfaces/legacy/Dialog.cpp b/xbmc/interfaces/legacy/Dialog.cpp index 9674875ea1a61..280c943b55d86 100644 --- a/xbmc/interfaces/legacy/Dialog.cpp +++ b/xbmc/interfaces/legacy/Dialog.cpp @@ -222,9 +222,9 @@ namespace XBMCAddon DelayedCallGuard dcguard(languageHook); std::string value; std::string mask = maskparam; - VECSOURCES *shares = CMediaSourceSettings::GetInstance().GetSources(s_shares); + std::vector<CMediaSource>* shares = CMediaSourceSettings::GetInstance().GetSources(s_shares); - VECSOURCES localShares; + std::vector<CMediaSource> localShares; if (!shares) { CServiceBroker::GetMediaManager().GetLocalDrives(localShares); @@ -255,11 +255,11 @@ namespace XBMCAddon bool useFileDirectories, const String& defaultt ) { DelayedCallGuard dcguard(languageHook); - VECSOURCES *shares = CMediaSourceSettings::GetInstance().GetSources(s_shares); + std::vector<CMediaSource>* shares = CMediaSourceSettings::GetInstance().GetSources(s_shares); std::vector<String> valuelist; String lmask = mask; - VECSOURCES localShares; + std::vector<CMediaSource> localShares; if (!shares) { CServiceBroker::GetMediaManager().GetLocalDrives(localShares); diff --git a/xbmc/music/GUIViewStateMusic.cpp b/xbmc/music/GUIViewStateMusic.cpp index 89a20a8c164ae..7c7f9d0fe59ab 100644 --- a/xbmc/music/GUIViewStateMusic.cpp +++ b/xbmc/music/GUIViewStateMusic.cpp @@ -54,7 +54,7 @@ std::string CGUIViewStateWindowMusic::GetExtensions() return CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(); } -VECSOURCES& CGUIViewStateWindowMusic::GetSources() +std::vector<CMediaSource>& CGUIViewStateWindowMusic::GetSources() { return CGUIViewState::GetSources(); } @@ -571,7 +571,7 @@ void CGUIViewStateWindowMusicNav::AddOnlineShares() if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bVirtualShares) return; - VECSOURCES *musicSources = CMediaSourceSettings::GetInstance().GetSources("music"); + std::vector<CMediaSource>* musicSources = CMediaSourceSettings::GetInstance().GetSources("music"); for (int i = 0; i < (int)musicSources->size(); ++i) { @@ -579,7 +579,7 @@ void CGUIViewStateWindowMusicNav::AddOnlineShares() } } -VECSOURCES& CGUIViewStateWindowMusicNav::GetSources() +std::vector<CMediaSource>& CGUIViewStateWindowMusicNav::GetSources() { // Setup shares we want to have m_sources.clear(); @@ -639,7 +639,7 @@ bool CGUIViewStateWindowMusicPlaylist::HideParentDirItems() return true; } -VECSOURCES& CGUIViewStateWindowMusicPlaylist::GetSources() +std::vector<CMediaSource>& CGUIViewStateWindowMusicPlaylist::GetSources() { m_sources.clear(); // Playlist share diff --git a/xbmc/music/GUIViewStateMusic.h b/xbmc/music/GUIViewStateMusic.h index 59e21d4d84526..208a95bf008ef 100644 --- a/xbmc/music/GUIViewStateMusic.h +++ b/xbmc/music/GUIViewStateMusic.h @@ -15,7 +15,7 @@ class CGUIViewStateWindowMusic : public CGUIViewState public: explicit CGUIViewStateWindowMusic(const CFileItemList& items) : CGUIViewState(items) {} protected: - VECSOURCES& GetSources() override; + std::vector<CMediaSource>& GetSources() override; KODI::PLAYLIST::Id GetPlaylist() const override; bool AutoPlayNextItem() override; std::string GetLockType() override; @@ -65,7 +65,7 @@ class CGUIViewStateWindowMusicNav : public CGUIViewStateWindowMusic protected: void SaveViewState() override; - VECSOURCES& GetSources() override; + std::vector<CMediaSource>& GetSources() override; private: void AddOnlineShares(); @@ -81,5 +81,5 @@ class CGUIViewStateWindowMusicPlaylist : public CGUIViewStateWindowMusic KODI::PLAYLIST::Id GetPlaylist() const override; bool AutoPlayNextItem() override; bool HideParentDirItems() override; - VECSOURCES& GetSources() override; + std::vector<CMediaSource>& GetSources() override; }; diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index 99d5a76ffc735..286cb928593e6 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -10221,7 +10221,7 @@ bool CMusicDatabase::DeleteAlbumSources(int idAlbum) return ExecuteQuery(PrepareSQL("DELETE FROM album_source WHERE idAlbum = %i", idAlbum)); } -bool CMusicDatabase::CheckSources(VECSOURCES& sources) +bool CMusicDatabase::CheckSources(std::vector<CMediaSource>& sources) { if (sources.empty()) { @@ -10283,7 +10283,7 @@ bool CMusicDatabase::CheckSources(VECSOURCES& sources) bool CMusicDatabase::MigrateSources() { //Fetch music sources from xml - VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("music")); + std::vector<CMediaSource> sources(*CMediaSourceSettings::GetInstance().GetSources("music")); std::string strSQL; try @@ -10323,7 +10323,7 @@ bool CMusicDatabase::MigrateSources() bool CMusicDatabase::UpdateSources() { //Check library and xml sources match - VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("music")); + std::vector<CMediaSource> sources(*CMediaSourceSettings::GetInstance().GetSources("music")); if (CheckSources(sources)) return true; diff --git a/xbmc/music/MusicDatabase.h b/xbmc/music/MusicDatabase.h index f2aca6258ec36..a2e1eb7b4405c 100644 --- a/xbmc/music/MusicDatabase.h +++ b/xbmc/music/MusicDatabase.h @@ -982,7 +982,7 @@ class CMusicDatabase : public CDatabase /*! \brief Checks that source table matches sources.xml returns true when they do */ - bool CheckSources(VECSOURCES& sources); + bool CheckSources(std::vector<CMediaSource>& sources); /*! \brief Initially fills source table from sources.xml for use only at migration of db from an earlier version than 72 diff --git a/xbmc/music/dialogs/GUIDialogInfoProviderSettings.cpp b/xbmc/music/dialogs/GUIDialogInfoProviderSettings.cpp index a443f9973136b..42fc162c3da98 100644 --- a/xbmc/music/dialogs/GUIDialogInfoProviderSettings.cpp +++ b/xbmc/music/dialogs/GUIDialogInfoProviderSettings.cpp @@ -229,7 +229,7 @@ void CGUIDialogInfoProviderSettings::OnSettingAction(const std::shared_ptr<const CGUIDialogAddonSettings::ShowForAddon(m_artistscraper, false); else if (settingId == CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER) { - VECSOURCES shares; + std::vector<CMediaSource> shares; CServiceBroker::GetMediaManager().GetLocalDrives(shares); CServiceBroker::GetMediaManager().GetNetworkLocations(shares); CServiceBroker::GetMediaManager().GetRemovableDrives(shares); diff --git a/xbmc/music/dialogs/GUIDialogMusicInfo.cpp b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp index e31ba8da339ce..3291556021205 100644 --- a/xbmc/music/dialogs/GUIDialogMusicInfo.cpp +++ b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp @@ -706,7 +706,8 @@ std::string CGUIDialogMusicInfo::GetContent() return "albums"; } -void CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(VECSOURCES &sources, const CFileItem &item) +void CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(std::vector<CMediaSource>& sources, + const CFileItem& item) { std::string itemDir; std::string artistFolder; @@ -900,7 +901,7 @@ void CGUIDialogMusicInfo::OnGetArt() // never used by Kodi again, but there is no obvious way to clear these // thumbs from the cache automatically. std::string result; - VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("music")); + std::vector<CMediaSource> sources(*CMediaSourceSettings::GetInstance().GetSources("music")); CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(sources, *m_item); CServiceBroker::GetMediaManager().GetLocalDrives(sources); if (CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(13511), result) && diff --git a/xbmc/music/dialogs/GUIDialogMusicInfo.h b/xbmc/music/dialogs/GUIDialogMusicInfo.h index 781a7d1bcbaa5..07e445d4f556d 100644 --- a/xbmc/music/dialogs/GUIDialogMusicInfo.h +++ b/xbmc/music/dialogs/GUIDialogMusicInfo.h @@ -37,7 +37,8 @@ class CGUIDialogMusicInfo : bool HasListItems() const override { return true; } CFileItemPtr GetCurrentListItem(int offset = 0) override; std::string GetContent(); - static void AddItemPathToFileBrowserSources(VECSOURCES &sources, const CFileItem &item); + static void AddItemPathToFileBrowserSources(std::vector<CMediaSource>& sources, + const CFileItem& item); void SetDiscography(CMusicDatabase& database) const; void SetSongs(const VECSONGS &songs) const; void SetArtTypeList(CFileItemList& artlist); diff --git a/xbmc/music/dialogs/GUIDialogSongInfo.cpp b/xbmc/music/dialogs/GUIDialogSongInfo.cpp index 424dcd2dd4445..467f3e06951f2 100644 --- a/xbmc/music/dialogs/GUIDialogSongInfo.cpp +++ b/xbmc/music/dialogs/GUIDialogSongInfo.cpp @@ -413,7 +413,7 @@ void CGUIDialogSongInfo::OnGetArt() // Show list of possible art for user selection std::string result; - VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("music")); + std::vector<CMediaSource> sources(*CMediaSourceSettings::GetInstance().GetSources("music")); // Add album folder as source (could be disc set) std::string albumpath = m_song->GetProperty("album_path").asString(); if (!albumpath.empty()) diff --git a/xbmc/music/windows/GUIWindowMusicNav.cpp b/xbmc/music/windows/GUIWindowMusicNav.cpp index 840881515a357..0a30faa73ec7d 100644 --- a/xbmc/music/windows/GUIWindowMusicNav.cpp +++ b/xbmc/music/windows/GUIWindowMusicNav.cpp @@ -921,10 +921,10 @@ void CGUIWindowMusicNav::AddSearchFolder() if (viewState) { // add our remove the musicsearch source - VECSOURCES &sources = viewState->GetSources(); + std::vector<CMediaSource>& sources = viewState->GetSources(); bool haveSearchSource = false; bool needSearchSource = !GetProperty("search").empty() || !m_searchWithEdit; // we always need it if we don't have the edit control - for (IVECSOURCES it = sources.begin(); it != sources.end(); ++it) + for (std::vector<CMediaSource>::iterator it = sources.begin(); it != sources.end(); ++it) { CMediaSource& share = *it; if (share.strPath == "musicsearch://") diff --git a/xbmc/music/windows/GUIWindowMusicNav.h b/xbmc/music/windows/GUIWindowMusicNav.h index 9804e7f27bbda..8b6e2dc5669e4 100644 --- a/xbmc/music/windows/GUIWindowMusicNav.h +++ b/xbmc/music/windows/GUIWindowMusicNav.h @@ -41,7 +41,7 @@ class CGUIWindowMusicNav : public CGUIWindowMusicBase bool GetSongsFromPlayList(const std::string& strPlayList, CFileItemList &items); bool ManageInfoProvider(const CFileItemPtr& item); - VECSOURCES m_shares; + std::vector<CMediaSource> m_shares; // searching void OnSearchUpdate(); diff --git a/xbmc/music/windows/GUIWindowMusicPlaylist.h b/xbmc/music/windows/GUIWindowMusicPlaylist.h index 019a1c72c5bd5..b7d3f6aa11dee 100644 --- a/xbmc/music/windows/GUIWindowMusicPlaylist.h +++ b/xbmc/music/windows/GUIWindowMusicPlaylist.h @@ -40,5 +40,5 @@ class CGUIWindowMusicPlayList : public CGUIWindowMusicBase bool MoveCurrentPlayListItem(int iItem, int iAction, bool bUpdate = true); int m_movingFrom; - VECSOURCES m_shares; + std::vector<CMediaSource> m_shares; }; diff --git a/xbmc/network/GUIDialogNetworkSetup.cpp b/xbmc/network/GUIDialogNetworkSetup.cpp index 0b56e36d10571..b6b459243d1fe 100644 --- a/xbmc/network/GUIDialogNetworkSetup.cpp +++ b/xbmc/network/GUIDialogNetworkSetup.cpp @@ -211,7 +211,7 @@ void CGUIDialogNetworkSetup::InitializeSettings() void CGUIDialogNetworkSetup::OnServerBrowse() { // open a filebrowser dialog with the current address - VECSOURCES shares; + std::vector<CMediaSource> shares; std::string path = ConstructPath(); // get the share as the base path CMediaSource share; diff --git a/xbmc/network/WakeOnAccess.cpp b/xbmc/network/WakeOnAccess.cpp index 36ba50ef12481..672a7e4244a6d 100644 --- a/xbmc/network/WakeOnAccess.cpp +++ b/xbmc/network/WakeOnAccess.cpp @@ -662,13 +662,15 @@ static void AddHostsFromMediaSource(const CMediaSource& source, std::vector<std: } } -static void AddHostsFromVecSource(const VECSOURCES& sources, std::vector<std::string>& hosts) +static void AddHostsFromVecSource(const std::vector<CMediaSource>& sources, + std::vector<std::string>& hosts) { for (const auto& it : sources) AddHostsFromMediaSource(it, hosts); } -static void AddHostsFromVecSource(const VECSOURCES* sources, std::vector<std::string>& hosts) +static void AddHostsFromVecSource(const std::vector<CMediaSource>* sources, + std::vector<std::string>& hosts) { if (sources) AddHostsFromVecSource(*sources, hosts); diff --git a/xbmc/network/httprequesthandler/HTTPVfsHandler.cpp b/xbmc/network/httprequesthandler/HTTPVfsHandler.cpp index ef2f7a646b617..021b10c3eb048 100644 --- a/xbmc/network/httprequesthandler/HTTPVfsHandler.cpp +++ b/xbmc/network/httprequesthandler/HTTPVfsHandler.cpp @@ -44,7 +44,7 @@ CHTTPVfsHandler::CHTTPVfsHandler(const HTTPRequest &request) realPath = CURL(realPath).GetHostName(); // Check manually configured sources - VECSOURCES *sources = NULL; + std::vector<CMediaSource>* sources = NULL; for (unsigned int index = 0; index < size && !accessible; index++) { sources = CMediaSourceSettings::GetInstance().GetSources(sourceTypes[index]); @@ -76,7 +76,7 @@ CHTTPVfsHandler::CHTTPVfsHandler(const HTTPRequest &request) if (!accessible) { bool isSource; - VECSOURCES removableSources; + std::vector<CMediaSource> removableSources; CServiceBroker::GetMediaManager().GetRemovableDrives(removableSources); int sourceIndex = CUtil::GetMatchingSource(realPath, removableSources, isSource); if (sourceIndex >= 0 && sourceIndex < static_cast<int>(removableSources.size()) && diff --git a/xbmc/pictures/GUIViewStatePictures.cpp b/xbmc/pictures/GUIViewStatePictures.cpp index 5b9ebfd208e6e..3c25123db578a 100644 --- a/xbmc/pictures/GUIViewStatePictures.cpp +++ b/xbmc/pictures/GUIViewStatePictures.cpp @@ -71,14 +71,15 @@ std::string CGUIViewStateWindowPictures::GetExtensions() return extensions; } -VECSOURCES& CGUIViewStateWindowPictures::GetSources() +std::vector<CMediaSource>& CGUIViewStateWindowPictures::GetSources() { - VECSOURCES *pictureSources = CMediaSourceSettings::GetInstance().GetSources("pictures"); + std::vector<CMediaSource>* pictureSources = + CMediaSourceSettings::GetInstance().GetSources("pictures"); // Guard against source type not existing if (pictureSources == nullptr) { - static VECSOURCES empty; + static std::vector<CMediaSource> empty; return empty; } diff --git a/xbmc/pictures/GUIViewStatePictures.h b/xbmc/pictures/GUIViewStatePictures.h index 343089a9a1c1b..6d3d097d30e64 100644 --- a/xbmc/pictures/GUIViewStatePictures.h +++ b/xbmc/pictures/GUIViewStatePictures.h @@ -17,7 +17,7 @@ class CGUIViewStateWindowPictures : public CGUIViewState std::string GetLockType() override; std::string GetExtensions() override; - VECSOURCES& GetSources() override; + std::vector<CMediaSource>& GetSources() override; protected: void SaveViewState() override; diff --git a/xbmc/pictures/GUIWindowPictures.cpp b/xbmc/pictures/GUIWindowPictures.cpp index 89d704bef8abb..089fd9e2e5d7d 100644 --- a/xbmc/pictures/GUIWindowPictures.cpp +++ b/xbmc/pictures/GUIWindowPictures.cpp @@ -597,7 +597,7 @@ std::string CGUIWindowPictures::GetStartFolder(const std::string &dir) return "addons://sources/image/"; SetupShares(); - VECSOURCES shares; + std::vector<CMediaSource> shares; m_rootDir.GetSources(shares); bool bIsSourceName = false; int iIndex = CUtil::GetMatchingSource(dir, shares, bIsSourceName); diff --git a/xbmc/platform/android/storage/AndroidStorageProvider.cpp b/xbmc/platform/android/storage/AndroidStorageProvider.cpp index 59fe3d6ae2741..6ab1e262f3ea2 100644 --- a/xbmc/platform/android/storage/AndroidStorageProvider.cpp +++ b/xbmc/platform/android/storage/AndroidStorageProvider.cpp @@ -123,7 +123,7 @@ std::string CAndroidStorageProvider::unescape(const std::string& str) return retString; } -void CAndroidStorageProvider::GetLocalDrives(VECSOURCES &localDrives) +void CAndroidStorageProvider::GetLocalDrives(std::vector<CMediaSource>& localDrives) { CMediaSource share; @@ -143,7 +143,7 @@ void CAndroidStorageProvider::GetLocalDrives(VECSOURCES &localDrives) localDrives.push_back(share); } -void CAndroidStorageProvider::GetRemovableDrives(VECSOURCES& removableDrives) +void CAndroidStorageProvider::GetRemovableDrives(std::vector<CMediaSource>& removableDrives) { bool inError = false; @@ -167,7 +167,7 @@ void CAndroidStorageProvider::GetRemovableDrives(VECSOURCES& removableDrives) if (!inError) { - VECSOURCES droidDrives; + std::vector<CMediaSource> droidDrives; for (int i = 0; i < vols.size(); ++i) { @@ -393,7 +393,7 @@ std::vector<std::string> CAndroidStorageProvider::GetDiskUsage() result.push_back(usage); // add removable storage - VECSOURCES drives; + std::vector<CMediaSource> drives; GetRemovableDrives(drives); for (unsigned int i = 0; i < drives.size(); i++) { @@ -407,7 +407,7 @@ std::vector<std::string> CAndroidStorageProvider::GetDiskUsage() bool CAndroidStorageProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback) { - VECSOURCES drives; + std::vector<CMediaSource> drives; GetRemovableDrives(drives); bool changed = m_removableDrives != drives; m_removableDrives = std::move(drives); diff --git a/xbmc/platform/android/storage/AndroidStorageProvider.h b/xbmc/platform/android/storage/AndroidStorageProvider.h index abd7b8e3b2979..27ed350c38372 100644 --- a/xbmc/platform/android/storage/AndroidStorageProvider.h +++ b/xbmc/platform/android/storage/AndroidStorageProvider.h @@ -24,8 +24,8 @@ class CAndroidStorageProvider : public IStorageProvider void Stop() override {} bool Eject(const std::string& mountpath) override { return false; } - void GetLocalDrives(VECSOURCES& localDrives) override; - void GetRemovableDrives(VECSOURCES& removableDrives) override; + void GetLocalDrives(std::vector<CMediaSource>& localDrives) override; + void GetRemovableDrives(std::vector<CMediaSource>& removableDrives) override; std::vector<std::string> GetDiskUsage() override; bool PumpDriveChangeEvents(IStorageEventsCallback* callback) override; @@ -40,7 +40,7 @@ class CAndroidStorageProvider : public IStorageProvider private: std::string unescape(const std::string& str); - VECSOURCES m_removableDrives; + std::vector<CMediaSource> m_removableDrives; unsigned int m_removableLength; static std::set<std::string> GetRemovableDrives(); diff --git a/xbmc/platform/darwin/ios-common/storage/IOSStorageProvider.h b/xbmc/platform/darwin/ios-common/storage/IOSStorageProvider.h index 902234287e3c8..c01cf772a95db 100644 --- a/xbmc/platform/darwin/ios-common/storage/IOSStorageProvider.h +++ b/xbmc/platform/darwin/ios-common/storage/IOSStorageProvider.h @@ -22,8 +22,8 @@ class CIOSStorageProvider : public IStorageProvider void Initialize() override {} void Stop() override {} - void GetLocalDrives(VECSOURCES& localDrives) override; - void GetRemovableDrives(VECSOURCES& removableDrives) override {} + void GetLocalDrives(std::vector<CMediaSource>& localDrives) override; + void GetRemovableDrives(std::vector<CMediaSource>& removableDrives) override {} std::vector<std::string> GetDiskUsage(void) override; diff --git a/xbmc/platform/darwin/ios-common/storage/IOSStorageProvider.mm b/xbmc/platform/darwin/ios-common/storage/IOSStorageProvider.mm index cef431a3da9a2..250b9f6fd2e3f 100644 --- a/xbmc/platform/darwin/ios-common/storage/IOSStorageProvider.mm +++ b/xbmc/platform/darwin/ios-common/storage/IOSStorageProvider.mm @@ -18,7 +18,7 @@ return std::make_unique<CIOSStorageProvider>(); } -void CIOSStorageProvider::GetLocalDrives(VECSOURCES& localDrives) +void CIOSStorageProvider::GetLocalDrives(std::vector<CMediaSource>& localDrives) { CMediaSource share; diff --git a/xbmc/platform/darwin/osx/storage/OSXStorageProvider.cpp b/xbmc/platform/darwin/osx/storage/OSXStorageProvider.cpp index 47020128125a9..e9e61d645914d 100644 --- a/xbmc/platform/darwin/osx/storage/OSXStorageProvider.cpp +++ b/xbmc/platform/darwin/osx/storage/OSXStorageProvider.cpp @@ -31,7 +31,7 @@ COSXStorageProvider::COSXStorageProvider() PumpDriveChangeEvents(NULL); } -void COSXStorageProvider::GetLocalDrives(VECSOURCES& localDrives) +void COSXStorageProvider::GetLocalDrives(std::vector<CMediaSource>& localDrives) { CMediaSource share; @@ -93,7 +93,7 @@ void COSXStorageProvider::GetLocalDrives(VECSOURCES& localDrives) } } -void COSXStorageProvider::GetRemovableDrives(VECSOURCES& removableDrives) +void COSXStorageProvider::GetRemovableDrives(std::vector<CMediaSource>& removableDrives) { DASessionRef session = DASessionCreate(kCFAllocatorDefault); if (session) diff --git a/xbmc/platform/darwin/osx/storage/OSXStorageProvider.h b/xbmc/platform/darwin/osx/storage/OSXStorageProvider.h index 26f57fec4b5e2..dd9c6d85f5d9c 100644 --- a/xbmc/platform/darwin/osx/storage/OSXStorageProvider.h +++ b/xbmc/platform/darwin/osx/storage/OSXStorageProvider.h @@ -23,8 +23,8 @@ class COSXStorageProvider : public IStorageProvider void Initialize() override {} void Stop() override {} - void GetLocalDrives(VECSOURCES& localDrives) override; - void GetRemovableDrives(VECSOURCES& removableDrives) override; + void GetLocalDrives(std::vector<CMediaSource>& localDrives) override; + void GetRemovableDrives(std::vector<CMediaSource>& removableDrives) override; std::vector<std::string> GetDiskUsage() override; diff --git a/xbmc/platform/linux/storage/LinuxStorageProvider.cpp b/xbmc/platform/linux/storage/LinuxStorageProvider.cpp index 62064668f9f52..10431d441e4ec 100644 --- a/xbmc/platform/linux/storage/LinuxStorageProvider.cpp +++ b/xbmc/platform/linux/storage/LinuxStorageProvider.cpp @@ -54,7 +54,7 @@ void CLinuxStorageProvider::Stop() m_instance->Stop(); } -void CLinuxStorageProvider::GetLocalDrives(VECSOURCES &localDrives) +void CLinuxStorageProvider::GetLocalDrives(std::vector<CMediaSource>& localDrives) { // Home directory CMediaSource share; @@ -70,7 +70,7 @@ void CLinuxStorageProvider::GetLocalDrives(VECSOURCES &localDrives) m_instance->GetLocalDrives(localDrives); } -void CLinuxStorageProvider::GetRemovableDrives(VECSOURCES &removableDrives) +void CLinuxStorageProvider::GetRemovableDrives(std::vector<CMediaSource>& removableDrives) { m_instance->GetRemovableDrives(removableDrives); } diff --git a/xbmc/platform/linux/storage/LinuxStorageProvider.h b/xbmc/platform/linux/storage/LinuxStorageProvider.h index e1c575fb6e619..5184b21ab20b0 100644 --- a/xbmc/platform/linux/storage/LinuxStorageProvider.h +++ b/xbmc/platform/linux/storage/LinuxStorageProvider.h @@ -20,8 +20,8 @@ class CLinuxStorageProvider : public IStorageProvider void Initialize() override; void Stop() override; - void GetLocalDrives(VECSOURCES &localDrives) override; - void GetRemovableDrives(VECSOURCES &removableDrives) override; + void GetLocalDrives(std::vector<CMediaSource>& localDrives) override; + void GetRemovableDrives(std::vector<CMediaSource>& removableDrives) override; bool Eject(const std::string& mountpath) override; std::vector<std::string> GetDiskUsage() override; bool PumpDriveChangeEvents(IStorageEventsCallback *callback) override; diff --git a/xbmc/platform/linux/storage/UDevProvider.cpp b/xbmc/platform/linux/storage/UDevProvider.cpp index 7123e87a77cef..891bb3f8680aa 100644 --- a/xbmc/platform/linux/storage/UDevProvider.cpp +++ b/xbmc/platform/linux/storage/UDevProvider.cpp @@ -91,7 +91,7 @@ void CUDevProvider::Stop() udev_unref(m_udev); } -void CUDevProvider::GetDisks(VECSOURCES& disks, bool removable) +void CUDevProvider::GetDisks(std::vector<CMediaSource>& disks, bool removable) { // enumerate existing block devices struct udev_enumerate *u_enum = udev_enumerate_new(m_udev); @@ -188,12 +188,12 @@ void CUDevProvider::GetDisks(VECSOURCES& disks, bool removable) udev_enumerate_unref(u_enum); } -void CUDevProvider::GetLocalDrives(VECSOURCES &localDrives) +void CUDevProvider::GetLocalDrives(std::vector<CMediaSource>& localDrives) { GetDisks(localDrives, false); } -void CUDevProvider::GetRemovableDrives(VECSOURCES &removableDrives) +void CUDevProvider::GetRemovableDrives(std::vector<CMediaSource>& removableDrives) { GetDisks(removableDrives, true); } diff --git a/xbmc/platform/linux/storage/UDevProvider.h b/xbmc/platform/linux/storage/UDevProvider.h index a4c83b3472d94..c088db45d06db 100644 --- a/xbmc/platform/linux/storage/UDevProvider.h +++ b/xbmc/platform/linux/storage/UDevProvider.h @@ -25,8 +25,8 @@ class CUDevProvider : public IStorageProvider void Initialize() override; void Stop() override; - void GetLocalDrives(VECSOURCES &localDrives) override; - void GetRemovableDrives(VECSOURCES &removableDrives) override; + void GetLocalDrives(std::vector<CMediaSource>& localDrives) override; + void GetRemovableDrives(std::vector<CMediaSource>& removableDrives) override; bool Eject(const std::string& mountpath) override; @@ -35,7 +35,7 @@ class CUDevProvider : public IStorageProvider bool PumpDriveChangeEvents(IStorageEventsCallback *callback) override; private: - void GetDisks(VECSOURCES& devices, bool removable); + void GetDisks(std::vector<CMediaSource>& devices, bool removable); struct udev *m_udev; struct udev_monitor *m_udevMon; diff --git a/xbmc/platform/linux/storage/UDisks2Provider.cpp b/xbmc/platform/linux/storage/UDisks2Provider.cpp index ee2fc92ac9aa7..ef1c6125fb124 100644 --- a/xbmc/platform/linux/storage/UDisks2Provider.cpp +++ b/xbmc/platform/linux/storage/UDisks2Provider.cpp @@ -318,7 +318,7 @@ std::vector<std::string> CUDisks2Provider::GetDiskUsage() return legacy.GetDiskUsage(); } -void CUDisks2Provider::GetDisks(VECSOURCES &devices, bool enumerateRemovable) +void CUDisks2Provider::GetDisks(std::vector<CMediaSource>& devices, bool enumerateRemovable) { for (const auto &elt: m_filesystems) { diff --git a/xbmc/platform/linux/storage/UDisks2Provider.h b/xbmc/platform/linux/storage/UDisks2Provider.h index 7693dd31ab617..01beea5a2463f 100644 --- a/xbmc/platform/linux/storage/UDisks2Provider.h +++ b/xbmc/platform/linux/storage/UDisks2Provider.h @@ -159,10 +159,10 @@ class CUDisks2Provider : public IStorageProvider std::vector<std::string> GetDiskUsage() override; - void GetLocalDrives(VECSOURCES &localDrives) override + void GetLocalDrives(std::vector<CMediaSource>& localDrives) override { GetDisks(localDrives, false); } - void GetRemovableDrives(VECSOURCES &removableDrives) override + void GetRemovableDrives(std::vector<CMediaSource>& removableDrives) override { GetDisks(removableDrives, true); } void Stop() override @@ -177,7 +177,7 @@ class CUDisks2Provider : public IStorageProvider std::string m_daemonVersion; - void GetDisks(VECSOURCES &devices, bool enumerateRemovable); + void GetDisks(std::vector<CMediaSource>& devices, bool enumerateRemovable); void DriveAdded(Drive *drive); bool DriveRemoved(const std::string& object); diff --git a/xbmc/platform/linux/storage/UDisksProvider.cpp b/xbmc/platform/linux/storage/UDisksProvider.cpp index 083fb8b847fd6..cb75875be361c 100644 --- a/xbmc/platform/linux/storage/UDisksProvider.cpp +++ b/xbmc/platform/linux/storage/UDisksProvider.cpp @@ -391,7 +391,7 @@ std::vector<std::string> CUDisksProvider::EnumerateDisks() return devices; } -void CUDisksProvider::GetDisks(VECSOURCES& devices, bool EnumerateRemovable) +void CUDisksProvider::GetDisks(std::vector<CMediaSource>& devices, bool EnumerateRemovable) { for (auto& itr : m_AvailableDevices) { diff --git a/xbmc/platform/linux/storage/UDisksProvider.h b/xbmc/platform/linux/storage/UDisksProvider.h index 2f0ab8484a49b..60400282b8757 100644 --- a/xbmc/platform/linux/storage/UDisksProvider.h +++ b/xbmc/platform/linux/storage/UDisksProvider.h @@ -101,8 +101,14 @@ class CUDisksProvider : public IStorageProvider void Initialize() override; void Stop() override { } - void GetLocalDrives(VECSOURCES &localDrives) override { GetDisks(localDrives, false); } - void GetRemovableDrives(VECSOURCES &removableDrives) override { GetDisks(removableDrives, true); } + void GetLocalDrives(std::vector<CMediaSource>& localDrives) override + { + GetDisks(localDrives, false); + } + void GetRemovableDrives(std::vector<CMediaSource>& removableDrives) override + { + GetDisks(removableDrives, true); + } bool Eject(const std::string& mountpath) override; @@ -121,7 +127,7 @@ class CUDisksProvider : public IStorageProvider std::vector<std::string> EnumerateDisks(); - void GetDisks(VECSOURCES& devices, bool EnumerateRemovable); + void GetDisks(std::vector<CMediaSource>& devices, bool EnumerateRemovable); int m_DaemonVersion; diff --git a/xbmc/platform/posix/PosixMountProvider.cpp b/xbmc/platform/posix/PosixMountProvider.cpp index e0b679d7930a3..25ec0896a6034 100644 --- a/xbmc/platform/posix/PosixMountProvider.cpp +++ b/xbmc/platform/posix/PosixMountProvider.cpp @@ -25,7 +25,7 @@ void CPosixMountProvider::Initialize() CLog::Log(LOGDEBUG, "Selected Posix mount as storage provider"); } -void CPosixMountProvider::GetDrives(VECSOURCES &drives) +void CPosixMountProvider::GetDrives(std::vector<CMediaSource>& drives) { std::vector<std::string> result; @@ -136,7 +136,7 @@ bool CPosixMountProvider::Eject(const std::string& mountpath) bool CPosixMountProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback) { - VECSOURCES drives; + std::vector<CMediaSource> drives; GetRemovableDrives(drives); bool changed = drives.size() != m_removableLength; m_removableLength = drives.size(); diff --git a/xbmc/platform/posix/PosixMountProvider.h b/xbmc/platform/posix/PosixMountProvider.h index 170c6445c3732..138014b3a19e2 100644 --- a/xbmc/platform/posix/PosixMountProvider.h +++ b/xbmc/platform/posix/PosixMountProvider.h @@ -22,8 +22,9 @@ class CPosixMountProvider : public IStorageProvider void Initialize() override; void Stop() override { } - void GetLocalDrives(VECSOURCES &localDrives) override { GetDrives(localDrives); } - void GetRemovableDrives(VECSOURCES &removableDrives) override { /*GetDrives(removableDrives);*/ } + void GetLocalDrives(std::vector<CMediaSource>& localDrives) override { GetDrives(localDrives); } + void GetRemovableDrives(std::vector<CMediaSource>& removableDrives) override + { /*GetDrives(removableDrives);*/ } std::vector<std::string> GetDiskUsage() override; @@ -31,7 +32,7 @@ class CPosixMountProvider : public IStorageProvider bool PumpDriveChangeEvents(IStorageEventsCallback *callback) override; private: - void GetDrives(VECSOURCES &drives); + void GetDrives(std::vector<CMediaSource>& drives); unsigned int m_removableLength; }; diff --git a/xbmc/platform/win10/storage/Win10StorageProvider.cpp b/xbmc/platform/win10/storage/Win10StorageProvider.cpp index bb3901afac67c..8efc01f274c8f 100644 --- a/xbmc/platform/win10/storage/Win10StorageProvider.cpp +++ b/xbmc/platform/win10/storage/Win10StorageProvider.cpp @@ -44,7 +44,7 @@ CStorageProvider::~CStorageProvider() void CStorageProvider::Initialize() { m_changed = false; - VECSOURCES vShare; + std::vector<CMediaSource> vShare; GetDrivesByType(vShare, DVD_DRIVES); if (!vShare.empty()) CServiceBroker::GetMediaManager().SetHasOpticalDrive(true); @@ -67,7 +67,7 @@ void CStorageProvider::Initialize() m_watcher.Start(); } -void CStorageProvider::GetLocalDrives(VECSOURCES &localDrives) +void CStorageProvider::GetLocalDrives(std::vector<CMediaSource>& localDrives) { CMediaSource share; share.strPath = CSpecialProtocol::TranslatePath("special://home"); @@ -79,7 +79,7 @@ void CStorageProvider::GetLocalDrives(VECSOURCES &localDrives) GetDrivesByType(localDrives, LOCAL_DRIVES, true); } -void CStorageProvider::GetRemovableDrives(VECSOURCES &removableDrives) +void CStorageProvider::GetRemovableDrives(std::vector<CMediaSource>& removableDrives) { using KODI::PLATFORM::WINDOWS::FromW; @@ -124,7 +124,7 @@ void CStorageProvider::GetRemovableDrives(VECSOURCES &removableDrives) std::string CStorageProvider::GetFirstOpticalDeviceFileName() { - VECSOURCES vShare; + std::vector<CMediaSource> vShare; std::string strdevice = "\\\\.\\"; GetDrivesByType(vShare, DVD_DRIVES); @@ -187,7 +187,9 @@ bool CStorageProvider::PumpDriveChangeEvents(IStorageEventsCallback *callback) return res; } -void CStorageProvider::GetDrivesByType(VECSOURCES & localDrives, Drive_Types eDriveType, bool bonlywithmedia) +void CStorageProvider::GetDrivesByType(std::vector<CMediaSource>& localDrives, + Drive_Types eDriveType, + bool bonlywithmedia) { DWORD drivesBits = GetLogicalDrives(); if (drivesBits == 0) diff --git a/xbmc/platform/win10/storage/Win10StorageProvider.h b/xbmc/platform/win10/storage/Win10StorageProvider.h index 51064dec80c69..5bda2f1ea2fc7 100644 --- a/xbmc/platform/win10/storage/Win10StorageProvider.h +++ b/xbmc/platform/win10/storage/Win10StorageProvider.h @@ -23,8 +23,8 @@ class CStorageProvider : public IStorageProvider void Initialize() override; void Stop() override { } - void GetLocalDrives(VECSOURCES &localDrives) override; - void GetRemovableDrives(VECSOURCES &removableDrives) override; + void GetLocalDrives(std::vector<CMediaSource>& localDrives) override; + void GetRemovableDrives(std::vector<CMediaSource>& removableDrives) override; std::string GetFirstOpticalDeviceFileName() override; bool Eject(const std::string& mountpath) override; std::vector<std::string> GetDiskUsage() override; @@ -38,7 +38,9 @@ class CStorageProvider : public IStorageProvider REMOVABLE_DRIVES, DVD_DRIVES }; - static void GetDrivesByType(VECSOURCES &localDrives, Drive_Types eDriveType = ALL_DRIVES, bool bonlywithmedia = false); + static void GetDrivesByType(std::vector<CMediaSource>& localDrives, + Drive_Types eDriveType = ALL_DRIVES, + bool bonlywithmedia = false); winrt::Windows::Devices::Enumeration::DeviceWatcher m_watcher{ nullptr }; std::atomic<bool> m_changed; diff --git a/xbmc/platform/win32/storage/Win32StorageProvider.cpp b/xbmc/platform/win32/storage/Win32StorageProvider.cpp index 9df7b2f48fcb1..f48d9157f8f51 100644 --- a/xbmc/platform/win32/storage/Win32StorageProvider.cpp +++ b/xbmc/platform/win32/storage/Win32StorageProvider.cpp @@ -31,7 +31,7 @@ std::unique_ptr<IStorageProvider> IStorageProvider::CreateInstance() void CWin32StorageProvider::Initialize() { // check for a DVD drive - VECSOURCES vShare; + std::vector<CMediaSource> vShare; GetDrivesByType(vShare, DVD_DRIVES); if(!vShare.empty()) CServiceBroker::GetMediaManager().SetHasOpticalDrive(true); @@ -48,7 +48,7 @@ void CWin32StorageProvider::Initialize() #endif } -void CWin32StorageProvider::GetLocalDrives(VECSOURCES &localDrives) +void CWin32StorageProvider::GetLocalDrives(std::vector<CMediaSource>& localDrives) { CMediaSource share; wchar_t profilePath[MAX_PATH]; @@ -65,14 +65,14 @@ void CWin32StorageProvider::GetLocalDrives(VECSOURCES &localDrives) GetDrivesByType(localDrives, LOCAL_DRIVES); } -void CWin32StorageProvider::GetRemovableDrives(VECSOURCES &removableDrives) +void CWin32StorageProvider::GetRemovableDrives(std::vector<CMediaSource>& removableDrives) { GetDrivesByType(removableDrives, REMOVABLE_DRIVES, true); } std::string CWin32StorageProvider::GetFirstOpticalDeviceFileName() { - VECSOURCES vShare; + std::vector<CMediaSource> vShare; std::string strdevice = "\\\\.\\"; GetDrivesByType(vShare, DVD_DRIVES); @@ -175,7 +175,9 @@ bool CWin32StorageProvider::PumpDriveChangeEvents(IStorageEventsCallback *callba return b; } -void CWin32StorageProvider::GetDrivesByType(VECSOURCES &localDrives, Drive_Types eDriveType, bool bonlywithmedia) +void CWin32StorageProvider::GetDrivesByType(std::vector<CMediaSource>& localDrives, + Drive_Types eDriveType, + bool bonlywithmedia) { using KODI::PLATFORM::WINDOWS::FromW; diff --git a/xbmc/platform/win32/storage/Win32StorageProvider.h b/xbmc/platform/win32/storage/Win32StorageProvider.h index 74fdeccc3a16d..7f6fc34c803cb 100644 --- a/xbmc/platform/win32/storage/Win32StorageProvider.h +++ b/xbmc/platform/win32/storage/Win32StorageProvider.h @@ -31,8 +31,8 @@ class CWin32StorageProvider : public IStorageProvider virtual void Initialize(); virtual void Stop() { } - virtual void GetLocalDrives(VECSOURCES &localDrives); - virtual void GetRemovableDrives(VECSOURCES &removableDrives); + virtual void GetLocalDrives(std::vector<CMediaSource>& localDrives); + virtual void GetRemovableDrives(std::vector<CMediaSource>& removableDrives); virtual std::string GetFirstOpticalDeviceFileName(); virtual bool Eject(const std::string& mountpath); @@ -45,7 +45,9 @@ class CWin32StorageProvider : public IStorageProvider static bool xbevent; private: - static void GetDrivesByType(VECSOURCES &localDrives, Drive_Types eDriveType=ALL_DRIVES, bool bonlywithmedia=false); + static void GetDrivesByType(std::vector<CMediaSource>& localDrives, + Drive_Types eDriveType = ALL_DRIVES, + bool bonlywithmedia = false); static DEVINST GetDrivesDevInstByDiskNumber(long DiskNumber); }; diff --git a/xbmc/profiles/dialogs/GUIDialogProfileSettings.cpp b/xbmc/profiles/dialogs/GUIDialogProfileSettings.cpp index 9f455c4a6b239..31b1d8b98d75c 100644 --- a/xbmc/profiles/dialogs/GUIDialogProfileSettings.cpp +++ b/xbmc/profiles/dialogs/GUIDialogProfileSettings.cpp @@ -225,7 +225,7 @@ void CGUIDialogProfileSettings::OnSettingAction(const std::shared_ptr<const CSet const std::string &settingId = setting->GetId(); if (settingId == SETTING_PROFILE_IMAGE) { - VECSOURCES shares; + std::vector<CMediaSource> shares; CServiceBroker::GetMediaManager().GetLocalDrives(shares); CFileItemList items; @@ -361,7 +361,7 @@ void CGUIDialogProfileSettings::InitializeSettings() bool CGUIDialogProfileSettings::GetProfilePath(std::string &directory, bool isDefault) { - VECSOURCES shares; + std::vector<CMediaSource> shares; CMediaSource share; share.strName = g_localizeStrings.Get(13200); share.strPath = "special://masterprofile/profiles/"; diff --git a/xbmc/programs/GUIViewStatePrograms.cpp b/xbmc/programs/GUIViewStatePrograms.cpp index 0b08a42864278..ffd5596872606 100644 --- a/xbmc/programs/GUIViewStatePrograms.cpp +++ b/xbmc/programs/GUIViewStatePrograms.cpp @@ -51,7 +51,7 @@ std::string CGUIViewStateWindowPrograms::GetExtensions() return ".cut"; } -VECSOURCES& CGUIViewStateWindowPrograms::GetSources() +std::vector<CMediaSource>& CGUIViewStateWindowPrograms::GetSources() { #if defined(TARGET_ANDROID) { @@ -66,7 +66,8 @@ VECSOURCES& CGUIViewStateWindowPrograms::GetSources() } #endif - VECSOURCES *programSources = CMediaSourceSettings::GetInstance().GetSources("programs"); + std::vector<CMediaSource>* programSources = + CMediaSourceSettings::GetInstance().GetSources("programs"); AddOrReplace(*programSources, CGUIViewState::GetSources()); return *programSources; } diff --git a/xbmc/programs/GUIViewStatePrograms.h b/xbmc/programs/GUIViewStatePrograms.h index 38928884b9210..5d28f40208f7f 100644 --- a/xbmc/programs/GUIViewStatePrograms.h +++ b/xbmc/programs/GUIViewStatePrograms.h @@ -19,6 +19,6 @@ class CGUIViewStateWindowPrograms : public CGUIViewState void SaveViewState() override; std::string GetLockType() override; std::string GetExtensions() override; - VECSOURCES& GetSources() override; + std::vector<CMediaSource>& GetSources() override; }; diff --git a/xbmc/programs/GUIWindowPrograms.cpp b/xbmc/programs/GUIWindowPrograms.cpp index 7a57f1cc0ba60..03e7b425b83bb 100644 --- a/xbmc/programs/GUIWindowPrograms.cpp +++ b/xbmc/programs/GUIWindowPrograms.cpp @@ -154,7 +154,7 @@ std::string CGUIWindowPrograms::GetStartFolder(const std::string &dir) return "androidapp://sources/apps/"; SetupShares(); - VECSOURCES shares; + std::vector<CMediaSource> shares; m_rootDir.GetSources(shares); bool bIsSourceName = false; int iIndex = CUtil::GetMatchingSource(dir, shares, bIsSourceName); diff --git a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp index fccedb6c68b99..7cbc953aa0d32 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp @@ -426,7 +426,7 @@ bool CGUIDialogPVRChannelManager::OnClickButtonChannelLogo() items.Add(nothumb); std::string strThumb; - VECSOURCES shares; + std::vector<CMediaSource> shares; const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); if (settings->GetString(CSettings::SETTING_PVRMENU_ICONPATH) != "") { diff --git a/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp b/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp index 866c3987e81dd..6cd9626d56106 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp @@ -231,7 +231,7 @@ bool CPVRGUIActionsEPG::ChooseIconForSavedSearch(const CFileItem& item) items.Add(std::move(nothumb)); std::string icon; - VECSOURCES sources; + std::vector<CMediaSource> sources; CServiceBroker::GetMediaManager().GetLocalDrives(sources); if (!CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(19285), // Browse for icon diff --git a/xbmc/settings/DisplaySettings.cpp b/xbmc/settings/DisplaySettings.cpp index 031ca0be96b30..bda508fb37ccc 100644 --- a/xbmc/settings/DisplaySettings.cpp +++ b/xbmc/settings/DisplaySettings.cpp @@ -228,7 +228,7 @@ void CDisplaySettings::OnSettingAction(const std::shared_ptr<const CSetting>& se if (settingId == "videoscreen.cms3dlut") { std::string path = std::static_pointer_cast<const CSettingString>(setting)->GetValue(); - VECSOURCES shares; + std::vector<CMediaSource> shares; CServiceBroker::GetMediaManager().GetLocalDrives(shares); if (CGUIDialogFileBrowser::ShowAndGetFile(shares, ".3dlut", g_localizeStrings.Get(36580), path)) { @@ -238,7 +238,7 @@ void CDisplaySettings::OnSettingAction(const std::shared_ptr<const CSetting>& se else if (settingId == "videoscreen.displayprofile") { std::string path = std::static_pointer_cast<const CSettingString>(setting)->GetValue(); - VECSOURCES shares; + std::vector<CMediaSource> shares; CServiceBroker::GetMediaManager().GetLocalDrives(shares); if (CGUIDialogFileBrowser::ShowAndGetFile(shares, ".icc|.icm", g_localizeStrings.Get(36581), path)) { diff --git a/xbmc/settings/MediaSettings.cpp b/xbmc/settings/MediaSettings.cpp index 76c97b32895e1..07a7ae95aa4da 100644 --- a/xbmc/settings/MediaSettings.cpp +++ b/xbmc/settings/MediaSettings.cpp @@ -331,7 +331,7 @@ void CMediaSettings::OnSettingAction(const std::shared_ptr<const CSetting>& sett else if (settingId == CSettings::SETTING_MUSICLIBRARY_IMPORT) { std::string path; - VECSOURCES shares; + std::vector<CMediaSource> shares; CServiceBroker::GetMediaManager().GetLocalDrives(shares); CServiceBroker::GetMediaManager().GetNetworkLocations(shares); CServiceBroker::GetMediaManager().GetRemovableDrives(shares); @@ -355,7 +355,7 @@ void CMediaSettings::OnSettingAction(const std::shared_ptr<const CSetting>& sett else if (settingId == CSettings::SETTING_VIDEOLIBRARY_IMPORT) { std::string path; - VECSOURCES shares; + std::vector<CMediaSource> shares; CServiceBroker::GetMediaManager().GetLocalDrives(shares); CServiceBroker::GetMediaManager().GetNetworkLocations(shares); CServiceBroker::GetMediaManager().GetRemovableDrives(shares); diff --git a/xbmc/settings/MediaSourceSettings.cpp b/xbmc/settings/MediaSourceSettings.cpp index b587e1c8cdfda..a22b97f7cabf9 100644 --- a/xbmc/settings/MediaSourceSettings.cpp +++ b/xbmc/settings/MediaSourceSettings.cpp @@ -142,7 +142,7 @@ void CMediaSourceSettings::Clear() m_gameSources.clear(); } -VECSOURCES* CMediaSourceSettings::GetSources(const std::string& type) +std::vector<CMediaSource>* CMediaSourceSettings::GetSources(const std::string& type) { if (type == "programs" || type == "myprograms") return &m_programSources; @@ -192,11 +192,11 @@ bool CMediaSourceSettings::UpdateSource(const std::string& strType, const std::string& strUpdateChild, const std::string& strUpdateValue) { - VECSOURCES* pShares = GetSources(strType); + std::vector<CMediaSource>* pShares = GetSources(strType); if (pShares == NULL) return false; - for (IVECSOURCES it = pShares->begin(); it != pShares->end(); ++it) + for (std::vector<CMediaSource>::iterator it = pShares->begin(); it != pShares->end(); ++it) { if (it->strName == strOldName) { @@ -231,13 +231,13 @@ bool CMediaSourceSettings::DeleteSource(const std::string& strType, const std::string& strPath, bool virtualSource /* = false */) { - VECSOURCES* pShares = GetSources(strType); + std::vector<CMediaSource>* pShares = GetSources(strType); if (pShares == NULL) return false; bool found = false; - for (IVECSOURCES it = pShares->begin(); it != pShares->end(); ++it) + for (std::vector<CMediaSource>::iterator it = pShares->begin(); it != pShares->end(); ++it) { if (it->strName == strName && it->strPath == strPath) { @@ -256,7 +256,7 @@ bool CMediaSourceSettings::DeleteSource(const std::string& strType, bool CMediaSourceSettings::AddShare(const std::string& type, const CMediaSource& share) { - VECSOURCES* pShares = GetSources(type); + std::vector<CMediaSource>* pShares = GetSources(type); if (pShares == NULL) return false; @@ -295,13 +295,13 @@ bool CMediaSourceSettings::UpdateShare(const std::string& type, const std::string& oldName, const CMediaSource& share) { - VECSOURCES* pShares = GetSources(type); + std::vector<CMediaSource>* pShares = GetSources(type); if (pShares == NULL) return false; // update our current share list CMediaSource* pShare = NULL; - for (IVECSOURCES it = pShares->begin(); it != pShares->end(); ++it) + for (std::vector<CMediaSource>::iterator it = pShares->begin(); it != pShares->end(); ++it) { if (it->strName == oldName) { @@ -442,7 +442,7 @@ bool CMediaSourceSettings::GetSource(const std::string& category, void CMediaSourceSettings::GetSources(const tinyxml2::XMLNode* rootElement, const std::string& tagName, - VECSOURCES& items, + std::vector<CMediaSource>& items, std::string& defaultString) { @@ -490,7 +490,7 @@ void CMediaSourceSettings::GetSources(const tinyxml2::XMLNode* rootElement, bool CMediaSourceSettings::SetSources(tinyxml2::XMLNode* root, const char* section, - const VECSOURCES& shares, + const std::vector<CMediaSource>& shares, const std::string& defaultPath) const { auto* doc = root->GetDocument(); @@ -501,7 +501,7 @@ bool CMediaSourceSettings::SetSources(tinyxml2::XMLNode* root, return false; XMLUtils::SetPath(sectionNode, "default", defaultPath); - for (CIVECSOURCES it = shares.begin(); it != shares.end(); ++it) + for (std::vector<CMediaSource>::const_iterator it = shares.begin(); it != shares.end(); ++it) { const CMediaSource& share = *it; if (share.m_ignore) diff --git a/xbmc/settings/MediaSourceSettings.h b/xbmc/settings/MediaSourceSettings.h index df1c15fef0c5f..1bb482b4e6773 100644 --- a/xbmc/settings/MediaSourceSettings.h +++ b/xbmc/settings/MediaSourceSettings.h @@ -36,7 +36,7 @@ class CMediaSourceSettings : public ISettingsHandler bool Save(const std::string &file) const; void Clear(); - VECSOURCES* GetSources(const std::string &type); + std::vector<CMediaSource>* GetSources(const std::string& type); const std::string& GetDefaultSource(const std::string &type) const; void SetDefaultSource(const std::string &type, const std::string &source); @@ -55,19 +55,19 @@ class CMediaSourceSettings : public ISettingsHandler bool GetSource(const std::string& category, const tinyxml2::XMLNode* source, CMediaSource& share); void GetSources(const tinyxml2::XMLNode* rootElement, const std::string& tagName, - VECSOURCES& items, + std::vector<CMediaSource>& items, std::string& defaultString); bool SetSources(tinyxml2::XMLNode* rootNode, const char* section, - const VECSOURCES& shares, + const std::vector<CMediaSource>& shares, const std::string& defaultPath) const; - VECSOURCES m_programSources; - VECSOURCES m_pictureSources; - VECSOURCES m_fileSources; - VECSOURCES m_musicSources; - VECSOURCES m_videoSources; - VECSOURCES m_gameSources; + std::vector<CMediaSource> m_programSources; + std::vector<CMediaSource> m_pictureSources; + std::vector<CMediaSource> m_fileSources; + std::vector<CMediaSource> m_musicSources; + std::vector<CMediaSource> m_videoSources; + std::vector<CMediaSource> m_gameSources; std::string m_defaultProgramSource; std::string m_defaultMusicSource; diff --git a/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp b/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp index bfcca80941455..b4cdf24105306 100644 --- a/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp +++ b/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp @@ -148,7 +148,7 @@ void CGUIDialogLibExportSettings::OnSettingAction(const std::shared_ptr<const CS if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER && !m_settings.IsToLibFolders() && !m_settings.IsArtistFoldersOnly()) { - VECSOURCES shares; + std::vector<CMediaSource> shares; CServiceBroker::GetMediaManager().GetLocalDrives(shares); CServiceBroker::GetMediaManager().GetNetworkLocations(shares); CServiceBroker::GetMediaManager().GetRemovableDrives(shares); diff --git a/xbmc/settings/windows/GUIControlSettings.cpp b/xbmc/settings/windows/GUIControlSettings.cpp index 15e209a8163b6..84d9fe4cad1c0 100644 --- a/xbmc/settings/windows/GUIControlSettings.cpp +++ b/xbmc/settings/windows/GUIControlSettings.cpp @@ -1173,7 +1173,7 @@ bool CGUIControlButtonSetting::GetPath(const std::shared_ptr<CSettingPath>& path std::string path = pathSetting->GetValue(); - VECSOURCES shares; + std::vector<CMediaSource> shares; bool localSharesOnly = false; const std::vector<std::string>& sources = pathSetting->GetSources(); for (const auto& source : sources) @@ -1182,7 +1182,7 @@ bool CGUIControlButtonSetting::GetPath(const std::shared_ptr<CSettingPath>& path localSharesOnly = true; else { - VECSOURCES* sources = CMediaSourceSettings::GetInstance().GetSources(source); + std::vector<CMediaSource>* sources = CMediaSourceSettings::GetInstance().GetSources(source); if (sources != NULL) shares.insert(shares.end(), sources->begin(), sources->end()); } diff --git a/xbmc/storage/IStorageProvider.h b/xbmc/storage/IStorageProvider.h index 13d7fd2cc07f2..7a01cfa128bc2 100644 --- a/xbmc/storage/IStorageProvider.h +++ b/xbmc/storage/IStorageProvider.h @@ -70,8 +70,8 @@ class IStorageProvider virtual void Initialize() = 0; virtual void Stop() = 0; - virtual void GetLocalDrives(VECSOURCES &localDrives) = 0; - virtual void GetRemovableDrives(VECSOURCES &removableDrives) = 0; + virtual void GetLocalDrives(std::vector<CMediaSource>& localDrives) = 0; + virtual void GetRemovableDrives(std::vector<CMediaSource>& removableDrives) = 0; virtual std::string GetFirstOpticalDeviceFileName() { #ifdef HAS_OPTICAL_DRIVE diff --git a/xbmc/storage/MediaManager.cpp b/xbmc/storage/MediaManager.cpp index bf51733424802..53caad980ee7f 100644 --- a/xbmc/storage/MediaManager.cpp +++ b/xbmc/storage/MediaManager.cpp @@ -146,20 +146,20 @@ bool CMediaManager::SaveSources() return doc.SaveFile(MEDIA_SOURCES_XML); } -void CMediaManager::GetLocalDrives(VECSOURCES &localDrives, bool includeQ) +void CMediaManager::GetLocalDrives(std::vector<CMediaSource>& localDrives, bool includeQ) { std::unique_lock<CCriticalSection> lock(m_CritSecStorageProvider); m_platformStorage->GetLocalDrives(localDrives); } -void CMediaManager::GetRemovableDrives(VECSOURCES &removableDrives) +void CMediaManager::GetRemovableDrives(std::vector<CMediaSource>& removableDrives) { std::unique_lock<CCriticalSection> lock(m_CritSecStorageProvider); if (m_platformStorage) m_platformStorage->GetRemovableDrives(removableDrives); } -void CMediaManager::GetNetworkLocations(VECSOURCES &locations, bool autolocations) +void CMediaManager::GetNetworkLocations(std::vector<CMediaSource>& locations, bool autolocations) { for (unsigned int i = 0; i < m_locations.size(); i++) { @@ -609,7 +609,7 @@ std::string CMediaManager::GetDiscPath() #else std::unique_lock<CCriticalSection> lock(m_CritSecStorageProvider); - VECSOURCES drives; + std::vector<CMediaSource> drives; m_platformStorage->GetRemovableDrives(drives); for(unsigned i = 0; i < drives.size(); ++i) { diff --git a/xbmc/storage/MediaManager.h b/xbmc/storage/MediaManager.h index c64c10be217be..8c53771de3d7f 100644 --- a/xbmc/storage/MediaManager.h +++ b/xbmc/storage/MediaManager.h @@ -9,7 +9,7 @@ #pragma once #include "IStorageProvider.h" -#include "MediaSource.h" // for VECSOURCES +#include "MediaSource.h" // for std::vector<CMediaSource> #include "storage/discs/IDiscDriveHandler.h" #include "threads/CriticalSection.h" #include "utils/DiscsUtils.h" @@ -42,9 +42,9 @@ class CMediaManager : public IStorageEventsCallback, public IJobCallback bool LoadSources(); bool SaveSources(); - void GetLocalDrives(VECSOURCES &localDrives, bool includeQ = true); - void GetRemovableDrives(VECSOURCES &removableDrives); - void GetNetworkLocations(VECSOURCES &locations, bool autolocations = true); + void GetLocalDrives(std::vector<CMediaSource>& localDrives, bool includeQ = true); + void GetRemovableDrives(std::vector<CMediaSource>& removableDrives); + void GetNetworkLocations(std::vector<CMediaSource>& locations, bool autolocations = true); bool AddNetworkLocation(const std::string &path); bool HasLocation(const std::string& path) const; diff --git a/xbmc/utils/FileUtils.cpp b/xbmc/utils/FileUtils.cpp index fd4cd7596f145..806c649fa51f6 100644 --- a/xbmc/utils/FileUtils.cpp +++ b/xbmc/utils/FileUtils.cpp @@ -145,7 +145,7 @@ bool CFileUtils::RemoteAccessAllowed(const std::string &strPath) // Check manually added sources (held in sources.xml) for (const std::string& sourceName : SourceNames) { - VECSOURCES* sources = CMediaSourceSettings::GetInstance().GetSources(sourceName); + std::vector<CMediaSource>* sources = CMediaSourceSettings::GetInstance().GetSources(sourceName); int sourceIndex = CUtil::GetMatchingSource(realPath, *sources, isSource); if (sourceIndex >= 0 && sourceIndex < static_cast<int>(sources->size()) && sources->at(sourceIndex).m_iHasLock != LOCK_STATE_LOCKED && @@ -153,7 +153,7 @@ bool CFileUtils::RemoteAccessAllowed(const std::string &strPath) return true; } // Check auto-mounted sources - VECSOURCES sources; + std::vector<CMediaSource> sources; CServiceBroker::GetMediaManager().GetRemovableDrives( sources); // Sources returned always have m_allowsharing = true //! @todo Make sharing of auto-mounted sources user configurable diff --git a/xbmc/video/GUIViewStateVideo.cpp b/xbmc/video/GUIViewStateVideo.cpp index 52cf65d719884..ee20ba1e7f186 100644 --- a/xbmc/video/GUIViewStateVideo.cpp +++ b/xbmc/video/GUIViewStateVideo.cpp @@ -45,7 +45,7 @@ PLAYLIST::Id CGUIViewStateWindowVideo::GetPlaylist() const return PLAYLIST::Id::TYPE_VIDEO; } -VECSOURCES& CGUIViewStateWindowVideo::GetSources() +std::vector<CMediaSource>& CGUIViewStateWindowVideo::GetSources() { AddLiveTVSources(); return CGUIViewState::GetSources(); @@ -383,7 +383,7 @@ void CGUIViewStateWindowVideoNav::SaveViewState() } } -VECSOURCES& CGUIViewStateWindowVideoNav::GetSources() +std::vector<CMediaSource>& CGUIViewStateWindowVideoNav::GetSources() { // Setup shares we want to have m_sources.clear(); @@ -442,7 +442,7 @@ bool CGUIViewStateWindowVideoPlaylist::HideParentDirItems() return true; } -VECSOURCES& CGUIViewStateWindowVideoPlaylist::GetSources() +std::vector<CMediaSource>& CGUIViewStateWindowVideoPlaylist::GetSources() { m_sources.clear(); // Playlist share diff --git a/xbmc/video/GUIViewStateVideo.h b/xbmc/video/GUIViewStateVideo.h index 5cf2cdcde2d2b..862db52a7621e 100644 --- a/xbmc/video/GUIViewStateVideo.h +++ b/xbmc/video/GUIViewStateVideo.h @@ -16,7 +16,7 @@ class CGUIViewStateWindowVideo : public CGUIViewState explicit CGUIViewStateWindowVideo(const CFileItemList& items) : CGUIViewState(items) {} protected: - VECSOURCES& GetSources() override; + std::vector<CMediaSource>& GetSources() override; std::string GetLockType() override; KODI::PLAYLIST::Id GetPlaylist() const override; std::string GetExtensions() override; @@ -40,7 +40,7 @@ class CGUIViewStateWindowVideoNav : public CGUIViewStateWindowVideo protected: void SaveViewState() override; - VECSOURCES& GetSources() override; + std::vector<CMediaSource>& GetSources() override; }; class CGUIViewStateWindowVideoPlaylist : public CGUIViewStateWindowVideo @@ -52,7 +52,7 @@ class CGUIViewStateWindowVideoPlaylist : public CGUIViewStateWindowVideo void SaveViewState() override; bool HideExtensions() override; bool HideParentDirItems() override; - VECSOURCES& GetSources() override; + std::vector<CMediaSource>& GetSources() override; bool AutoPlayNextItem() override { return false; } }; diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index f333bb334bf5e..3907532fdf648 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -10059,7 +10059,8 @@ void CVideoDatabase::CleanDatabase(CGUIDialogProgressBarHandle* handle, if (m_pDS2->num_rows() > 0) { std::string filesToTestForDelete; - VECSOURCES videoSources(*CMediaSourceSettings::GetInstance().GetSources("video")); + std::vector<CMediaSource> videoSources( + *CMediaSourceSettings::GetInstance().GetSources("video")); CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources); int total = m_pDS2->num_rows(); @@ -10458,7 +10459,7 @@ std::vector<int> CVideoDatabase::CleanMediaType(const std::string &mediaType, co parentPathIdField.c_str(), table.c_str(), cleanableFileIDs.c_str()); - VECSOURCES videoSources(*CMediaSourceSettings::GetInstance().GetSources("video")); + std::vector<CMediaSource> videoSources(*CMediaSourceSettings::GetInstance().GetSources("video")); CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources); // map of parent path ID to boolean pair (if not exists and user choice) diff --git a/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp b/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp index 171c69a294bfe..8ee9cffdd1823 100644 --- a/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp +++ b/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp @@ -146,7 +146,7 @@ std::string CGUIDialogSubtitleSettings::BrowseForSubtitle() strMask += extras; - VECSOURCES shares(*CMediaSourceSettings::GetInstance().GetSources("video")); + std::vector<CMediaSource> shares(*CMediaSourceSettings::GetInstance().GetSources("video")); if (CMediaSettings::GetInstance().GetAdditionalSubtitleDirectoryChecked() != -1 && !CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SUBTITLES_CUSTOMPATH).empty()) { CMediaSource share; diff --git a/xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp b/xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp index 4795a70500878..c3eb06c50b1a2 100644 --- a/xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoManagerExtras.cpp @@ -110,7 +110,7 @@ bool CGUIDialogVideoManagerExtras::AddVideoExtra() const MediaType mediaType{m_videoAsset->GetVideoInfoTag()->m_type}; // prompt to choose a video file - VECSOURCES sources{*CMediaSourceSettings::GetInstance().GetSources("files")}; + std::vector<CMediaSource> sources{*CMediaSourceSettings::GetInstance().GetSources("files")}; CServiceBroker::GetMediaManager().GetLocalDrives(sources); CServiceBroker::GetMediaManager().GetNetworkLocations(sources); diff --git a/xbmc/video/dialogs/GUIDialogVideoManagerVersions.cpp b/xbmc/video/dialogs/GUIDialogVideoManagerVersions.cpp index 49fad7c9ffecc..da8b1ab2c2f0e 100644 --- a/xbmc/video/dialogs/GUIDialogVideoManagerVersions.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoManagerVersions.cpp @@ -479,7 +479,7 @@ bool CGUIDialogVideoManagerVersions::AddVideoVersionFilePicker() const MediaType mediaType{m_videoAsset->GetVideoInfoTag()->m_type}; // prompt to choose a video file - VECSOURCES sources{*CMediaSourceSettings::GetInstance().GetSources("files")}; + std::vector<CMediaSource> sources{*CMediaSourceSettings::GetInstance().GetSources("files")}; CServiceBroker::GetMediaManager().GetLocalDrives(sources); CServiceBroker::GetMediaManager().GetNetworkLocations(sources); diff --git a/xbmc/video/windows/GUIWindowVideoNav.h b/xbmc/video/windows/GUIWindowVideoNav.h index 7918ee4b50fd5..49e887df1c532 100644 --- a/xbmc/video/windows/GUIWindowVideoNav.h +++ b/xbmc/video/windows/GUIWindowVideoNav.h @@ -56,7 +56,7 @@ class CGUIWindowVideoNav : public CGUIWindowVideoBase bool OnClick(int iItem, const std::string &player = "") override; std::string GetStartFolder(const std::string &dir) override; - VECSOURCES m_shares; + std::vector<CMediaSource> m_shares; private: virtual SelectFirstUnwatchedItem GetSettingSelectFirstUnwatchedItem(); diff --git a/xbmc/video/windows/GUIWindowVideoPlaylist.h b/xbmc/video/windows/GUIWindowVideoPlaylist.h index 3bf3ef37d9799..94ade0d2c6886 100644 --- a/xbmc/video/windows/GUIWindowVideoPlaylist.h +++ b/xbmc/video/windows/GUIWindowVideoPlaylist.h @@ -40,5 +40,5 @@ class CGUIWindowVideoPlaylist : public CGUIWindowVideoBase void SavePlayList(); int m_movingFrom; - VECSOURCES m_shares; + std::vector<CMediaSource> m_shares; }; diff --git a/xbmc/view/GUIViewState.cpp b/xbmc/view/GUIViewState.cpp index b60f43d64edcc..aa0dc9749ea6d 100644 --- a/xbmc/view/GUIViewState.cpp +++ b/xbmc/view/GUIViewState.cpp @@ -52,7 +52,7 @@ using namespace ADDON; using namespace PVR; std::string CGUIViewState::m_strPlaylistDirectory; -VECSOURCES CGUIViewState::m_sources; +std::vector<CMediaSource> CGUIViewState::m_sources; CGUIViewState* CGUIViewState::GetViewState(int windowId, const CFileItemList& items) { @@ -462,15 +462,15 @@ std::string CGUIViewState::GetExtensions() return ""; } -VECSOURCES& CGUIViewState::GetSources() +std::vector<CMediaSource>& CGUIViewState::GetSources() { return m_sources; } void CGUIViewState::AddLiveTVSources() { - VECSOURCES *sources = CMediaSourceSettings::GetInstance().GetSources("video"); - for (IVECSOURCES it = sources->begin(); it != sources->end(); ++it) + std::vector<CMediaSource>* sources = CMediaSourceSettings::GetInstance().GetSources("video"); + for (std::vector<CMediaSource>::iterator it = sources->begin(); it != sources->end(); ++it) { if (URIUtils::IsLiveTV((*it).strPath)) { diff --git a/xbmc/view/GUIViewState.h b/xbmc/view/GUIViewState.h index e995a3b8b8e13..4016e243c3638 100644 --- a/xbmc/view/GUIViewState.h +++ b/xbmc/view/GUIViewState.h @@ -58,7 +58,7 @@ class CGUIViewState virtual std::string GetLockType(); virtual std::string GetExtensions(); - virtual VECSOURCES& GetSources(); + virtual std::vector<CMediaSource>& GetSources(); protected: explicit CGUIViewState(const CFileItemList& items); // no direct object creation, use GetViewState() @@ -93,7 +93,7 @@ class CGUIViewState std::vector<GUIViewSortDetails> m_sortMethods; int m_currentSortMethod; - static VECSOURCES m_sources; + static std::vector<CMediaSource> m_sources; static std::string m_strPlaylistDirectory; }; diff --git a/xbmc/windows/GUIMediaWindow.cpp b/xbmc/windows/GUIMediaWindow.cpp index 9c1f8e48dfd47..29746e81f15da 100644 --- a/xbmc/windows/GUIMediaWindow.cpp +++ b/xbmc/windows/GUIMediaWindow.cpp @@ -866,7 +866,7 @@ bool CGUIMediaWindow::Update(const std::string &strDirectory, bool updateFilterP if (m_vecItems->GetLabel().empty()) { // Removable sources - VECSOURCES removables; + std::vector<CMediaSource> removables; CServiceBroker::GetMediaManager().GetRemovableDrives(removables); for (const auto& s : removables) { diff --git a/xbmc/windows/GUIWindowFileManager.cpp b/xbmc/windows/GUIWindowFileManager.cpp index 5c288c0209a38..0dd2f3418b39c 100644 --- a/xbmc/windows/GUIWindowFileManager.cpp +++ b/xbmc/windows/GUIWindowFileManager.cpp @@ -1301,7 +1301,7 @@ void CGUIWindowFileManager::SetInitialPath(const std::string &path) m_Directory[0]->SetPath(""); bool bIsSourceName = false; - VECSOURCES shares; + std::vector<CMediaSource> shares; m_rootDir.GetSources(shares); int iIndex = CUtil::GetMatchingSource(strDestination, shares, bIsSourceName); if (iIndex > -1 From ca63e568cf6b5d44652c1b187088c3a8f9675d0d Mon Sep 17 00:00:00 2001 From: boogie <boogiepop@gmx.com> Date: Sat, 13 Jan 2024 21:48:53 +0100 Subject: [PATCH 538/651] VideoLayerBridgeDRMPRIME: Use crop fields to render the picture offsets Hardware decoders when used with AFBC compression, may output picture with offsets which may be different for each frame. Since this offset is applied after the decompression is done, only way to represent this in SRC_X and SRC_Y plane props. This commits utilizes AVFrame crop fields to pass picture offsets. --- xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h | 2 ++ .../VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp | 2 ++ .../cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h | 2 ++ .../DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp | 2 ++ .../HwDecRender/VideoLayerBridgeDRMPRIME.cpp | 11 ++++++----- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h index b83ee8ca68a5f..dca6e821775d0 100644 --- a/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h +++ b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h @@ -54,6 +54,8 @@ class CVideoBufferDRMPRIME : public CVideoBuffer virtual const VideoPicture& GetPicture() const { return m_picture; } virtual uint32_t GetWidth() const { return GetPicture().iWidth; } virtual uint32_t GetHeight() const { return GetPicture().iHeight; } + virtual uint32_t GetXOffset() const { return GetPicture().m_xOffset; } + virtual uint32_t GetYOffset() const { return GetPicture().m_yOffset; } virtual AVDRMFrameDescriptor* GetDescriptor() const = 0; virtual bool IsValid() const { return true; } diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp index 495d6a25f5813..a5468d12a0986 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp @@ -58,6 +58,8 @@ void VideoPicture::Reset() iWidth = 0; iHeight = 0; + m_xOffset = 0; + m_yOffset = 0; iDisplayWidth = 0; iDisplayHeight = 0; } diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h index ca83b1a04fb47..f3373612e876e 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h @@ -75,6 +75,8 @@ struct VideoPicture unsigned int iWidth; unsigned int iHeight; + unsigned int m_xOffset{0}; + unsigned int m_yOffset{0}; unsigned int iDisplayWidth; //< width of the picture without black bars unsigned int iDisplayHeight; //< height of the picture without black bars diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp index eb2943bb8ccb9..0d407043dda64 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp @@ -505,6 +505,8 @@ void CDVDVideoCodecDRMPRIME::SetPictureParams(VideoPicture* pVideoPicture) { pVideoPicture->iWidth = m_pFrame->width; pVideoPicture->iHeight = m_pFrame->height; + pVideoPicture->m_xOffset = m_pFrame->crop_left; + pVideoPicture->m_yOffset = m_pFrame->crop_top; double aspect_ratio = 0; AVRational pixel_aspect = m_pFrame->sample_aspect_ratio; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp index 34d1ab6235591..33db29b1408c2 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp @@ -118,9 +118,10 @@ bool CVideoLayerBridgeDRMPRIME::Map(CVideoBufferDRMPRIME* buffer) flags = DRM_MODE_FB_MODIFIERS; // add the video frame FB - ret = drmModeAddFB2WithModifiers(m_DRM->GetFileDescriptor(), buffer->GetWidth(), - buffer->GetHeight(), layer->format, handles, pitches, offsets, - modifier, &buffer->m_fb_id, flags); + ret = drmModeAddFB2WithModifiers(m_DRM->GetFileDescriptor(), + buffer->GetWidth() + buffer->GetXOffset(), + buffer->GetHeight() + buffer->GetYOffset(), layer->format, + handles, pitches, offsets, modifier, &buffer->m_fb_id, flags); if (ret < 0) { CLog::Log(LOGERROR, "CVideoLayerBridgeDRMPRIME::{} - failed to add fb {}, ret = {}", @@ -188,8 +189,8 @@ void CVideoLayerBridgeDRMPRIME::SetVideoPlane(CVideoBufferDRMPRIME* buffer, cons auto plane = m_DRM->GetVideoPlane(); m_DRM->AddProperty(plane, "FB_ID", buffer->m_fb_id); m_DRM->AddProperty(plane, "CRTC_ID", m_DRM->GetCrtc()->GetCrtcId()); - m_DRM->AddProperty(plane, "SRC_X", 0); - m_DRM->AddProperty(plane, "SRC_Y", 0); + m_DRM->AddProperty(plane, "SRC_X", buffer->GetXOffset() << 16); + m_DRM->AddProperty(plane, "SRC_Y", buffer->GetYOffset() << 16); m_DRM->AddProperty(plane, "SRC_W", buffer->GetWidth() << 16); m_DRM->AddProperty(plane, "SRC_H", buffer->GetHeight() << 16); m_DRM->AddProperty(plane, "CRTC_X", static_cast<int32_t>(destRect.x1) & ~1); From 83aa0749f36f38ccc946811230d37d5d0e104d18 Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Fri, 11 Oct 2024 10:45:32 +0200 Subject: [PATCH 539/651] [GUI][Skin] Video/Audio/Subtitle stream selection dialog --- .../resources/strings.po | 9 +- .../resource.language.en_gb/strings.po | 17 +- .../media/icons/menumarks/star.png | Bin 0 -> 467 bytes .../media/icons/menumarks/tick.png | Bin 0 -> 267 bytes addons/skin.estuary/xml/DialogSelect.xml | 5 +- .../xml/Includes_DialogSelect.xml | 544 ++++++++++++++++++ .../xml/Includes_SettingsDialog.xml | 18 +- addons/skin.estuary/xml/Variables.xml | 33 ++ xbmc/addons/interfaces/gui/GUITranslator.cpp | 6 + .../include/kodi/c-api/gui/input/action_ids.h | 9 + xbmc/cores/VideoPlayer/Interface/StreamInfo.h | 8 +- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 2 + xbmc/cores/VideoPlayer/VideoPlayer.h | 4 +- xbmc/guilib/GUIWindowManager.cpp | 6 + xbmc/guilib/WindowIDs.h | 4 + xbmc/input/WindowTranslator.cpp | 3 + xbmc/input/actions/ActionIDs.h | 9 + xbmc/input/actions/ActionTranslator.cpp | 3 + xbmc/video/PlayerController.cpp | 19 + xbmc/video/dialogs/GUIDialogAudioSettings.cpp | 7 +- .../dialogs/GUIDialogSubtitleSettings.cpp | 2 + xbmc/video/guilib/CMakeLists.txt | 2 + xbmc/video/guilib/VideoStreamSelectHelper.cpp | 517 +++++++++++++++++ xbmc/video/guilib/VideoStreamSelectHelper.h | 29 + 24 files changed, 1237 insertions(+), 19 deletions(-) create mode 100644 addons/skin.estuary/media/icons/menumarks/star.png create mode 100644 addons/skin.estuary/media/icons/menumarks/tick.png create mode 100644 xbmc/video/guilib/VideoStreamSelectHelper.cpp create mode 100644 xbmc/video/guilib/VideoStreamSelectHelper.h diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 46d1d6174f504..2ec1005854b51 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -2196,6 +2196,10 @@ msgctxt "#459" msgid "Subs" msgstr "" +#: xbmc/video/PlayerController.cpp +#: xbmc/video/dialogs/GUIDialogAudioSettings.cpp +#: addons/skin.estuary/xml/DialogPlayerProcessInfo.xml +#: xbmc/video/guilib/VideoStreamSelectHelper.cpp msgctxt "#460" msgid "Audio stream" msgstr "" @@ -2204,8 +2208,10 @@ msgctxt "#461" msgid "[active]" msgstr "" +#: xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp +#: xbmc/video/guilib/VideoStreamSelectHelper.cpp msgctxt "#462" -msgid "Subtitle" +msgid "Subtitle stream" msgstr "" msgctxt "#463" @@ -22827,6 +22833,7 @@ msgstr "" #. Label for an option to select the video stream to play if current video has more than one video stream #: xbmc/video/dialogs/GUIDialogVideoSettings.cpp #: xbmc/video/PlayerController.cpp +#: xbmc/video/guilib/VideoStreamSelectHelper.cpp msgctxt "#38031" msgid "Video stream" msgstr "" diff --git a/addons/skin.estuary/language/resource.language.en_gb/strings.po b/addons/skin.estuary/language/resource.language.en_gb/strings.po index 229f0b95e51b8..d6f2bc6c04b8b 100644 --- a/addons/skin.estuary/language/resource.language.en_gb/strings.po +++ b/addons/skin.estuary/language/resource.language.en_gb/strings.po @@ -502,7 +502,12 @@ msgctxt "#31107" msgid "WideList" msgstr "" -#empty strings from id 31108 to 31109 +#empty strings from id 31108 to 31108 + +#: /xml/VideoOSD.xml +msgctxt "#31109" +msgid "Video streams" +msgstr "" #: /xml/Home.xml msgctxt "#31110" @@ -516,7 +521,7 @@ msgstr "" #: /xml/VideoOSD.xml msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" #: /xml/Custom_1107_SearchDialog.xml @@ -531,7 +536,7 @@ msgstr "" #: /xml/VideoOSD.xml msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" #: /xml/Includes_Home.xml @@ -924,3 +929,9 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "" + +#: /xml/Includes_DialogSelect.xml +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/media/icons/menumarks/star.png b/addons/skin.estuary/media/icons/menumarks/star.png new file mode 100644 index 0000000000000000000000000000000000000000..da55f2ea2a95fdd0ed91a6254f7d5fc43cb726ba GIT binary patch literal 467 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dybpbvhu0Z<#z=8Mc+9m;Apji^+ z7t9cBdgI6W`R%o3cCzd&e?C1w5aDL6F7oEly-PbcP4CI(U7nvDZ>Xos_koK;0jTM= zr;B5V#p$&ZuM0IR2rzVd=xqFcWK!I_|Mdo3DMgDuJh3x;V^qv=bCyBwSx3GAiDt$G z12&NboIL+Hm3Jx@EMH*G@>0ZLul<c+fxTND+2-svyV3n%RZg_E=d_Lm=jyDd?b$7M zs=4XqpOflL=i@GK6aRNSW4hf|j%Q)#OWz2#ZaTcLI#HclX3DMbNpHgcGYZOzH8LB^ zTn*nS&~q|6#OuMS=EMu#Q?0*GZ?V^{pZ&*T?d0=17N0L_Tk7w>!2CPGMx=7ZcE*Sf uChmq`KU^FcKg9pI!aHT*oMY;@Kd|nW*H`$$WRL*#CWEJ|pUXO@geCx-)CW!g literal 0 HcmV?d00001 diff --git a/addons/skin.estuary/media/icons/menumarks/tick.png b/addons/skin.estuary/media/icons/menumarks/tick.png new file mode 100644 index 0000000000000000000000000000000000000000..c5a89ca6f8f1bd8b1e28f9a3921a27645278a40d GIT binary patch literal 267 zcmV+m0rdWfP)<h;3K|Lk000e1NJLTq001BW001Be1ONa4*>kdg0002eNkl<ZNXPA! zJqiLb5JnRVLDXi!TCLY>cnPmysW!QVr5?y~#mZJe5Ill{&q85$viULCC^)J1OWrGz z$ws3-tA_dueoxR;xoU}olrdlr92u2a8h+qe7xHvLi34m>1rmo-nqX+*F!c$#%$d=S z;KsL!J$3*TWbffp(0SoiMFf|EiC9>W#c?SZTZ^}G+}?vVp)#%$lAoGY8eTs}pThWh zq#n5x#@}SF&{mjGm%8Js!i3x0H=$W_=f)y-Pjuj6>8q<c$9_@z|1y1n-~qA?#f-^* R9R~ma002ovPDHLkV1f=Qa*+T4 literal 0 HcmV?d00001 diff --git a/addons/skin.estuary/xml/DialogSelect.xml b/addons/skin.estuary/xml/DialogSelect.xml index 308c14b743e3d..cafeb379982cb 100644 --- a/addons/skin.estuary/xml/DialogSelect.xml +++ b/addons/skin.estuary/xml/DialogSelect.xml @@ -4,7 +4,10 @@ <include>Animation_DialogPopupOpenClose</include> <depth>DepthOSD</depth> <controls> - <include condition="![Window.IsActive(selectvideoversion) | Window.IsActive(selectvideoextra) | Window.IsActive(gamesaves) | Window.IsActive(gamestretchmode) | Window.IsActive(gamevideofilter) | Window.IsActive(gamevideorotation) | Window.IsActive(ingamesaves)]">DefaultDialogSelectLayout</include> + <include condition="![Window.IsActive(videoselectdialog) | Window.IsActive(audioselectdialog) | Window.IsActive(subtitleselectdialog) | Window.IsActive(selectvideoversion) | Window.IsActive(selectvideoextra) | Window.IsActive(gamesaves) | Window.IsActive(gamestretchmode) | Window.IsActive(gamevideofilter) | Window.IsActive(gamevideorotation) | Window.IsActive(ingamesaves)]">DefaultDialogSelectLayout</include> + <include condition="Window.IsActive(videoselectdialog)">VideoDialogSelectVideoLayout</include> + <include condition="Window.IsActive(audioselectdialog)">VideoDialogSelectAudioLayout</include> + <include condition="Window.IsActive(subtitleselectdialog)">VideoDialogSelectSubtitleLayout</include> <include condition="Window.IsActive(gamesaves)">GameDialogSelectSaveLayout</include> <include condition="Window.IsActive(gamevideofilter)">GameDialogSelectFilterLayout</include> <include condition="Window.IsActive(gamestretchmode)">GameDialogSelectViewLayout</include> diff --git a/addons/skin.estuary/xml/Includes_DialogSelect.xml b/addons/skin.estuary/xml/Includes_DialogSelect.xml index 7ca4f0d459923..72049e69dd838 100644 --- a/addons/skin.estuary/xml/Includes_DialogSelect.xml +++ b/addons/skin.estuary/xml/Includes_DialogSelect.xml @@ -194,6 +194,550 @@ </control> </control> </include> + <include name="VideoDialogSelectVideoLayout"> + <!-- + Available ListItem video stream properties: + stream.description, stream.codec, stream.language, stream.resolution, stream.bitrate, + stream.fps, stream.is3d, stream.stereomode, stream.hdrtype, stream.isdefault, stream.isforced, + stream.ishearingimpaired, stream.isvisualimpaired + --> + <control type="group"> + <centertop>50%</centertop> + <centerleft>50%</centerleft> + <height>750</height> + <width>1220</width> + <include content="DialogBackgroundCommons"> + <param name="width" value="1220" /> + <param name="height" value="750" /> + <param name="header_label" value="" /> + <param name="header_id" value="1" /> + </include> + <control type="image"> + <left>0</left> + <top>80</top> + <width>920</width> + <bottom>2</bottom> + <texture border="40">buttons/dialogbutton-nofo.png</texture> + </control> + <control type="list" id="3"> + <left>20</left> + <top>100</top> + <width>880</width> + <bottom>20</bottom> + <onup>3</onup> + <ondown>3</ondown> + <onleft>9001</onleft> + <onright>61</onright> + <pagecontrol>61</pagecontrol> + <scrolltime>200</scrolltime> + <include content="DefaultSimpleListLayout"> + <param name="width" value="880" /> + <param name="list_id" value="3" /> + </include> + </control> + <control type="list" id="6"> + <left>20</left> + <top>100</top> + <width>880</width> + <bottom>20</bottom> + <onup>6</onup> + <ondown>6</ondown> + <onleft>9001</onleft> + <onright>61</onright> + <pagecontrol>61</pagecontrol> + <scrolltime>200</scrolltime> + <itemlayout height="100" width="880"> + <control type="image"> + <left>10</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/tick.png</texture> + <visible>ListItem.IsSelected</visible> + </control> + <control type="label"> + <left>50</left> + <top>0</top> + <right>80</right> + <height>60</height> + <font>font14</font> + <aligny>center</aligny> + <label>$INFO[ListItem.Property(stream.codec)]$INFO[ListItem.Property(stream.resolution),$COMMA ]$INFO[ListItem.Property(stream.bitrate),$COMMA , kbps]$INFO[ListItem.Property(stream.fps),$COMMA , fps]$VAR[VideoStreamDialogVideoItemLabelVar, - [LIGHT],[/LIGHT]]</label> + </control> + <control type="textbox"> + <left>50</left> + <top>50</top> + <right>80</right> + <height>67</height> + <font>font12</font> + <textcolor>grey</textcolor> + <label>$INFO[ListItem.Property(stream.description),, ]$INFO[ListItem.Property(stream.language),(,)]</label> + </control> + <control type="image"> + <left>830</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/star.png</texture> + <visible>ListItem.Property(stream.isdefault)</visible> + </control> + </itemlayout> + <focusedlayout height="100" width="880"> + <control type="image"> + <left>0</left> + <top>0</top> + <right>0</right> + <bottom>0</bottom> + <texture colordiffuse="button_focus">lists/focus.png</texture> + <animation effect="fade" start="100" end="50" time="0">UnFocus</animation> + </control> + <control type="image"> + <left>10</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/tick.png</texture> + <visible>ListItem.IsSelected</visible> + </control> + <control type="label"> + <left>50</left> + <top>0</top> + <right>80</right> + <height>60</height> + <aligny>center</aligny> + <scroll>true</scroll> + <font>font14</font> + <label>$INFO[ListItem.Property(stream.codec)]$INFO[ListItem.Property(stream.resolution),$COMMA ]$INFO[ListItem.Property(stream.bitrate),$COMMA , kbps]$INFO[ListItem.Property(stream.fps),$COMMA , fps]$VAR[VideoStreamDialogVideoItemLabelVar, - [LIGHT],[/LIGHT]]</label> + </control> + <control type="textbox"> + <left>50</left> + <top>50</top> + <right>80</right> + <height>67</height> + <font>font12</font> + <label>$INFO[ListItem.Property(stream.description),, ]$INFO[ListItem.Property(stream.language),(,)]</label> + </control> + <control type="image"> + <left>830</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/star.png</texture> + <visible>ListItem.Property(stream.isdefault)</visible> + </control> + </focusedlayout> + </control> + <control type="scrollbar" id="61"> + <left>910</left> + <top>100</top> + <width>12</width> + <bottom>20</bottom> + <onleft condition="Control.IsVisible(3)">3</onleft> + <onleft condition="Control.IsVisible(6)">6</onleft> + <onright>9001</onright> + <orientation>vertical</orientation> + </control> + <control type="label"> + <left>925</left> + <bottom>10</bottom> + <width>275</width> + <height>35</height> + <font>font12</font> + <align>right</align> + <textcolor>grey</textcolor> + <label>$VAR[SelectLabel]</label> + </control> + <control type="grouplist" id="9001"> + <left>920</left> + <top>80</top> + <onleft>61</onleft> + <itemgap>dialogbuttons_itemgap</itemgap> + <onright>3</onright> + <include content="DefaultDialogButton"> + <param name="id" value="5" /> + <param name="label" value="" /> + </include> + <include content="DefaultDialogButton"> + <param name="id" value="8" /> + <param name="label" value="" /> + </include> + <include content="DefaultDialogButton"> + <param name="id" value="7" /> + <param name="label" value="$LOCALIZE[222]" /> + </include> + </control> + </control> + </include> + <include name="VideoDialogSelectAudioLayout"> + <!-- + Available ListItem audio stream properties: + stream.description, stream.codec, stream.codecdesc, stream.channels, + stream.isdefault, stream.isforced, stream.isoriginal, stream.ishearingimpaired, stream.isvisualimpaired + --> + <control type="group"> + <centertop>50%</centertop> + <centerleft>50%</centerleft> + <height>750</height> + <width>1220</width> + <include content="DialogBackgroundCommons"> + <param name="width" value="1220" /> + <param name="height" value="750" /> + <param name="header_label" value="" /> + <param name="header_id" value="1" /> + </include> + <control type="image"> + <left>0</left> + <top>80</top> + <width>920</width> + <bottom>2</bottom> + <texture border="40">buttons/dialogbutton-nofo.png</texture> + </control> + <control type="list" id="3"> + <left>20</left> + <top>100</top> + <width>880</width> + <bottom>20</bottom> + <onup>3</onup> + <ondown>3</ondown> + <onleft>9001</onleft> + <onright>61</onright> + <pagecontrol>61</pagecontrol> + <scrolltime>200</scrolltime> + <include content="DefaultSimpleListLayout"> + <param name="width" value="880" /> + <param name="list_id" value="3" /> + </include> + </control> + <control type="list" id="6"> + <left>20</left> + <top>100</top> + <width>880</width> + <bottom>20</bottom> + <onup>6</onup> + <ondown>6</ondown> + <onleft>9001</onleft> + <onright>61</onright> + <pagecontrol>61</pagecontrol> + <scrolltime>200</scrolltime> + <itemlayout height="130" width="880"> + <control type="image"> + <left>10</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/tick.png</texture> + <visible>ListItem.IsSelected</visible> + </control> +- <control type="label"> + <left>50</left> + <top>0</top> + <right>80</right> + <height>60</height> + <font>font14</font> + <aligny>center</aligny> + <label>$INFO[ListItem.Label]$VAR[VideoStreamDialogAudioItemLabelVar, - [LIGHT],[/LIGHT]]</label> + </control> + <control type="textbox"> + <left>50</left> + <top>50</top> + <right>80</right> + <height>80</height> + <font>font12</font> + <textcolor>grey</textcolor> + <label>$INFO[ListItem.Property(stream.codecdesc),, - ]$INFO[ListItem.Property(stream.channels),$LOCALIZE[31612]: ][CR]$INFO[ListItem.Property(stream.description)]</label> + </control> + <control type="image"> + <left>830</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/star.png</texture> + <visible>ListItem.Property(stream.isdefault)</visible> + </control> + </itemlayout> + <focusedlayout height="130" width="880"> + <control type="image"> + <left>0</left> + <top>0</top> + <right>0</right> + <bottom>0</bottom> + <texture colordiffuse="button_focus">lists/focus.png</texture> + <animation effect="fade" start="100" end="50" time="0">UnFocus</animation> + </control> + <control type="image"> + <left>10</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/tick.png</texture> + <visible>ListItem.IsSelected</visible> + </control> + <control type="label"> + <left>50</left> + <top>0</top> + <right>80</right> + <height>60</height> + <aligny>center</aligny> + <scroll>true</scroll> + <font>font14</font> + <label>$INFO[ListItem.Label]$VAR[VideoStreamDialogAudioItemLabelVar, - [LIGHT],[/LIGHT]]</label> + </control> + <control type="textbox"> + <left>50</left> + <top>50</top> + <right>80</right> + <height>80</height> + <font>font12</font> + <label>$INFO[ListItem.Property(stream.codecdesc),, - ]$INFO[ListItem.Property(stream.channels),$LOCALIZE[31612]: ][CR]$INFO[ListItem.Property(stream.description)]</label> + </control> + <control type="image"> + <left>830</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/star.png</texture> + <visible>ListItem.Property(stream.isdefault)</visible> + </control> + </focusedlayout> + </control> + <control type="scrollbar" id="61"> + <left>910</left> + <top>100</top> + <width>12</width> + <bottom>20</bottom> + <onleft condition="Control.IsVisible(3)">3</onleft> + <onleft condition="Control.IsVisible(6)">6</onleft> + <onright>9001</onright> + <orientation>vertical</orientation> + </control> + <control type="label"> + <left>925</left> + <bottom>10</bottom> + <width>275</width> + <height>35</height> + <font>font12</font> + <align>right</align> + <textcolor>grey</textcolor> + <label>$VAR[SelectLabel]</label> + </control> + <control type="grouplist" id="9001"> + <left>920</left> + <top>80</top> + <onleft>61</onleft> + <itemgap>dialogbuttons_itemgap</itemgap> + <onright>3</onright> + <include content="DefaultDialogButton"> + <param name="id" value="5" /> + <param name="label" value="" /> + </include> + <include content="DefaultDialogButton"> + <param name="id" value="8" /> + <param name="label" value="" /> + </include> + <include content="DefaultDialogButton"> + <param name="id" value="7" /> + <param name="label" value="$LOCALIZE[222]" /> + </include> + </control> + </control> + </include> + <include name="VideoDialogSelectSubtitleLayout"> + <!-- + Available ListItem subtitle stream properties: + stream.description, stream.codec, stream.isdefault, stream.isforced, stream.isoriginal, + stream.ishearingimpaired, stream.isvisualimpaired, stream.isexternal + --> + <control type="group"> + <centertop>50%</centertop> + <centerleft>50%</centerleft> + <height>750</height> + <width>1220</width> + <include content="DialogBackgroundCommons"> + <param name="width" value="1220" /> + <param name="height" value="750" /> + <param name="header_label" value="" /> + <param name="header_id" value="1" /> + </include> + <control type="image"> + <left>0</left> + <top>80</top> + <width>920</width> + <bottom>2</bottom> + <texture border="40">buttons/dialogbutton-nofo.png</texture> + </control> + <control type="list" id="3"> + <left>20</left> + <top>100</top> + <width>880</width> + <bottom>20</bottom> + <onup>3</onup> + <ondown>3</ondown> + <onleft>9001</onleft> + <onright>61</onright> + <pagecontrol>61</pagecontrol> + <scrolltime>200</scrolltime> + <include content="DefaultSimpleListLayout"> + <param name="width" value="880" /> + <param name="list_id" value="3" /> + </include> + </control> + <control type="list" id="6"> + <left>20</left> + <top>100</top> + <width>880</width> + <bottom>20</bottom> + <onup>6</onup> + <ondown>6</ondown> + <onleft>9001</onleft> + <onright>61</onright> + <pagecontrol>61</pagecontrol> + <scrolltime>200</scrolltime> + <itemlayout height="100" width="880"> + <control type="image"> + <left>10</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/tick.png</texture> + <visible>ListItem.IsSelected</visible> + </control> + <control type="label"> + <left>50</left> + <top>0</top> + <right>80</right> + <height>60</height> + <font>font14</font> + <aligny>center</aligny> + <label>$INFO[ListItem.Label]$VAR[VideoStreamDialogSubItemLabelVar, - [LIGHT],[/LIGHT]]</label> + </control> + <control type="textbox"> + <left>50</left> + <top>50</top> + <right>80</right> + <height>67</height> + <font>font12</font> + <textcolor>grey</textcolor> + <label>$INFO[ListItem.Property(stream.description)]</label> + </control> + <control type="image"> + <left>830</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/star.png</texture> + <visible>ListItem.Property(stream.isdefault)</visible> + </control> + </itemlayout> + <focusedlayout height="100" width="880"> + <control type="image"> + <left>0</left> + <top>0</top> + <right>0</right> + <bottom>0</bottom> + <texture colordiffuse="button_focus">lists/focus.png</texture> + <animation effect="fade" start="100" end="50" time="0">UnFocus</animation> + </control> + <control type="image"> + <left>10</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/tick.png</texture> + <visible>ListItem.IsSelected</visible> + </control> + <control type="label"> + <left>50</left> + <top>0</top> + <right>80</right> + <height>60</height> + <aligny>center</aligny> + <scroll>true</scroll> + <font>font14</font> + <label>$INFO[ListItem.Label]$VAR[VideoStreamDialogSubItemLabelVar, - [LIGHT],[/LIGHT]]</label> + </control> + <control type="textbox"> + <left>50</left> + <top>50</top> + <right>80</right> + <height>67</height> + <font>font12</font> + <label>$INFO[ListItem.Property(stream.description)]</label> + </control> + <control type="image"> + <left>830</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/star.png</texture> + <visible>ListItem.Property(stream.isdefault)</visible> + </control> + </focusedlayout> + </control> + <control type="scrollbar" id="61"> + <left>910</left> + <top>100</top> + <width>12</width> + <bottom>20</bottom> + <onleft condition="Control.IsVisible(3)">3</onleft> + <onleft condition="Control.IsVisible(6)">6</onleft> + <onright>9001</onright> + <orientation>vertical</orientation> + </control> + <control type="label"> + <left>925</left> + <bottom>10</bottom> + <width>275</width> + <height>35</height> + <font>font12</font> + <align>right</align> + <textcolor>grey</textcolor> + <label>$VAR[SelectLabel]</label> + </control> + <control type="grouplist" id="9001"> + <left>920</left> + <top>80</top> + <onleft>61</onleft> + <itemgap>dialogbuttons_itemgap</itemgap> + <onright>3</onright> + <include content="DefaultDialogButton"> + <param name="id" value="5" /> + <param name="label" value="" /> + </include> + <include content="DefaultDialogButton"> + <param name="id" value="8" /> + <param name="label" value="" /> + </include> + <include content="DefaultDialogButton"> + <param name="id" value="7" /> + <param name="label" value="$LOCALIZE[222]" /> + </include> + </control> + </control> + </include> <include name="GameDialogSelectSaveLayout"> <control type="group"> <centertop>50%</centertop> diff --git a/addons/skin.estuary/xml/Includes_SettingsDialog.xml b/addons/skin.estuary/xml/Includes_SettingsDialog.xml index 7d8aa3cab9ffc..1dedc8b9a8893 100644 --- a/addons/skin.estuary/xml/Includes_SettingsDialog.xml +++ b/addons/skin.estuary/xml/Includes_SettingsDialog.xml @@ -25,19 +25,25 @@ <onclick>ActivateWindow(osdcmssettings)</onclick> <visible>System.HasCMS</visible> </control> + <control type="button" id="22106"> + <include>DialogSettingButton</include> + <label>$LOCALIZE[31115]</label> + <label2>$VAR[ActiveVideoPlayerSubtitleLanguage]</label2> + <onclick>DialogSelectSubtitle</onclick> + <visible>VideoPlayer.HasSubtitles</visible> + </control> <control type="button" id="22105"> <include>DialogSettingButton</include> <label>$LOCALIZE[31112]</label> <label2>[B]$INFO[VideoPlayer.AudioLanguage][/B]</label2> - <onclick>AudioNextLanguage</onclick> + <onclick>DialogSelectAudio</onclick> <visible>Integer.IsGreater(VideoPlayer.AudioStreamCount,1)</visible> </control> - <control type="button" id="22106"> + <control type="button" id="22110"> <include>DialogSettingButton</include> - <label>$LOCALIZE[31115]</label> - <label2>$VAR[ActiveVideoPlayerSubtitleLanguage]</label2> - <onclick>NextSubtitle</onclick> - <visible>VideoPlayer.HasSubtitles</visible> + <label>$LOCALIZE[31109]</label> + <onclick>DialogSelectVideo</onclick> + <visible>Integer.IsGreater(VideoPlayer.VideoStreamCount,1)</visible> </control> <control type="button" id="22107"> <include>DialogSettingButton</include> diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 48d31556dfff6..afb70923e5398 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -965,4 +965,37 @@ <variable name="PVRInstanceName"> <value condition="Integer.IsGreater(PVR.ClientCount,1)">$INFO[ListItem.PVRClientName,[COLOR grey]$LOCALIZE[31137]:[/COLOR] ,]$INFO[ListItem.PVRInstanceName, (,)]</value> </variable> + <variable name="VideoStreamDialogVideoItemLabelVar"> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39106], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39106], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)">$LOCALIZE[39106]</value> + </variable> + <variable name="VideoStreamDialogAudioItemLabelVar"> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39106], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39106], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isforced),true)">$LOCALIZE[39111], $LOCALIZE[39106]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39106], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39106], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)">$LOCALIZE[39106]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)">$LOCALIZE[39111]</value> + </variable> + <variable name="VideoStreamDialogSubItemLabelVar"> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39106], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39106], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isforced),true)">$LOCALIZE[39111], $LOCALIZE[39106]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39106], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39106], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)">$LOCALIZE[39106]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)">$LOCALIZE[39111]</value> + </variable> </includes> diff --git a/xbmc/addons/interfaces/gui/GUITranslator.cpp b/xbmc/addons/interfaces/gui/GUITranslator.cpp index 2062f38667881..bcc38b4815723 100644 --- a/xbmc/addons/interfaces/gui/GUITranslator.cpp +++ b/xbmc/addons/interfaces/gui/GUITranslator.cpp @@ -552,6 +552,12 @@ int CAddonGUITranslator::TranslateActionIdToKodi(ADDON_ACTION addonId) return ACTION_SHOW_SUBTITLES; case ADDON_ACTION_NEXT_SUBTITLE: return ACTION_NEXT_SUBTITLE; + case ADDON_ACTION_DIALOG_SELECT_VIDEO: + return ACTION_DIALOG_SELECT_VIDEO; + case ADDON_ACTION_DIALOG_SELECT_AUDIO: + return ACTION_DIALOG_SELECT_AUDIO; + case ADDON_ACTION_DIALOG_SELECT_SUBTITLE: + return ACTION_DIALOG_SELECT_SUBTITLE; case ADDON_ACTION_PLAYER_DEBUG: return ACTION_PLAYER_DEBUG; case ADDON_ACTION_NEXT_PICTURE: diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/gui/input/action_ids.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/gui/input/action_ids.h index b7e8f9c6d25b0..0f779ce464cea 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/gui/input/action_ids.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/gui/input/action_ids.h @@ -677,6 +677,15 @@ enum ADDON_ACTION /// @brief <b>`267`</b>: Tempo decrease in current file played. global action, can be used anywhere ADDON_ACTION_PLAYER_DECREASE_TEMPO = 267, + /// @brief <b>`270 `</b>: Open the dialog window to select a video stream + ADDON_ACTION_DIALOG_SELECT_VIDEO = 270, + + /// @brief <b>`271 `</b>: Open the dialog window to select a audio stream + ADDON_ACTION_DIALOG_SELECT_AUDIO = 271, + + /// @brief <b>`272 `</b>: Open the dialog window to select a subtitle stream + ADDON_ACTION_DIALOG_SELECT_SUBTITLE = 272, + /// @brief <b>`300`</b>: Voice actions ADDON_ACTION_VOICE_RECOGNIZE = 300, diff --git a/xbmc/cores/VideoPlayer/Interface/StreamInfo.h b/xbmc/cores/VideoPlayer/Interface/StreamInfo.h index c3791862e4601..924931e9b6583 100644 --- a/xbmc/cores/VideoPlayer/Interface/StreamInfo.h +++ b/xbmc/cores/VideoPlayer/Interface/StreamInfo.h @@ -44,8 +44,8 @@ struct StreamInfo int bitrate = 0; std::string language; std::string name; - std::string codecName; // Codec name (name definition from ffmpeg) - std::string codecDesc; // Codec description + std::string codecName; + std::string codecDesc; StreamFlags flags = StreamFlags::FLAG_NONE; protected: @@ -61,7 +61,9 @@ struct AudioStreamInfo : StreamInfo }; struct SubtitleStreamInfo : StreamInfo -{}; +{ + bool isExternal{false}; +}; struct VideoStreamInfo : StreamInfo { diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index f8e3de0287759..87a9f7240e0a2 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -5388,6 +5388,8 @@ void CVideoPlayer::GetSubtitleStreamInfo(int index, SubtitleStreamInfo& info) co info.language = s.language; info.codecName = s.codec; info.flags = s.flags; + info.isExternal = STREAM_SOURCE_MASK(s.source) == STREAM_SOURCE_DEMUX_SUB || + STREAM_SOURCE_MASK(s.source) == STREAM_SOURCE_TEXT; } void CVideoPlayer::SetSubtitle(int iStream) diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.h b/xbmc/cores/VideoPlayer/VideoPlayer.h index 86c783015f096..154650583000e 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.h +++ b/xbmc/cores/VideoPlayer/VideoPlayer.h @@ -186,8 +186,8 @@ struct SelectionStream int source = 0; int id = 0; int64_t demuxerId = -1; - std::string codec; // Codec name (name definition from ffmpeg) - std::string codecDesc; // Codec description + std::string codec; + std::string codecDesc; int channels = 0; int bitrate = 0; int width = 0; diff --git a/xbmc/guilib/GUIWindowManager.cpp b/xbmc/guilib/GUIWindowManager.cpp index 065a68cbb8603..2999fb4960958 100644 --- a/xbmc/guilib/GUIWindowManager.cpp +++ b/xbmc/guilib/GUIWindowManager.cpp @@ -299,6 +299,9 @@ void CGUIWindowManager::CreateWindows() Add(new CGUIDialogVideoManagerExtras); Add(new CGUIDialogSelect(WINDOW_DIALOG_SELECT_VIDEO_VERSION)); Add(new CGUIDialogSelect(WINDOW_DIALOG_SELECT_VIDEO_EXTRA)); + Add(new CGUIDialogSelect(WINDOW_DIALOG_SELECT_VIDEO_STREAM)); + Add(new CGUIDialogSelect(WINDOW_DIALOG_SELECT_AUDIO_STREAM)); + Add(new CGUIDialogSelect(WINDOW_DIALOG_SELECT_SUBTITLE_STREAM)); Add(new CGUIDialogTextViewer); Add(new CGUIWindowFullScreen); @@ -386,6 +389,9 @@ bool CGUIWindowManager::DestroyWindows() DestroyWindow(WINDOW_DIALOG_SLIDER); DestroyWindow(WINDOW_DIALOG_MEDIA_FILTER); DestroyWindow(WINDOW_DIALOG_SUBTITLES); + DestroyWindow(WINDOW_DIALOG_SELECT_VIDEO_STREAM); + DestroyWindow(WINDOW_DIALOG_SELECT_AUDIO_STREAM); + DestroyWindow(WINDOW_DIALOG_SELECT_SUBTITLE_STREAM); DestroyWindow(WINDOW_DIALOG_COLOR_PICKER); /* Delete PVR related windows and dialogs */ diff --git a/xbmc/guilib/WindowIDs.h b/xbmc/guilib/WindowIDs.h index 40e4724543eb2..318569115b4c0 100644 --- a/xbmc/guilib/WindowIDs.h +++ b/xbmc/guilib/WindowIDs.h @@ -180,6 +180,10 @@ #define WINDOW_DIALOG_SELECT_VIDEO_EXTRA 12016 #define WINDOW_DIALOG_MANAGE_VIDEO_EXTRAS 12017 +#define WINDOW_DIALOG_SELECT_VIDEO_STREAM 12300 +#define WINDOW_DIALOG_SELECT_AUDIO_STREAM 12301 +#define WINDOW_DIALOG_SELECT_SUBTITLE_STREAM 12302 + #define WINDOW_WEATHER 12600 #define WINDOW_SCREENSAVER 12900 #define WINDOW_DIALOG_VIDEO_OSD 12901 diff --git a/xbmc/input/WindowTranslator.cpp b/xbmc/input/WindowTranslator.cpp index 795c854f0f64c..9eeaceb63f51b 100644 --- a/xbmc/input/WindowTranslator.cpp +++ b/xbmc/input/WindowTranslator.cpp @@ -177,6 +177,9 @@ const CWindowTranslator::WindowMapByName CWindowTranslator::WindowMappingByName {"ingamesaves", WINDOW_DIALOG_IN_GAME_SAVES}, {"gamesaves", WINDOW_DIALOG_GAME_SAVES}, {"gameagents", WINDOW_DIALOG_GAME_AGENTS}, + {"videoselectdialog", WINDOW_DIALOG_SELECT_VIDEO_STREAM}, + {"audioselectdialog", WINDOW_DIALOG_SELECT_AUDIO_STREAM}, + {"subtitleselectdialog", WINDOW_DIALOG_SELECT_SUBTITLE_STREAM}, }; namespace diff --git a/xbmc/input/actions/ActionIDs.h b/xbmc/input/actions/ActionIDs.h index ddd0d44cb6078..11f9ed251d4a0 100644 --- a/xbmc/input/actions/ActionIDs.h +++ b/xbmc/input/actions/ActionIDs.h @@ -458,6 +458,15 @@ constexpr const int ACTION_KEYBOARD_COMPOSING_KEY_FINISHED = 265; constexpr const int ACTION_PLAYER_INCREASE_TEMPO = 266; constexpr const int ACTION_PLAYER_DECREASE_TEMPO = 267; +//! Open the dialog window to select a video stream +constexpr const int ACTION_DIALOG_SELECT_VIDEO = 270; + +//! Open the dialog window to select a audio stream +constexpr const int ACTION_DIALOG_SELECT_AUDIO = 271; + +//! Open the dialog window to select a subtitle stream +constexpr const int ACTION_DIALOG_SELECT_SUBTITLE = 272; + // Voice actions constexpr const int ACTION_VOICE_RECOGNIZE = 300; diff --git a/xbmc/input/actions/ActionTranslator.cpp b/xbmc/input/actions/ActionTranslator.cpp index c47ee6b3054ff..7233418115fa6 100644 --- a/xbmc/input/actions/ActionTranslator.cpp +++ b/xbmc/input/actions/ActionTranslator.cpp @@ -55,6 +55,9 @@ static const std::map<ActionName, ActionID> ActionMappings = { {"nextsubtitle", ACTION_NEXT_SUBTITLE}, {"browsesubtitle", ACTION_BROWSE_SUBTITLE}, {"cyclesubtitle", ACTION_CYCLE_SUBTITLE}, + {"dialogselectvideo", ACTION_DIALOG_SELECT_VIDEO}, + {"dialogselectaudio", ACTION_DIALOG_SELECT_AUDIO}, + {"dialogselectsubtitle", ACTION_DIALOG_SELECT_SUBTITLE}, {"playerdebug", ACTION_PLAYER_DEBUG}, {"playerdebugvideo", ACTION_PLAYER_DEBUG_VIDEO}, {"codecinfo", ACTION_PLAYER_PROCESS_INFO}, diff --git a/xbmc/video/PlayerController.cpp b/xbmc/video/PlayerController.cpp index 44a79b44204be..ace0f0a6374e5 100644 --- a/xbmc/video/PlayerController.cpp +++ b/xbmc/video/PlayerController.cpp @@ -31,6 +31,7 @@ #include "utils/StringUtils.h" #include "utils/Variant.h" #include "video/dialogs/GUIDialogAudioSettings.h" +#include "video/guilib/VideoStreamSelectHelper.h" using namespace KODI; using namespace UTILS; @@ -141,6 +142,12 @@ bool CPlayerController::OnAction(const CAction &action) return true; } + case ACTION_DIALOG_SELECT_SUBTITLE: + { + VIDEO::GUILIB::OpenDialogSelectSubtitleStream(); + return true; + } + case ACTION_SUBTITLE_DELAY_MIN: { float videoSubsDelayRange = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoSubsDelayRange; @@ -244,6 +251,12 @@ bool CPlayerController::OnAction(const CAction &action) return true; } + case ACTION_DIALOG_SELECT_AUDIO: + { + VIDEO::GUILIB::OpenDialogSelectAudioStream(); + return true; + } + case ACTION_VIDEO_NEXT_STREAM: { if (appPlayer->GetVideoStreamCount() == 1) @@ -263,6 +276,12 @@ bool CPlayerController::OnAction(const CAction &action) return true; } + case ACTION_DIALOG_SELECT_VIDEO: + { + VIDEO::GUILIB::OpenDialogSelectVideoStream(); + return true; + } + case ACTION_ZOOM_IN: { CVideoSettings vs = appPlayer->GetVideoSettings(); diff --git a/xbmc/video/dialogs/GUIDialogAudioSettings.cpp b/xbmc/video/dialogs/GUIDialogAudioSettings.cpp index 5fccdb7807838..3030efaeadcff 100644 --- a/xbmc/video/dialogs/GUIDialogAudioSettings.cpp +++ b/xbmc/video/dialogs/GUIDialogAudioSettings.cpp @@ -348,16 +348,17 @@ void CGUIDialogAudioSettings::AudioStreamsOptionFiller(const SettingConstPtr& se const auto appPlayer = components.GetComponent<CApplicationPlayer>(); const int audioStreamCount = appPlayer->GetAudioStreamCount(); - std::string strChannels = g_localizeStrings.Get(10127); + std::string channelsLabel = 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 strLanguage; + AudioStreamInfo info; appPlayer->GetAudioStreamInfo(i, info); - std::string strLanguage; if (!g_LangCodeExpander.Lookup(info.language, strLanguage)) strLanguage = strUnknown; @@ -369,7 +370,7 @@ void CGUIDialogAudioSettings::AudioStreamsOptionFiller(const SettingConstPtr& se if (!info.codecDesc.empty()) textInfo += info.codecDesc + ", "; - textInfo += std::to_string(info.channels) + " " + strChannels + ")"; + textInfo += std::to_string(info.channels) + " " + channelsLabel + ")"; textInfo += FormatFlags(info.flags); textInfo += StringUtils::Format(" ({}/{})", i + 1, audioStreamCount); diff --git a/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp b/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp index 171c69a294bfe..d1c8e31524f30 100644 --- a/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp +++ b/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp @@ -417,6 +417,8 @@ std::string CGUIDialogSubtitleSettings::FormatFlags(StreamFlags flags) 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, ", "); diff --git a/xbmc/video/guilib/CMakeLists.txt b/xbmc/video/guilib/CMakeLists.txt index 64192185c66b5..83205a56a468c 100644 --- a/xbmc/video/guilib/CMakeLists.txt +++ b/xbmc/video/guilib/CMakeLists.txt @@ -1,12 +1,14 @@ set(SOURCES VideoGUIUtils.cpp VideoPlayActionProcessor.cpp VideoSelectActionProcessor.cpp + VideoStreamSelectHelper.cpp VideoVersionHelper.cpp) set(HEADERS VideoAction.h VideoGUIUtils.h VideoPlayActionProcessor.h VideoSelectActionProcessor.h + VideoStreamSelectHelper.h VideoVersionHelper.h) core_add_library(video_guilib) diff --git a/xbmc/video/guilib/VideoStreamSelectHelper.cpp b/xbmc/video/guilib/VideoStreamSelectHelper.cpp new file mode 100644 index 0000000000000..a8c046eafecdb --- /dev/null +++ b/xbmc/video/guilib/VideoStreamSelectHelper.cpp @@ -0,0 +1,517 @@ +/* + * Copyright (C) 2024 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 "VideoStreamSelectHelper.h" + +#include "FileItem.h" +#include "FileItemList.h" +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "dialogs/GUIDialogSelect.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "utils/LangCodeExpander.h" +#include "utils/StreamDetails.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +namespace +{ +constexpr int STREAM_ID_DISABLE = -2; // Stream id referred to item that disable the stream +constexpr int STREAM_ID_NONE = -1; // Stream id referred to item for "none" stream + +// \brief Make a FileItem entry with "Disable" label, allow to disable a stream +const std::shared_ptr<CFileItem> MakeFileItemDisable(bool isSelected) +{ + const auto fileItem{std::make_shared<CFileItem>(g_localizeStrings.Get(24021))}; + fileItem->Select(isSelected); + fileItem->SetProperty("stream.id", STREAM_ID_DISABLE); + return fileItem; +} + +// \brief Make a FileItem entry with "None" label, signal an empty list +const std::shared_ptr<CFileItem> MakeFileItemNone() +{ + const auto fileItem{std::make_shared<CFileItem>(g_localizeStrings.Get(231))}; + fileItem->SetProperty("stream.id", STREAM_ID_NONE); + return fileItem; +} + +std::shared_ptr<const CFileItem> OpenSelectDialog(CGUIDialogSelect& dialog, + int headingId, + const CFileItemList& itemsToDisplay) +{ + dialog.Reset(); + dialog.SetHeading(headingId); + dialog.SetUseDetails(true); + dialog.SetMultiSelection(false); + dialog.SetItems(itemsToDisplay); + + dialog.Open(); + + if (dialog.IsConfirmed()) + return dialog.GetSelectedFileItem(); + + return {}; +} + +std::string ConvertFpsToString(float value) +{ + if (value == 0.0f) + return ""; + + std::string str{StringUtils::Format("{:.3f}", value)}; + // Keep numbers after the comma only if they are not 0 + const size_t zeroPos = str.find_last_not_of("0"); + if (zeroPos != std::string::npos) + str.erase(zeroPos + 1); + + if (str.back() == '.') + str.pop_back(); + + return str; +} + +struct VideoStreamInfoExt : VideoStreamInfo +{ + VideoStreamInfoExt(int id, const VideoStreamInfo& info) : VideoStreamInfo(info) + { + streamId = id; + isDefault = info.flags & StreamFlags::FLAG_DEFAULT; + isForced = info.flags & StreamFlags::FLAG_FORCED; + isHearingImpaired = info.flags & StreamFlags::FLAG_HEARING_IMPAIRED; + isVisualImpaired = info.flags & StreamFlags::FLAG_VISUAL_IMPAIRED; + } + + int streamId{0}; + std::string languageDesc; + bool isDefault{false}; + bool isForced{false}; + bool isHearingImpaired{false}; + bool isVisualImpaired{false}; +}; + +struct AudioStreamInfoExt : AudioStreamInfo +{ + AudioStreamInfoExt(int id, const AudioStreamInfo& info) : AudioStreamInfo(info) + { + streamId = id; + + if (!g_LangCodeExpander.Lookup(info.language, languageDesc)) + languageDesc = g_localizeStrings.Get(13205); // Unknown + + isDefault = info.flags & StreamFlags::FLAG_DEFAULT; + isForced = info.flags & StreamFlags::FLAG_FORCED; + isHearingImpaired = info.flags & StreamFlags::FLAG_HEARING_IMPAIRED; + isVisualImpaired = info.flags & StreamFlags::FLAG_VISUAL_IMPAIRED; + isOriginal = info.flags & StreamFlags::FLAG_ORIGINAL; + } + + int streamId{0}; + std::string languageDesc; + bool isDefault{false}; + bool isForced{false}; + bool isHearingImpaired{false}; + bool isVisualImpaired{false}; + bool isOriginal{false}; +}; + +struct SubtitleStreamInfoExt : SubtitleStreamInfo +{ + SubtitleStreamInfoExt(int id, const SubtitleStreamInfo& info) : SubtitleStreamInfo(info) + { + streamId = id; + + if (!g_LangCodeExpander.Lookup(info.language, languageDesc)) + languageDesc = g_localizeStrings.Get(13205); // Unknown + + isDefault = info.flags & StreamFlags::FLAG_DEFAULT; + isForced = info.flags & StreamFlags::FLAG_FORCED; + isHearingImpaired = info.flags & StreamFlags::FLAG_HEARING_IMPAIRED; + isVisualImpaired = info.flags & StreamFlags::FLAG_VISUAL_IMPAIRED; + isOriginal = info.flags & StreamFlags::FLAG_ORIGINAL; + } + + int streamId{0}; + std::string languageDesc; + bool isDefault{false}; + bool isForced{false}; + bool isHearingImpaired{false}; + bool isVisualImpaired{false}; + bool isOriginal{false}; +}; + +struct SortComparerStreamVideo +{ + bool operator()(const VideoStreamInfoExt& a, const VideoStreamInfoExt& b) + { + if (a.language != b.language) + { + return a.language < b.language; + } + if (a.codecName != b.codecName) + { + return a.codecName < b.codecName; + } + if (a.hdrType != b.hdrType) + { + return a.hdrType < b.hdrType; + } + if (a.fpsRate != b.fpsRate) + { + return a.fpsRate < b.fpsRate; + } + if (a.fpsScale != b.fpsScale) + { + return a.fpsScale < b.fpsScale; + } + if (a.height != b.height) + { + return a.height < b.height; + } + if (a.width != b.width) + { + return a.width < b.width; + } + return a.bitrate < b.bitrate; + } +}; + +struct SortComparerStreamAudio +{ + bool operator()(const AudioStreamInfoExt& a, const AudioStreamInfoExt& b) + { + if (a.languageDesc != b.languageDesc) + { + return a.languageDesc < b.languageDesc; + } + if (a.isOriginal != b.isOriginal) + { + return a.isOriginal < b.isOriginal; + } + if (a.isHearingImpaired != b.isHearingImpaired) + { + return a.isHearingImpaired < b.isHearingImpaired; + } + if (a.isVisualImpaired != b.isVisualImpaired) + { + return a.isVisualImpaired < b.isVisualImpaired; + } + if (a.isForced != b.isForced) + { + return a.isForced < b.isForced; + } + if (a.channels != b.channels) + { + return a.channels < b.channels; + } + if (a.bitrate != b.bitrate) + { + return a.bitrate < b.bitrate; + } + if (a.samplerate != b.samplerate) + { + return a.samplerate < b.samplerate; + } + return a.codecName < b.codecName; + } +}; + +struct SortComparerStreamSubtitle +{ + bool operator()(const SubtitleStreamInfoExt& a, const SubtitleStreamInfoExt& b) + { + if (a.isExternal != b.isExternal) + { + return a.isExternal > b.isExternal; + } + if (a.languageDesc != b.languageDesc) + { + return a.languageDesc < b.languageDesc; + } + if (a.isOriginal != b.isOriginal) + { + return a.isOriginal < b.isOriginal; + } + if (a.isHearingImpaired != b.isHearingImpaired) + { + return a.isHearingImpaired < b.isHearingImpaired; + } + if (a.isVisualImpaired != b.isVisualImpaired) + { + return a.isVisualImpaired < b.isVisualImpaired; + } + if (a.isForced != b.isForced) + { + return a.isForced < b.isForced; + } + return a.codecName < b.codecName; + } +}; + +bool SupportsAudioFeature(int feature, const std::vector<int>& caps) +{ + for (int item : caps) + { + if (item == feature || item == IPC_AUD_ALL) + return true; + } + + return false; +} + +bool SupportsSubtitleFeature(int feature, const std::vector<int>& caps) +{ + for (int item : caps) + { + if (item == feature || item == IPC_SUBS_ALL) + return true; + } + return false; +} + +} // unnamed namespace + +void KODI::VIDEO::GUILIB::OpenDialogSelectVideoStream() +{ + CGUIDialogSelect* dialog{CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>( + WINDOW_DIALOG_SELECT_VIDEO_STREAM)}; + if (!dialog) + { + CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_SELECT_VIDEO_STREAM dialog instance"); + return; + } + + auto& components = CServiceBroker::GetAppComponents(); + auto appPlayer = components.GetComponent<CApplicationPlayer>(); + const int streamCount = appPlayer->GetVideoStreamCount(); + const int selectedId = appPlayer->GetVideoStream(); + + std::vector<VideoStreamInfoExt> streams; + streams.reserve(streamCount); + + // Collect all streams + for (int i = 0; i < streamCount; ++i) + { + VideoStreamInfo info; + appPlayer->GetVideoStreamInfo(i, info); + streams.emplace_back(i, info); + } + + // Sort streams + std::sort(streams.begin(), streams.end(), SortComparerStreamVideo()); + + // Convert streams to FileItem's + CFileItemList itemsToDisplay; + itemsToDisplay.Reserve(streams.size()); + + for (const VideoStreamInfoExt& info : streams) + { + const auto fileItem = std::make_shared<CFileItem>(info.name); + fileItem->SetProperty("stream.id", info.streamId); + fileItem->SetProperty("stream.description", info.name); + fileItem->SetProperty("stream.codec", info.codecName); + + std::string languageDesc; + g_LangCodeExpander.Lookup(info.language, languageDesc); + fileItem->SetProperty("stream.language", languageDesc); + + fileItem->SetProperty("stream.resolution", + std::to_string(info.width) + "x" + std::to_string(info.height)); + fileItem->SetProperty("stream.bitrate", + static_cast<int>(std::lrint(static_cast<double>(info.bitrate) / 1000.0))); + + float fps = static_cast<float>(info.fpsRate); + if (fps > 0.0f && info.fpsScale > 0) + fps /= info.fpsScale; + + fileItem->SetProperty("stream.fps", ConvertFpsToString(fps)); + + fileItem->SetProperty("stream.is3d", !info.stereoMode.empty() && info.stereoMode != "mono"); + fileItem->SetProperty("stream.stereomode", info.stereoMode); + fileItem->SetProperty("stream.hdrtype", CStreamDetails::HdrTypeToString(info.hdrType)); + + fileItem->SetProperty("stream.isdefault", info.isDefault); + fileItem->SetProperty("stream.isforced", info.isForced); + fileItem->SetProperty("stream.ishearingimpaired", info.isHearingImpaired); + fileItem->SetProperty("stream.isvisualimpaired", info.isVisualImpaired); + if (selectedId == info.streamId) + fileItem->Select(true); + + itemsToDisplay.Add(fileItem); + } + + if (itemsToDisplay.IsEmpty()) + itemsToDisplay.Add(MakeFileItemNone()); + + const auto selectedItem = OpenSelectDialog(*dialog, 38031, itemsToDisplay); + if (selectedItem) + { + const int id = selectedItem->GetProperty("stream.id").asInteger32(STREAM_ID_NONE); + + if (id != STREAM_ID_NONE) + appPlayer->SetVideoStream(id); + } +} + +void KODI::VIDEO::GUILIB::OpenDialogSelectAudioStream() +{ + CGUIDialogSelect* dialog{CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>( + WINDOW_DIALOG_SELECT_AUDIO_STREAM)}; + if (!dialog) + { + CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_SELECT_AUDIO_STREAM dialog instance"); + return; + } + + auto& components = CServiceBroker::GetAppComponents(); + auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + std::vector<int> caps; + appPlayer->GetAudioCapabilities(caps); + if (!SupportsAudioFeature(IPC_AUD_SELECT_STREAM, caps)) + return; + + const int streamCount = appPlayer->GetAudioStreamCount(); + const int selectedId = appPlayer->GetAudioStream(); + + std::vector<AudioStreamInfoExt> streams; + streams.reserve(streamCount); + + // Collect all streams + for (int i = 0; i < streamCount; ++i) + { + AudioStreamInfo info; + appPlayer->GetAudioStreamInfo(i, info); + streams.emplace_back(i, info); + } + + // Sort streams + std::sort(streams.begin(), streams.end(), SortComparerStreamAudio()); + + // Convert streams to FileItem's + CFileItemList itemsToDisplay; + itemsToDisplay.Reserve(streams.size()); + + for (const AudioStreamInfoExt& info : streams) + { + CFileItemPtr fileItem = std::make_shared<CFileItem>(info.languageDesc); + fileItem->SetProperty("stream.id", info.streamId); + fileItem->SetProperty("stream.description", info.name); + fileItem->SetProperty("stream.codec", info.codecName); + fileItem->SetProperty("stream.codecdesc", info.codecDesc); + fileItem->SetProperty("stream.channels", info.channels); + + fileItem->SetProperty("stream.isdefault", info.isDefault); + fileItem->SetProperty("stream.isforced", info.isForced); + fileItem->SetProperty("stream.ishearingimpaired", info.isHearingImpaired); + fileItem->SetProperty("stream.isvisualimpaired", info.isVisualImpaired); + fileItem->SetProperty("stream.isoriginal", info.isOriginal); + if (selectedId == info.streamId) + fileItem->Select(true); + + itemsToDisplay.Add(fileItem); + } + + if (itemsToDisplay.IsEmpty()) + itemsToDisplay.Add(MakeFileItemNone()); + + const auto selectedItem = OpenSelectDialog(*dialog, 460, itemsToDisplay); + if (selectedItem) + { + const int id = selectedItem->GetProperty("stream.id").asInteger32(STREAM_ID_NONE); + + if (id != STREAM_ID_NONE) + appPlayer->SetAudioStream(id); + } +} + +void KODI::VIDEO::GUILIB::OpenDialogSelectSubtitleStream() +{ + CGUIDialogSelect* dialog{CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>( + WINDOW_DIALOG_SELECT_SUBTITLE_STREAM)}; + if (!dialog) + { + CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_SELECT_SUBTITLE_STREAM dialog instance"); + return; + } + + auto& components = CServiceBroker::GetAppComponents(); + auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + std::vector<int> caps; + appPlayer->GetSubtitleCapabilities(caps); + if (!SupportsSubtitleFeature(IPC_SUBS_SELECT, caps)) + return; + + const int streamCount = appPlayer->GetSubtitleCount(); + const int selectedId = appPlayer->GetSubtitle(); + const bool isSubtitleEnabled = appPlayer->GetSubtitleVisible(); + + std::vector<SubtitleStreamInfoExt> streams; + streams.reserve(streamCount); + + // Collect all streams + for (int i = 0; i < streamCount; ++i) + { + SubtitleStreamInfo info; + appPlayer->GetSubtitleStreamInfo(i, info); + streams.emplace_back(i, info); + } + + // Sort streams + std::sort(streams.begin(), streams.end(), SortComparerStreamSubtitle()); + + // Convert streams to FileItem's + CFileItemList itemsToDisplay; + itemsToDisplay.Reserve(streams.size() + 1); + + for (const SubtitleStreamInfoExt& info : streams) + { + CFileItemPtr fileItem = std::make_shared<CFileItem>(info.languageDesc); + fileItem->SetProperty("stream.id", info.streamId); + fileItem->SetProperty("stream.description", info.name); + fileItem->SetProperty("stream.codec", info.codecName); + + fileItem->SetProperty("stream.isdefault", info.isDefault); + fileItem->SetProperty("stream.isforced", info.isForced); + fileItem->SetProperty("stream.isoriginal", info.isOriginal); + fileItem->SetProperty("stream.ishearingimpaired", info.isHearingImpaired); + fileItem->SetProperty("stream.isvisualimpaired", info.isVisualImpaired); + fileItem->SetProperty("stream.isexternal", info.isExternal); + if (selectedId == info.streamId && isSubtitleEnabled) + fileItem->Select(true); + + itemsToDisplay.Add(fileItem); + } + + if (itemsToDisplay.IsEmpty()) + itemsToDisplay.Add(MakeFileItemNone()); + else + itemsToDisplay.AddFront(MakeFileItemDisable(!isSubtitleEnabled), 0); + + const auto selectedItem = OpenSelectDialog(*dialog, 462, itemsToDisplay); + if (selectedItem) + { + const int id = selectedItem->GetProperty("stream.id").asInteger32(STREAM_ID_NONE); + + if (id == STREAM_ID_DISABLE) + { + appPlayer->SetSubtitleVisible(false); + } + else if (id != STREAM_ID_NONE) + { + appPlayer->SetSubtitle(id); + + if (!appPlayer->GetSubtitleVisible()) + appPlayer->SetSubtitleVisible(true); + } + } +} diff --git a/xbmc/video/guilib/VideoStreamSelectHelper.h b/xbmc/video/guilib/VideoStreamSelectHelper.h new file mode 100644 index 0000000000000..598682422b1ca --- /dev/null +++ b/xbmc/video/guilib/VideoStreamSelectHelper.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 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 + +namespace KODI::VIDEO::GUILIB +{ + +/*! + * \brief Open dialog window to select/change video stream + */ +void OpenDialogSelectVideoStream(); + +/*! + * \brief Open dialog window to select/change audio stream + */ +void OpenDialogSelectAudioStream(); + +/*! + * \brief Open dialog window to select/change subtitle stream + */ +void OpenDialogSelectSubtitleStream(); + +} // namespace KODI::VIDEO::GUILIB From a4dd41aeb0b423a281c5ca3950edd621775b0435 Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Tue, 1 Oct 2024 07:42:59 +0200 Subject: [PATCH 540/651] [addons][gui] increase GUI API version to 5.15.1 --- xbmc/addons/kodi-dev-kit/include/kodi/versions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h index c4c45d6f95e99..34de2017ac7be 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h @@ -49,7 +49,7 @@ #define ADDON_GLOBAL_VERSION_GENERAL_XML_ID "kodi.binary.global.general" #define ADDON_GLOBAL_VERSION_GENERAL_DEPENDS "General.h" -#define ADDON_GLOBAL_VERSION_GUI "5.15.0" +#define ADDON_GLOBAL_VERSION_GUI "5.15.1" #define ADDON_GLOBAL_VERSION_GUI_MIN "5.15.0" #define ADDON_GLOBAL_VERSION_GUI_XML_ID "kodi.binary.global.gui" #define ADDON_GLOBAL_VERSION_GUI_DEPENDS "c-api/gui/input/action_ids.h" \ From 0623561fd66fab72c20155c008065869e5e559ef Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Mon, 7 Oct 2024 08:48:13 +0200 Subject: [PATCH 541/651] [Players][cleanup] Changed audio/sub capabilities to enum class --- xbmc/application/ApplicationPlayer.cpp | 8 ++-- xbmc/application/ApplicationPlayer.h | 4 +- xbmc/cores/IPlayer.h | 40 +++++++++++-------- xbmc/cores/paplayer/PAPlayer.h | 2 +- xbmc/video/dialogs/GUIDialogAudioSettings.cpp | 14 +++---- xbmc/video/dialogs/GUIDialogAudioSettings.h | 7 ++-- .../dialogs/GUIDialogSubtitleSettings.cpp | 12 +++--- .../video/dialogs/GUIDialogSubtitleSettings.h | 5 ++- xbmc/video/guilib/VideoStreamSelectHelper.cpp | 21 +++++----- 9 files changed, 61 insertions(+), 52 deletions(-) diff --git a/xbmc/application/ApplicationPlayer.cpp b/xbmc/application/ApplicationPlayer.cpp index 9a59242a85b7d..a222557677846 100644 --- a/xbmc/application/ApplicationPlayer.cpp +++ b/xbmc/application/ApplicationPlayer.cpp @@ -760,18 +760,18 @@ void CApplicationPlayer::LoadPage(int p, int sp, unsigned char* buffer) player->LoadPage(p, sp, buffer); } -void CApplicationPlayer::GetAudioCapabilities(std::vector<int>& audioCaps) const +void CApplicationPlayer::GetAudioCapabilities(std::vector<IPlayerAudioCaps>& caps) const { const std::shared_ptr<const IPlayer> player = GetInternal(); if (player) - player->GetAudioCapabilities(audioCaps); + player->GetAudioCapabilities(caps); } -void CApplicationPlayer::GetSubtitleCapabilities(std::vector<int>& subCaps) const +void CApplicationPlayer::GetSubtitleCapabilities(std::vector<IPlayerSubtitleCaps>& caps) const { const std::shared_ptr<const IPlayer> player = GetInternal(); if (player) - player->GetSubtitleCapabilities(subCaps); + player->GetSubtitleCapabilities(caps); } int CApplicationPlayer::SeekChapter(int iChapter) diff --git a/xbmc/application/ApplicationPlayer.h b/xbmc/application/ApplicationPlayer.h index ccee506bd62d1..ae901d4058468 100644 --- a/xbmc/application/ApplicationPlayer.h +++ b/xbmc/application/ApplicationPlayer.h @@ -83,7 +83,7 @@ class CApplicationPlayer : public IApplicationComponent bool CanPause() const; bool CanSeek() const; int GetAudioDelay() const; - void GetAudioCapabilities(std::vector<int>& audioCaps) const; + void GetAudioCapabilities(std::vector<IPlayerAudioCaps>& caps) const; int GetAudioStream(); int GetAudioStreamCount() const; void GetAudioStreamInfo(int index, AudioStreamInfo& info) const; @@ -98,7 +98,7 @@ class CApplicationPlayer : public IApplicationComponent KODI::PLAYLIST::Id GetPreferredPlaylist() const; int GetSubtitleDelay() const; int GetSubtitle(); - void GetSubtitleCapabilities(std::vector<int>& subCaps) const; + void GetSubtitleCapabilities(std::vector<IPlayerSubtitleCaps>& caps) const; int GetSubtitleCount() const; void GetSubtitleStreamInfo(int index, SubtitleStreamInfo& info) const; bool GetSubtitleVisible() const; diff --git a/xbmc/cores/IPlayer.h b/xbmc/cores/IPlayer.h index d988d775271b6..8f6bd763bf36e 100644 --- a/xbmc/cores/IPlayer.h +++ b/xbmc/cores/IPlayer.h @@ -50,22 +50,24 @@ class CPlayerOptions class CFileItem; -enum IPlayerAudioCapabilities +// \brief Player Audio capabilities +enum class IPlayerAudioCaps { - IPC_AUD_ALL, - IPC_AUD_OFFSET, - IPC_AUD_AMP, - IPC_AUD_SELECT_STREAM, - IPC_AUD_OUTPUT_STEREO, - IPC_AUD_SELECT_OUTPUT + ALL, // All capabilities supported + SELECT_STREAM, // Support to change stream + SELECT_OUTPUT, // Support to select an output device + OUTPUT_STEREO, // Support output in stereo mode + OFFSET, // Support to change sync offset + VOLUME_AMP, // Support volume amplification }; -enum IPlayerSubtitleCapabilities +// \brief Player Subtitle capabilities +enum class IPlayerSubtitleCaps { - IPC_SUBS_ALL, - IPC_SUBS_SELECT, - IPC_SUBS_EXTERNAL, - IPC_SUBS_OFFSET + ALL, // All capabilities supported + SELECT_STREAM, // Support to change stream + EXTERNAL, // Support to load external subtitles + OFFSET, // Support to change sync offset }; enum ERENDERFEATURE @@ -211,16 +213,20 @@ class IPlayer virtual std::string GetPlayerState() { return ""; } virtual bool SetPlayerState(const std::string& state) { return false; } - virtual void GetAudioCapabilities(std::vector<int>& audioCaps) const + /*! + * \brief Define the audio capabilities of the player + */ + virtual void GetAudioCapabilities(std::vector<IPlayerAudioCaps>& caps) const { - audioCaps.assign(1, IPC_AUD_ALL); + caps.assign(1, IPlayerAudioCaps::ALL); } + /*! - \brief define the subtitle capabilities of the player + * \brief Define the subtitle capabilities of the player */ - virtual void GetSubtitleCapabilities(std::vector<int>& subCaps) const + virtual void GetSubtitleCapabilities(std::vector<IPlayerSubtitleCaps>& caps) const { - subCaps.assign(1, IPC_SUBS_ALL); + caps.assign(1, IPlayerSubtitleCaps::ALL); } /*! diff --git a/xbmc/cores/paplayer/PAPlayer.h b/xbmc/cores/paplayer/PAPlayer.h index c2a77dd84a4a0..ae7059bfd8253 100644 --- a/xbmc/cores/paplayer/PAPlayer.h +++ b/xbmc/cores/paplayer/PAPlayer.h @@ -50,7 +50,7 @@ friend class CQueueNextFileJob; void GetAudioStreamInfo(int index, AudioStreamInfo& info) const override; void SetTime(int64_t time) override; void SeekTime(int64_t iTime = 0) override; - void GetAudioCapabilities(std::vector<int>& audioCaps) const override {} + void GetAudioCapabilities(std::vector<IPlayerAudioCaps>& caps) const override {} int GetAudioStreamCount() const override { return 1; } int GetAudioStream() override { return 0; } diff --git a/xbmc/video/dialogs/GUIDialogAudioSettings.cpp b/xbmc/video/dialogs/GUIDialogAudioSettings.cpp index 3030efaeadcff..e8371ffa3bc3e 100644 --- a/xbmc/video/dialogs/GUIDialogAudioSettings.cpp +++ b/xbmc/video/dialogs/GUIDialogAudioSettings.cpp @@ -263,7 +263,7 @@ void CGUIDialogAudioSettings::InitializeSettings() std::static_pointer_cast<CSettingControlSlider>(settingAudioVolume->GetControl())->SetFormatter(SettingFormatterPercentAsDecibel); // audio volume amplification setting - if (SupportsAudioFeature(IPC_AUD_AMP)) + if (SupportsAudioFeature(IPlayerAudioCaps::VOLUME_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); @@ -277,7 +277,7 @@ void CGUIDialogAudioSettings::InitializeSettings() } // audio delay setting - if (SupportsAudioFeature(IPC_AUD_OFFSET)) + if (SupportsAudioFeature(IPlayerAudioCaps::OFFSET)) { std::shared_ptr<CSettingNumber> settingAudioDelay = AddSlider( groupAudio, SETTING_AUDIO_DELAY, 297, SettingLevel::Basic, videoSettings.m_AudioDelay, 0, @@ -289,11 +289,11 @@ void CGUIDialogAudioSettings::InitializeSettings() } // audio stream setting - if (SupportsAudioFeature(IPC_AUD_SELECT_STREAM)) + if (SupportsAudioFeature(IPlayerAudioCaps::SELECT_STREAM)) AddAudioStreams(groupAudio, SETTING_AUDIO_STREAM); // audio digital/analog setting - if (SupportsAudioFeature(IPC_AUD_SELECT_OUTPUT)) + if (SupportsAudioFeature(IPlayerAudioCaps::SELECT_OUTPUT)) { m_passthrough = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH); AddToggle(groupAudio, SETTING_AUDIO_PASSTHROUGH, 348, SettingLevel::Basic, m_passthrough); @@ -303,11 +303,11 @@ void CGUIDialogAudioSettings::InitializeSettings() AddButton(groupSaveAsDefault, SETTING_AUDIO_MAKE_DEFAULT, 12376, SettingLevel::Basic); } -bool CGUIDialogAudioSettings::SupportsAudioFeature(int feature) +bool CGUIDialogAudioSettings::SupportsAudioFeature(IPlayerAudioCaps feature) { - for (Features::iterator itr = m_audioCaps.begin(); itr != m_audioCaps.end(); ++itr) + for (IPlayerAudioCaps cap : m_audioCaps) { - if (*itr == feature || *itr == IPC_AUD_ALL) + if (cap == feature || cap == IPlayerAudioCaps::ALL) return true; } diff --git a/xbmc/video/dialogs/GUIDialogAudioSettings.h b/xbmc/video/dialogs/GUIDialogAudioSettings.h index de69b77ae7085..d07b694e68f86 100644 --- a/xbmc/video/dialogs/GUIDialogAudioSettings.h +++ b/xbmc/video/dialogs/GUIDialogAudioSettings.h @@ -15,6 +15,7 @@ #include <utility> #include <vector> +enum class IPlayerAudioCaps; class CVariant; struct IntegerSettingOption; @@ -44,7 +45,7 @@ class CGUIDialogAudioSettings : public CGUIDialogSettingsManualBase // specialization of CGUIDialogSettingsManualBase void InitializeSettings() override; - bool SupportsAudioFeature(int feature); + bool SupportsAudioFeature(IPlayerAudioCaps feature); void AddAudioStreams(const std::shared_ptr<CSettingGroup>& group, const std::string& settingId); @@ -75,8 +76,8 @@ class CGUIDialogAudioSettings : public CGUIDialogSettingsManualBase int m_audioStream; bool m_passthrough = false; - typedef std::vector<int> Features; - Features m_audioCaps; + std::vector<IPlayerAudioCaps> m_audioCaps; + private: static std::string FormatFlags(StreamFlags flags); }; diff --git a/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp b/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp index d1c8e31524f30..20dd6d8b4b85f 100644 --- a/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp +++ b/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp @@ -297,18 +297,18 @@ void CGUIDialogSubtitleSettings::InitializeSettings() AddToggle(groupSubtitles, SETTING_SUBTITLE_ENABLE, 13397, SettingLevel::Basic, m_subtitleVisible); // subtitle delay setting - if (SupportsSubtitleFeature(IPC_SUBS_OFFSET)) + if (SupportsSubtitleFeature(IPlayerSubtitleCaps::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)) + if (SupportsSubtitleFeature(IPlayerSubtitleCaps::SELECT_STREAM)) AddSubtitleStreams(groupSubtitles, SETTING_SUBTITLE_STREAM); // subtitle browser setting - if (SupportsSubtitleFeature(IPC_SUBS_EXTERNAL)) + if (SupportsSubtitleFeature(IPlayerSubtitleCaps::EXTERNAL)) AddButton(groupSubtitles, SETTING_SUBTITLE_BROWSER, 13250, SettingLevel::Basic); AddButton(groupSubtitles, SETTING_SUBTITLE_SEARCH, 24134, SettingLevel::Basic); @@ -317,11 +317,11 @@ void CGUIDialogSubtitleSettings::InitializeSettings() AddButton(groupSaveAsDefault, SETTING_MAKE_DEFAULT, 12376, SettingLevel::Basic); } -bool CGUIDialogSubtitleSettings::SupportsSubtitleFeature(int feature) +bool CGUIDialogSubtitleSettings::SupportsSubtitleFeature(IPlayerSubtitleCaps feature) { - for (auto item : m_subtitleCapabilities) + for (IPlayerSubtitleCaps cap : m_subtitleCapabilities) { - if (item == feature || item == IPC_SUBS_ALL) + if (cap == feature || cap == IPlayerSubtitleCaps::ALL) return true; } return false; diff --git a/xbmc/video/dialogs/GUIDialogSubtitleSettings.h b/xbmc/video/dialogs/GUIDialogSubtitleSettings.h index 65216ede389c9..588973493971d 100644 --- a/xbmc/video/dialogs/GUIDialogSubtitleSettings.h +++ b/xbmc/video/dialogs/GUIDialogSubtitleSettings.h @@ -15,6 +15,7 @@ #include <utility> #include <vector> +enum class IPlayerSubtitleCaps; class CVariant; struct IntegerSettingOption; @@ -44,7 +45,7 @@ class CGUIDialogSubtitleSettings : public CGUIDialogSettingsManualBase void InitializeSettings() override; private: - bool SupportsSubtitleFeature(int feature); + bool SupportsSubtitleFeature(IPlayerSubtitleCaps feature); void AddSubtitleStreams(const std::shared_ptr<CSettingGroup>& group, const std::string& settingId); @@ -53,7 +54,7 @@ class CGUIDialogSubtitleSettings : public CGUIDialogSettingsManualBase bool m_subtitleVisible; std::shared_ptr<CSettingInt> m_subtitleStreamSetting; - std::vector<int> m_subtitleCapabilities; + std::vector<IPlayerSubtitleCaps> m_subtitleCapabilities; static std::string FormatFlags(StreamFlags flags); static void SubtitleStreamsOptionFiller(const std::shared_ptr<const CSetting>& setting, diff --git a/xbmc/video/guilib/VideoStreamSelectHelper.cpp b/xbmc/video/guilib/VideoStreamSelectHelper.cpp index a8c046eafecdb..93d7a809365f0 100644 --- a/xbmc/video/guilib/VideoStreamSelectHelper.cpp +++ b/xbmc/video/guilib/VideoStreamSelectHelper.cpp @@ -256,22 +256,23 @@ struct SortComparerStreamSubtitle } }; -bool SupportsAudioFeature(int feature, const std::vector<int>& caps) +bool SupportsAudioFeature(IPlayerAudioCaps feature, const std::vector<IPlayerAudioCaps>& caps) { - for (int item : caps) + for (IPlayerAudioCaps cap : caps) { - if (item == feature || item == IPC_AUD_ALL) + if (cap == feature || cap == IPlayerAudioCaps::ALL) return true; } return false; } -bool SupportsSubtitleFeature(int feature, const std::vector<int>& caps) +bool SupportsSubtitleFeature(IPlayerSubtitleCaps feature, + const std::vector<IPlayerSubtitleCaps>& caps) { - for (int item : caps) + for (IPlayerSubtitleCaps cap : caps) { - if (item == feature || item == IPC_SUBS_ALL) + if (cap == feature || cap == IPlayerSubtitleCaps::ALL) return true; } return false; @@ -374,9 +375,9 @@ void KODI::VIDEO::GUILIB::OpenDialogSelectAudioStream() auto& components = CServiceBroker::GetAppComponents(); auto appPlayer = components.GetComponent<CApplicationPlayer>(); - std::vector<int> caps; + std::vector<IPlayerAudioCaps> caps; appPlayer->GetAudioCapabilities(caps); - if (!SupportsAudioFeature(IPC_AUD_SELECT_STREAM, caps)) + if (!SupportsAudioFeature(IPlayerAudioCaps::SELECT_STREAM, caps)) return; const int streamCount = appPlayer->GetAudioStreamCount(); @@ -446,9 +447,9 @@ void KODI::VIDEO::GUILIB::OpenDialogSelectSubtitleStream() auto& components = CServiceBroker::GetAppComponents(); auto appPlayer = components.GetComponent<CApplicationPlayer>(); - std::vector<int> caps; + std::vector<IPlayerSubtitleCaps> caps; appPlayer->GetSubtitleCapabilities(caps); - if (!SupportsSubtitleFeature(IPC_SUBS_SELECT, caps)) + if (!SupportsSubtitleFeature(IPlayerSubtitleCaps::SELECT_STREAM, caps)) return; const int streamCount = appPlayer->GetSubtitleCount(); From 5374282863b481433828bfe06e7e1321870cc823 Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Sat, 28 Sep 2024 16:44:08 +0200 Subject: [PATCH 542/651] [keymaps] Use video/audio/subtitle selection dialogs --- system/keymaps/customcontroller.Harmony.xml | 4 ++-- system/keymaps/keyboard.xml | 8 ++++---- system/keymaps/remote.xml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/system/keymaps/customcontroller.Harmony.xml b/system/keymaps/customcontroller.Harmony.xml index 77f288dfc212f..98e3f21906d66 100644 --- a/system/keymaps/customcontroller.Harmony.xml +++ b/system/keymaps/customcontroller.Harmony.xml @@ -94,10 +94,10 @@ <!-- F8 --> <button id="196">ActivateWindow(FavouritesBrowser)</button> <!-- F9 --> <button id="173">ShowVideoMenu</button> <!-- F10 --> <button id="174">ShowSubtitles</button> - <!-- F11 --> <button id="175">NextSubtitle</button> + <!-- F11 --> <button id="175">DialogSelectSubtitle</button> <!-- F12 --> <button id="176">ActivateWindow(Videos)</button> <!-- F13 --> <button id="163">Playlist</button> - <!-- F14 --> <button id="164">AudioNextLanguage</button> + <!-- F14 --> <button id="164">DialogSelectAudio</button> <!-- Large Down --> <button id="182">PageDown</button> <!-- Large Up --> <button id="181">PageUp</button> <!-- pwrToggle --> <button id="166">ShutDown()</button> diff --git a/system/keymaps/keyboard.xml b/system/keymaps/keyboard.xml index ac0033d7f8c1b..4b69e9b8c340b 100644 --- a/system/keymaps/keyboard.xml +++ b/system/keymaps/keyboard.xml @@ -371,7 +371,7 @@ <zoom>AspectRatio</zoom> <t>ShowSubtitles</t> <t mod="ctrl">SubtitleAlign</t> - <l>NextSubtitle</l> + <l>DialogSelectSubtitle</l> <left>StepBack</left> <right>StepForward</right> <up>ChapterOrBigStepForward</up> @@ -381,11 +381,11 @@ <left mod="alt">PlayerControl(tempodown)</left> <right mod="alt">PlayerControl(tempoup)</right> <a>AudioDelay</a> - <a mod="ctrl">AudioNextLanguage</a> + <a mod="ctrl">DialogSelectAudio</a> <escape>Fullscreen</escape> <c>Playlist</c> <v>ActivateWindow(Teletext)</v> - <v mod="ctrl">VideoNextStream</v> + <v mod="ctrl">DialogSelectVideo</v> <text>ActivateWindow(Teletext)</text> <up mod="ctrl">SubtitleShiftUp</up> <down mod="ctrl">SubtitleShiftDown</down> @@ -583,7 +583,7 @@ <z>AspectRatio</z> <zoom>AspectRatio</zoom> <t>ShowSubtitles</t> - <l>NextSubtitle</l> + <l>DialogSelectSubtitle</l> <a>AudioDelay</a> <escape>Fullscreen</escape> <return>Select</return> diff --git a/system/keymaps/remote.xml b/system/keymaps/remote.xml index 5c0baf7e9d229..5306e94cb9834 100644 --- a/system/keymaps/remote.xml +++ b/system/keymaps/remote.xml @@ -198,9 +198,9 @@ <info>Info</info> <guide>ActivateWindow(TVGuide)</guide> <teletext>ActivateWindow(Teletext)</teletext> - <subtitle>NextSubtitle</subtitle> + <subtitle>DialogSelectSubtitle</subtitle> <star>NextSubtitle</star> - <language>AudioNextLanguage</language> + <language>DialogSelectAudio</language> <playlist>Playlist</playlist> <hash>AudioNextLanguage</hash> <pageplus>SkipNext</pageplus> From 193fc6a1e89db1fc86cde682753341040f4dc491 Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Fri, 11 Oct 2024 07:51:06 +0200 Subject: [PATCH 543/651] [Utils] Add missing original, imparied filename parsing --- xbmc/Util.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/xbmc/Util.cpp b/xbmc/Util.cpp index cd761165e3c2c..de9dd6f8b4b04 100644 --- a/xbmc/Util.cpp +++ b/xbmc/Util.cpp @@ -2200,6 +2200,16 @@ ExternalStreamInfo CUtil::GetExternalStreamDetailsFromFilename(const std::string info.flag |= StreamFlags::FLAG_FORCED; continue; } + else if (!flag_tmp.compare("original")) + { + info.flag |= StreamFlags::FLAG_ORIGINAL; + continue; + } + else if (!flag_tmp.compare("impaired")) + { + info.flag |= StreamFlags::FLAG_HEARING_IMPAIRED; + continue; + } if (info.language.empty()) { From a5c3649c69b009a9334435b1b08af981e402d39b Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:49:35 +0200 Subject: [PATCH 544/651] [PVR] Support to the second duration when creating time-based timers (before it was full minutes only). --- xbmc/pvr/guilib/PVRGUIActionsTimers.cpp | 4 ++-- xbmc/pvr/timers/PVRTimerInfoTag.cpp | 9 +++++---- xbmc/pvr/timers/PVRTimers.cpp | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp b/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp index 570249b12a628..582a3174f19f9 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp @@ -562,7 +562,7 @@ bool CPVRGUIActionsTimers::SetRecordingOnChannel(const std::shared_ptr<CPVRChann const std::shared_ptr<CPVRTimerInfoTag> newTimer( epgTag ? CPVRTimerInfoTag::CreateFromEpg(epgTag, false) - : CPVRTimerInfoTag::CreateInstantTimerTag(channel, iDuration)); + : CPVRTimerInfoTag::CreateInstantTimerTag(channel, iDuration * 60)); if (newTimer) bReturn = CServiceBroker::GetPVRManager().Timers()->AddTimer(newTimer); @@ -984,7 +984,7 @@ void CPVRGUIActionsTimers::AnnounceReminder(const std::shared_ptr<CPVRTimerInfoT } else { - int iDuration = (timer->EndAsUTC() - timer->StartAsUTC()).GetSecondsTotal() / 60; + const int iDuration{(timer->EndAsUTC() - timer->StartAsUTC()).GetSecondsTotal()}; newTimer = CPVRTimerInfoTag::CreateTimerTag(timer->Channel(), timer->StartAsUTC(), iDuration); } diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.cpp b/xbmc/pvr/timers/PVRTimerInfoTag.cpp index 6f5ea746eabfc..9c49d9d850a10 100644 --- a/xbmc/pvr/timers/PVRTimerInfoTag.cpp +++ b/xbmc/pvr/timers/PVRTimerInfoTag.cpp @@ -791,7 +791,7 @@ std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateFromDate( if (bInstantStart) epgTag = channel->GetEPGNow(); else if (channel->GetEPG()) - epgTag = channel->GetEPG()->GetTagBetween(start, start + CDateTimeSpan(0, 0, iDuration, 0)); + epgTag = channel->GetEPG()->GetTagBetween(start, start + CDateTimeSpan(0, 0, 0, iDuration)); } std::shared_ptr<CPVRTimerInfoTag> newTimer; @@ -853,16 +853,17 @@ std::shared_ptr<CPVRTimerInfoTag> CPVRTimerInfoTag::CreateFromDate( if (iDuration == DEFAULT_PVRRECORD_INSTANTRECORDTIME) iDuration = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( - CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME); + CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME) * + 60; if (bInstantStart) { - CDateTime endTime = now + CDateTimeSpan(0, 0, iDuration ? iDuration : 120, 0); + const CDateTime endTime{now + CDateTimeSpan(0, 0, 0, iDuration ? iDuration : 2 * 60 * 60)}; newTimer->SetEndFromUTC(endTime); } else { - CDateTime endTime = start + CDateTimeSpan(0, 0, iDuration ? iDuration : 120, 0); + const CDateTime endTime{start + CDateTimeSpan(0, 0, 0, iDuration ? iDuration : 2 * 60 * 60)}; newTimer->SetEndFromUTC(endTime); } diff --git a/xbmc/pvr/timers/PVRTimers.cpp b/xbmc/pvr/timers/PVRTimers.cpp index ccf719091aed8..a6d0ae395dc83 100644 --- a/xbmc/pvr/timers/PVRTimers.cpp +++ b/xbmc/pvr/timers/PVRTimers.cpp @@ -635,8 +635,8 @@ bool CPVRTimers::UpdateEntries(int iMaxNotificationDelay) { const CDateTimeSpan duration = timer->EndAsUTC() - timer->StartAsUTC(); const std::shared_ptr<CPVRTimerInfoTag> childTimer = - CPVRTimerInfoTag::CreateReminderFromDate( - nextStart, duration.GetSecondsTotal() / 60, timer); + CPVRTimerInfoTag::CreateReminderFromDate(nextStart, duration.GetSecondsTotal(), + timer); if (childTimer) { bChanged = true; @@ -1062,7 +1062,7 @@ bool CPVRTimers::AddLocalTimer(const std::shared_ptr<CPVRTimerInfoTag>& tag, boo { const CDateTimeSpan duration = persistedTimer->EndAsUTC() - persistedTimer->StartAsUTC(); const std::shared_ptr<CPVRTimerInfoTag> childTimer = - CPVRTimerInfoTag::CreateReminderFromDate(nextStart, duration.GetSecondsTotal() / 60, + CPVRTimerInfoTag::CreateReminderFromDate(nextStart, duration.GetSecondsTotal(), persistedTimer); if (childTimer) { From 58db23532ede2a5987091682eb0234045ff87b1d Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:06:08 +0200 Subject: [PATCH 545/651] [PVR] CPVRGUIActionsTimers::AddTimer: Fix support for EPG gap tags. --- xbmc/pvr/guilib/PVRGUIActionsTimers.cpp | 33 ++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp b/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp index 582a3174f19f9..a247d46ec5253 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp @@ -207,11 +207,17 @@ bool CPVRGUIActionsTimers::AddTimer(const CFileItem& item, ParentalCheckResult::SUCCESS) return false; + CDateTime gapStart; + int gapDuration{CPVRTimerInfoTag::DEFAULT_PVRRECORD_INSTANTRECORDTIME}; std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag(); if (epgTag) { if (epgTag->IsGapTag()) - epgTag.reset(); // for gap tags, we can only create instant timers + { + gapStart = epgTag->StartAsUTC(); + gapDuration = (epgTag->EndAsUTC() - gapStart).GetSecondsTotal(); + epgTag.reset(); // for gap tags, we can only create instant or time-based timers + } } else if (bCreateRule) { @@ -232,9 +238,28 @@ bool CPVRGUIActionsTimers::AddTimer(const CFileItem& item, return false; } - std::shared_ptr<CPVRTimerInfoTag> newTimer( - epgTag ? CPVRTimerInfoTag::CreateFromEpg(epgTag, bCreateRule) - : CPVRTimerInfoTag::CreateInstantTimerTag(channel)); + std::shared_ptr<CPVRTimerInfoTag> newTimer; + if (epgTag) + { + newTimer = CPVRTimerInfoTag::CreateFromEpg(epgTag, bCreateRule); + } + else if (gapStart.IsValid() && + gapDuration != CPVRTimerInfoTag::DEFAULT_PVRRECORD_INSTANTRECORDTIME) + { + if (gapStart <= CDateTime::GetUTCDateTime()) + gapStart = CDateTime{time_t{0}}; // special PVR addon API value for an instant recording + + // prevent super long recordings for channels without any epg data + gapDuration = std::min( + m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME) * 60, gapDuration); + + newTimer = CPVRTimerInfoTag::CreateTimerTag(channel, gapStart, gapDuration); + } + else + { + newTimer = CPVRTimerInfoTag::CreateInstantTimerTag(channel); + } + if (!newTimer) { if (bCreateRule && bFallbackToOneShotTimer) From 48cf6651503676da464c388bfb6b253b8e76eea0 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Sat, 12 Oct 2024 13:28:38 -0400 Subject: [PATCH 546/651] [UPnP] no external subs scan for url pushed to renderer --- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 3 ++- xbmc/network/upnp/UPnPRenderer.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index 1deb7d2ba7dd5..b182b6ec2041e 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -795,7 +795,8 @@ bool CVideoPlayer::OpenInputStream() // find any available external subtitles std::vector<std::string> filenames; - if (!URIUtils::IsUPnP(m_item.GetPath())) + if (!URIUtils::IsUPnP(m_item.GetPath()) && + !m_item.GetProperty("no-ext-subs-scan").asBoolean(false)) CUtil::ScanForExternalSubtitles(m_item.GetDynPath(), filenames); // load any subtitles from file item diff --git a/xbmc/network/upnp/UPnPRenderer.cpp b/xbmc/network/upnp/UPnPRenderer.cpp index acf6296d3b012..196824fbeeaf8 100644 --- a/xbmc/network/upnp/UPnPRenderer.cpp +++ b/xbmc/network/upnp/UPnPRenderer.cpp @@ -686,6 +686,7 @@ NPT_Result CUPnPRenderer::PlayMedia(const NPT_String& uri, } else { + item->SetProperty("no-ext-subs-scan", true); CFileItemList* l = new CFileItemList; //don't delete, l->Add(std::make_shared<CFileItem>(*item)); CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, -1, -1, static_cast<void*>(l)); From 583e0902e43a41eaed5df0008f2d24ec68ec2716 Mon Sep 17 00:00:00 2001 From: fritsch <Peter.Fruehberger@gmail.com> Date: Sat, 12 Oct 2024 11:06:06 +0200 Subject: [PATCH 547/651] AESinkAudioTrack: Revisit Pause bursts for RAW - Properly Check Pause Bursts again - Do not weight the average as it's meant to smooth audio hal abruptness - Stop sleeping in normal AddPacket Use-Case --- .../AudioEngine/Sinks/AESinkAUDIOTRACK.cpp | 65 +++++++------------ 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp index 149bc7a2681b5..5c29565aba28b 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp @@ -20,6 +20,8 @@ #include "platform/android/activity/XBMCApp.h" +#include <numeric> + #include <androidjni/AudioFormat.h> #include <androidjni/AudioManager.h> #include <androidjni/AudioTrack.h> @@ -768,25 +770,16 @@ void CAESinkAUDIOTRACK::GetDelay(AEDelayStatus& status) // the RAW hack for simulating pause bursts should not come // into the way of hw delay + if (m_pause_ms > m_audiotrackbuffer_sec * 1000.0) + m_pause_ms = m_audiotrackbuffer_sec * 1000.0; + if (m_pause_ms > 0.0) { - double difference = (m_audiotrackbuffer_sec - delay) * 1000; - if (usesAdvancedLogging) - { - CLog::Log(LOGINFO, "Faking Pause-Bursts in Delay - returning smoothed {} ms Original {} ms", - m_audiotrackbuffer_sec * 1000, delay * 1000); - CLog::Log(LOGINFO, "Difference: {} ms m_pause_ms {}", difference, m_pause_ms); - } - // buffer not yet reached - if (difference > 0.0) + if (delay < m_audiotrackbuffer_sec) delay = m_audiotrackbuffer_sec; else - { - CLog::Log(LOGINFO, "Resetting pause bursts as buffer level was reached! (2)"); - m_pause_ms = 0.0; - } + m_audiotrackbuffer_sec = delay; } - const double d = GetMovingAverageDelay(delay); // Audiotrack is caching more than we thought it would @@ -798,6 +791,8 @@ void CAESinkAUDIOTRACK::GetDelay(AEDelayStatus& status) if (usesAdvancedLogging) { CLog::Log(LOGINFO, "Delay Current: {:f} ms", d * 1000); + if (m_pause_ms > 0.0) + CLog::Log(LOGINFO, "Delay faked due to pause delay: {:f} ms", m_pause_ms); } status.SetDelay(d); } @@ -927,35 +922,27 @@ unsigned int CAESinkAUDIOTRACK::AddPackets(uint8_t **data, unsigned int frames, } unsigned int written_frames = static_cast<unsigned int>(written / m_format.m_frameSize); double time_to_add_ms = 1000.0 * (CurrentHostCounter() - startTime) / CurrentHostFrequency(); + // Get Back in Sync with faked pause bursts if (m_passthrough && !m_info.m_wantsIECPassthrough) { - // AT does not consume in a blocking way - it runs ahead and blocks - // exactly once with the last package for some 100 ms - double extra_sleep = 0.0; - if (time_to_add_ms < m_format.m_streamInfo.GetDuration()) - extra_sleep = (m_format.m_streamInfo.GetDuration() - time_to_add_ms) / 2; - - // if there is still place, just add it without blocking - if (m_delay < (m_audiotrackbuffer_sec - (m_format.m_streamInfo.GetDuration() / 1000.0))) - extra_sleep = 0; - if (m_pause_ms > 0.0) { - extra_sleep = 0; - m_pause_ms -= m_format.m_streamInfo.GetDuration(); + // Idea here is: Slowly correct the wrong buffer so that AE should not realize + // but do not underrun while doing so + double extra_sleep_ms = m_format.m_streamInfo.GetDuration() / 2.0 - time_to_add_ms; + if (extra_sleep_ms > 0) + { + CLog::Log(LOGDEBUG, "Sleeping for {:f}", extra_sleep_ms); + m_pause_ms -= extra_sleep_ms; + usleep(extra_sleep_ms * 1000); + } if (m_pause_ms <= 0.0) { m_pause_ms = 0.0; - CLog::Log(LOGINFO, "Resetting pause bursts as buffer level was reached! (1)"); + extra_sleep_ms = 0.0; + CLog::Log(LOGDEBUG, "Resetting pause bursts as buffer level was reached! (1)"); } } - else - { - if (m_delay > 0.3) - extra_sleep *= 2; - } - - usleep(extra_sleep * 1000); } else { @@ -969,7 +956,6 @@ unsigned int CAESinkAUDIOTRACK::AddPackets(uint8_t **data, unsigned int frames, usleep(time_off * 500); // sleep half the error on average away } } - return written_frames; } @@ -1248,7 +1234,6 @@ double CAESinkAUDIOTRACK::GetMovingAverageDelay(double newestdelay) return d; #endif - m_linearmovingaverage.push_back(newestdelay); // new values are in the back, old values are in the front @@ -1261,12 +1246,8 @@ double CAESinkAUDIOTRACK::GetMovingAverageDelay(double newestdelay) m_linearmovingaverage.pop_front(); size--; } - // m_{LWMA}^{(n)}(t) = \frac{2}{n (n+1)} \sum_{i=1}^n i \; x(t-n+i) - const double denom = 2.0 / (size * (size + 1)); - double sum = 0.0; - for (size_t i = 0; i < m_linearmovingaverage.size(); i++) - sum += (i + 1) * m_linearmovingaverage.at(i); + double sum = std::accumulate(m_linearmovingaverage.begin(), m_linearmovingaverage.end(), 0.0); - return sum * denom; + return sum / size; } From 33c70144f7dbfca50b05082bc88a9d6371e3be58 Mon Sep 17 00:00:00 2001 From: fritsch <Peter.Fruehberger@gmail.com> Date: Sat, 12 Oct 2024 11:07:29 +0200 Subject: [PATCH 548/651] AESinkAudioTrack: Stop pseudo blocking for IEC The audio hal does not care if we sleep for it, it blocks itself when the buffer is filled. --- xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp index 5c29565aba28b..875901b1583a5 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp @@ -944,18 +944,6 @@ unsigned int CAESinkAUDIOTRACK::AddPackets(uint8_t **data, unsigned int frames, } } } - else - { - // waiting should only be done if sink is not run dry - double period_time = m_format.m_frames / static_cast<double>(m_sink_sampleRate); - if (m_delay >= (m_audiotrackbuffer_sec - period_time)) - { - double time_should_ms = 1000.0 * written_frames / m_format.m_sampleRate; - double time_off = time_should_ms - time_to_add_ms; - if (time_off > 0) - usleep(time_off * 500); // sleep half the error on average away - } - } return written_frames; } From eb62ba12e1d61e037aae40f320fdd1de5807b372 Mon Sep 17 00:00:00 2001 From: fritsch <Peter.Fruehberger@gmail.com> Date: Sat, 12 Oct 2024 14:59:09 +0200 Subject: [PATCH 549/651] Use Silence again for all formats (AddPause) --- xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp index f0cce1885b641..9eb39fdc2c511 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp @@ -1207,12 +1207,7 @@ void CActiveAESink::SetSilenceTimer() m_extSilenceTimeout = XbmcThreads::EndTime<decltype(m_extSilenceTimeout)>::Max(); else if (m_extAppFocused) // handles no playback/GUI and playback in pause and seek { - // only true with AudioTrack RAW + passthrough + TrueHD - const bool noSilenceOnPause = - !m_needIecPack && m_requestedFormat.m_dataFormat == AE_FMT_RAW && - m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD; - - m_extSilenceTimeout = (noSilenceOnPause) ? 0ms : m_silenceTimeOut; + m_extSilenceTimeout = m_silenceTimeOut; } else { From 77e6d10c83a804b1152953a182464cf5ebf20581 Mon Sep 17 00:00:00 2001 From: 78andyp <drandypowell@live.co.uk> Date: Mon, 14 Oct 2024 15:17:24 +0100 Subject: [PATCH 550/651] [Art] Fix #25601 - Refresh art after update in information dialog. --- xbmc/video/VideoDatabase.cpp | 2 ++ xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 30 +++++++++++++++++------ xbmc/video/windows/GUIWindowVideoBase.cpp | 1 - 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index f333bb334bf5e..20b87249778fc 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -4994,6 +4994,8 @@ void CVideoDatabase::SetArtForItem(int mediaId, const MediaType &mediaType, cons sql = PrepareSQL("INSERT INTO art(media_id, media_type, type, url) VALUES (%d, '%s', '%s', '%s')", mediaId, mediaType.c_str(), artType.c_str(), url.c_str()); m_pDS->exec(sql); } + + AnnounceUpdate(mediaType, mediaId); } catch (...) { diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 7d93996ba5bf4..efe837a0e309f 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -863,8 +863,9 @@ void AddHardCodedAndExtendedArtTypes(std::vector<std::string>& artTypes, const C } // Add art types currently assigned to the media item -void AddCurrentArtTypes(std::vector<std::string>& artTypes, const CVideoInfoTag& tag, - CVideoDatabase& db) +void AddCurrentArtTypes(std::vector<std::string>& artTypes, + const CVideoInfoTag& tag, + CVideoDatabase& db) { std::map<std::string, std::string> currentArt; @@ -882,8 +883,9 @@ void AddCurrentArtTypes(std::vector<std::string>& artTypes, const CVideoInfoTag& } // 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) +void AddMediaTypeArtTypes(std::vector<std::string>& artTypes, + const CVideoInfoTag& tag, + CVideoDatabase& db) { std::vector<std::string> dbArtTypes; db.GetArtTypes(tag.m_type, dbArtTypes); @@ -895,8 +897,9 @@ void AddMediaTypeArtTypes(std::vector<std::string>& artTypes, const CVideoInfoTa } // Add art types from available but unassigned artwork for this media item -void AddAvailableArtTypes(std::vector<std::string>& artTypes, const CVideoInfoTag& tag, - CVideoDatabase& db) +void AddAvailableArtTypes(std::vector<std::string>& artTypes, + const CVideoInfoTag& tag, + CVideoDatabase& db) { for (const auto& artType : db.GetAvailableArtTypesForItem(tag.m_iDbId, tag.m_type)) { @@ -929,6 +932,7 @@ class CArtTypeChooser bool ChooseArtType(); const std::string& GetArtType() const { return m_artType; } + void UpdateArtType(const std::string& type, const std::string& art) const; private: std::shared_ptr<CFileItem> m_item; @@ -937,6 +941,15 @@ class CArtTypeChooser std::string m_artType; }; +void CArtTypeChooser::UpdateArtType(const std::string& type, const std::string& art) const +{ + m_item->SetArt(type, art); + if (!m_items.IsEmpty()) + for (auto& item : m_items) + if (item->GetProperty("type") == type) + item->SetArt("thumb", art); +} + bool CArtTypeChooser::ChooseArtType() { CGUIDialogSelect* dialog = @@ -1831,7 +1844,10 @@ bool CGUIDialogVideoInfo::ChooseAndManageVideoItemArtwork(const std::shared_ptr< if (!chooser.ChooseArtType()) break; - result = ManageVideoItemArtwork(item, item->GetVideoInfoTag()->m_type, chooser.GetArtType()); + const std::string chosenArtType{chooser.GetArtType()}; + result = ManageVideoItemArtwork(item, item->GetVideoInfoTag()->m_type, chosenArtType); + if (result) + chooser.UpdateArtType(chosenArtType, item->GetArt(chosenArtType)); } while (true); diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index a875ae4bfaca6..d231a809f765d 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -65,7 +65,6 @@ #include "video/guilib/VideoGUIUtils.h" #include "video/guilib/VideoPlayActionProcessor.h" #include "video/guilib/VideoSelectActionProcessor.h" -#include "video/guilib/VideoVersionHelper.h" #include "view/GUIViewState.h" #include <memory> From a6c001fa1882dfe722179e50240586a2cc6203e8 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Mon, 14 Oct 2024 11:57:22 +0100 Subject: [PATCH 551/651] [GUI] Introduce GUI Announcement Handlers --- cmake/treedata/common/subdirs.txt | 1 + xbmc/guilib/GUIComponent.cpp | 4 +- xbmc/guilib/GUIComponent.h | 2 + xbmc/guilib/handlers/CMakeLists.txt | 7 +++ .../GUIAnnouncementHandlerContainer.cpp | 16 +++++++ .../GUIAnnouncementHandlerContainer.h | 29 ++++++++++++ .../sources/GUISourcesAnnouncementHandler.cpp | 42 ++++++++++++++++++ .../sources/GUISourcesAnnouncementHandler.h | 26 +++++++++++ xbmc/interfaces/IAnnouncer.h | 44 ++++++++++--------- xbmc/network/upnp/UPnP.cpp | 21 +++------ 10 files changed, 157 insertions(+), 35 deletions(-) create mode 100644 xbmc/guilib/handlers/CMakeLists.txt create mode 100644 xbmc/guilib/handlers/GUIAnnouncementHandlerContainer.cpp create mode 100644 xbmc/guilib/handlers/GUIAnnouncementHandlerContainer.h create mode 100644 xbmc/guilib/handlers/sources/GUISourcesAnnouncementHandler.cpp create mode 100644 xbmc/guilib/handlers/sources/GUISourcesAnnouncementHandler.h diff --git a/cmake/treedata/common/subdirs.txt b/cmake/treedata/common/subdirs.txt index 00bd9396e5467..f4e46076fb449 100644 --- a/cmake/treedata/common/subdirs.txt +++ b/cmake/treedata/common/subdirs.txt @@ -16,6 +16,7 @@ xbmc/dialogs dialogs xbmc/favourites favourites xbmc/guilib guilib xbmc/guilib/guiinfo guilib_guiinfo +xbmc/guilib/handlers guilib_announcement_handlers xbmc/guilib/listproviders guilib_listproviders xbmc/imagefiles imagefiles xbmc/messaging messaging diff --git a/xbmc/guilib/GUIComponent.cpp b/xbmc/guilib/GUIComponent.cpp index 5c8b414fd8cb3..449a85d073ba0 100644 --- a/xbmc/guilib/GUIComponent.cpp +++ b/xbmc/guilib/GUIComponent.cpp @@ -18,6 +18,7 @@ #include "TextureManager.h" #include "URL.h" #include "dialogs/GUIDialogYesNo.h" +#include "handlers/GUIAnnouncementHandlerContainer.h" #include <memory> @@ -28,7 +29,8 @@ CGUIComponent::CGUIComponent() m_stereoscopicsManager(std::make_unique<CStereoscopicsManager>()), m_guiInfoManager(std::make_unique<CGUIInfoManager>()), m_guiColorManager(std::make_unique<CGUIColorManager>()), - m_guiAudioManager(std::make_unique<CGUIAudioManager>()) + m_guiAudioManager(std::make_unique<CGUIAudioManager>()), + m_announcementHandlerContainer(std::make_unique<CGUIAnnouncementHandlerContainer>()) { } diff --git a/xbmc/guilib/GUIComponent.h b/xbmc/guilib/GUIComponent.h index b7eb75c58a394..2e3e7aadff12c 100644 --- a/xbmc/guilib/GUIComponent.h +++ b/xbmc/guilib/GUIComponent.h @@ -18,6 +18,7 @@ class CStereoscopicsManager; class CGUIInfoManager; class CGUIColorManager; class CGUIAudioManager; +class CGUIAnnouncementHandlerContainer; class CGUIComponent { @@ -47,4 +48,5 @@ class CGUIComponent std::unique_ptr<CGUIInfoManager> m_guiInfoManager; std::unique_ptr<CGUIColorManager> m_guiColorManager; std::unique_ptr<CGUIAudioManager> m_guiAudioManager; + std::unique_ptr<CGUIAnnouncementHandlerContainer> m_announcementHandlerContainer; }; diff --git a/xbmc/guilib/handlers/CMakeLists.txt b/xbmc/guilib/handlers/CMakeLists.txt new file mode 100644 index 0000000000000..fc052e35dfc97 --- /dev/null +++ b/xbmc/guilib/handlers/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOURCES GUIAnnouncementHandlerContainer.cpp + sources/GUISourcesAnnouncementHandler.cpp) + +set(HEADERS GUIAnnouncementHandlerContainer.h + sources/GUISourcesAnnouncementHandler.h) + +core_add_library(guilib_announcement_handlers) diff --git a/xbmc/guilib/handlers/GUIAnnouncementHandlerContainer.cpp b/xbmc/guilib/handlers/GUIAnnouncementHandlerContainer.cpp new file mode 100644 index 0000000000000..f7a5fbae4af9a --- /dev/null +++ b/xbmc/guilib/handlers/GUIAnnouncementHandlerContainer.cpp @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2024 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 "GUIAnnouncementHandlerContainer.h" + +#include "sources/GUISourcesAnnouncementHandler.h" + +CGUIAnnouncementHandlerContainer::CGUIAnnouncementHandlerContainer() +{ + m_announcementHandlers.emplace_back(std::make_unique<CGUISourcesAnnouncementHandler>()); +} diff --git a/xbmc/guilib/handlers/GUIAnnouncementHandlerContainer.h b/xbmc/guilib/handlers/GUIAnnouncementHandlerContainer.h new file mode 100644 index 0000000000000..40122c5dbb7ff --- /dev/null +++ b/xbmc/guilib/handlers/GUIAnnouncementHandlerContainer.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 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 "interfaces/IAnnouncer.h" + +#include <memory> +#include <vector> + +/*! +\brief This class is a container of announcement handlers per application component. It allows the GUI Layer +to execute GUI Actions upon receiving announcements from other components effectively decoupling GUI +from other components. +*/ +class CGUIAnnouncementHandlerContainer final +{ +public: + CGUIAnnouncementHandlerContainer(); + ~CGUIAnnouncementHandlerContainer() = default; + +private: + std::vector<std::unique_ptr<ANNOUNCEMENT::IAnnouncer>> m_announcementHandlers; +}; diff --git a/xbmc/guilib/handlers/sources/GUISourcesAnnouncementHandler.cpp b/xbmc/guilib/handlers/sources/GUISourcesAnnouncementHandler.cpp new file mode 100644 index 0000000000000..251f0e14ec299 --- /dev/null +++ b/xbmc/guilib/handlers/sources/GUISourcesAnnouncementHandler.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 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 "GUISourcesAnnouncementHandler.h" + +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "interfaces/AnnouncementManager.h" + +CGUISourcesAnnouncementHandler::CGUISourcesAnnouncementHandler() +{ + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); +} + +CGUISourcesAnnouncementHandler::~CGUISourcesAnnouncementHandler() +{ + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); +} + +void CGUISourcesAnnouncementHandler::Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) +{ + // We are only interested in sources changes + if ((flag & ANNOUNCEMENT::Sources) == 0) + return; + + if (message == "OnAdded" || message == "OnRemoved" || message == "OnUpdated") + { + CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH); + message.SetStringParam(data.asString()); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); + } +} diff --git a/xbmc/guilib/handlers/sources/GUISourcesAnnouncementHandler.h b/xbmc/guilib/handlers/sources/GUISourcesAnnouncementHandler.h new file mode 100644 index 0000000000000..0ec166fdf1411 --- /dev/null +++ b/xbmc/guilib/handlers/sources/GUISourcesAnnouncementHandler.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 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 "interfaces/IAnnouncer.h" + +/*! +\brief Handler for announcements of type sources +*/ +class CGUISourcesAnnouncementHandler : public ANNOUNCEMENT::IAnnouncer +{ +public: + CGUISourcesAnnouncementHandler(); + ~CGUISourcesAnnouncementHandler(); + + void Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) override; +}; diff --git a/xbmc/interfaces/IAnnouncer.h b/xbmc/interfaces/IAnnouncer.h index 7c20203573a96..513fb25937e54 100644 --- a/xbmc/interfaces/IAnnouncer.h +++ b/xbmc/interfaces/IAnnouncer.h @@ -13,33 +13,35 @@ class CVariant; namespace ANNOUNCEMENT { - enum AnnouncementFlag - { - Player = 0x001, - Playlist = 0x002, - GUI = 0x004, - System = 0x008, - VideoLibrary = 0x010, - AudioLibrary = 0x020, - Application = 0x040, - Input = 0x080, - PVR = 0x100, - Other = 0x200, - Info = 0x400 - }; +enum AnnouncementFlag +{ + Player = 0x001, + Playlist = 0x002, + GUI = 0x004, + System = 0x008, + VideoLibrary = 0x010, + AudioLibrary = 0x020, + Application = 0x040, + Input = 0x080, + PVR = 0x100, + Other = 0x200, + Info = 0x400, + Sources = 0x800 +}; - const auto ANNOUNCE_ALL = (Player | Playlist | GUI | System | VideoLibrary | AudioLibrary | Application | Input | ANNOUNCEMENT::PVR | Other); +const auto ANNOUNCE_ALL = (Player | Playlist | GUI | System | VideoLibrary | AudioLibrary | + Application | Input | ANNOUNCEMENT::PVR | Other); - /*! +/*! \brief Returns a string representation for the given AnnouncementFlag \param notification Specific AnnouncementFlag \return String representation of the given AnnouncementFlag */ - inline const char *AnnouncementFlagToString(const AnnouncementFlag ¬ification) +inline const char* AnnouncementFlagToString(const AnnouncementFlag& notification) +{ + switch (notification) { - switch (notification) - { case Player: return "Player"; case Playlist: @@ -62,10 +64,12 @@ namespace ANNOUNCEMENT return "Other"; case Info: return "Info"; + case Sources: + return "Sources"; default: return "Unknown"; - } } +} class IAnnouncer { diff --git a/xbmc/network/upnp/UPnP.cpp b/xbmc/network/upnp/UPnP.cpp index 2acfc55069190..371efbace2cda 100644 --- a/xbmc/network/upnp/UPnP.cpp +++ b/xbmc/network/upnp/UPnP.cpp @@ -13,7 +13,6 @@ #include "UPnP.h" #include "FileItem.h" -#include "GUIUserMessages.h" #include "ServiceBroker.h" #include "UPnPInternal.h" #include "UPnPRenderer.h" @@ -21,8 +20,7 @@ #include "UPnPSettings.h" #include "URL.h" #include "cores/playercorefactory/PlayerCoreFactory.h" -#include "guilib/GUIComponent.h" -#include "guilib/GUIWindowManager.h" +#include "interfaces/AnnouncementManager.h" #include "messaging/ApplicationMessenger.h" #include "network/Network.h" #include "profiles/ProfileManager.h" @@ -171,19 +169,15 @@ class CMediaBrowser : public PLT_SyncMediaBrowser, public PLT_MediaContainerChan // PLT_MediaBrowser methods bool OnMSAdded(PLT_DeviceDataReference& device) override { - CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH); - message.SetStringParam("upnp://"); - CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Sources, "OnAdded", + CVariant{"upnp://"}); return PLT_SyncMediaBrowser::OnMSAdded(device); } void OnMSRemoved(PLT_DeviceDataReference& device) override { - PLT_SyncMediaBrowser::OnMSRemoved(device); - - CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH); - message.SetStringParam("upnp://"); - CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Sources, "OnRemoved", + CVariant{"upnp://"}); PLT_SyncMediaBrowser::OnMSRemoved(device); } @@ -202,9 +196,8 @@ class CMediaBrowser : public PLT_SyncMediaBrowser, public PLT_MediaContainerChan } m_logger->debug("notified container update {}", (const char*)path); - CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH); - message.SetStringParam(path.GetChars()); - CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Sources, "OnUpdated", + CVariant{path.GetChars()}); } bool MarkWatched(const CFileItem& item, const bool watched) From dab17829fb6b69bf3c96888c0c9675747384c535 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Mon, 14 Oct 2024 15:07:32 +0100 Subject: [PATCH 552/651] [Zeroconf] Use announcements instead of direct GUI messages --- xbmc/network/ZeroconfBrowser.h | 3 +-- xbmc/network/mdns/ZeroconfBrowserMDNS.cpp | 14 ++++++-------- .../android/network/ZeroconfBrowserAndroid.cpp | 13 +++++-------- .../darwin/network/ZeroconfBrowserDarwin.cpp | 13 +++++-------- 4 files changed, 17 insertions(+), 26 deletions(-) diff --git a/xbmc/network/ZeroconfBrowser.h b/xbmc/network/ZeroconfBrowser.h index 76a4439a11254..7f104eabc576c 100644 --- a/xbmc/network/ZeroconfBrowser.h +++ b/xbmc/network/ZeroconfBrowser.h @@ -89,8 +89,7 @@ class CZeroconfBrowser void Stop(); ///returns the list of found services - /// if this is updated, the following message with "zeroconf://" as path is sent: - /// CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH); + /// if this is updated, a source update announcement with "zeroconf://" as path is sent: std::vector<ZeroconfService> GetFoundServices(); ///@} diff --git a/xbmc/network/mdns/ZeroconfBrowserMDNS.cpp b/xbmc/network/mdns/ZeroconfBrowserMDNS.cpp index c4a1c1ecab115..9546dcc6b5a45 100644 --- a/xbmc/network/mdns/ZeroconfBrowserMDNS.cpp +++ b/xbmc/network/mdns/ZeroconfBrowserMDNS.cpp @@ -8,11 +8,8 @@ #include "ZeroconfBrowserMDNS.h" -#include "GUIUserMessages.h" #include "ServiceBroker.h" -#include "guilib/GUIComponent.h" -#include "guilib/GUIMessage.h" -#include "guilib/GUIWindowManager.h" +#include "interfaces/AnnouncementManager.h" #include "network/DNSNameCache.h" #include "utils/log.h" @@ -88,10 +85,11 @@ void DNSSD_API CZeroconfBrowserMDNS::BrowserCallback(DNSServiceRef browser, } if(! (flags & kDNSServiceFlagsMoreComing) ) { - CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH); - message.SetStringParam("zeroconf://"); - CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); - CLog::Log(LOGDEBUG, "ZeroconfBrowserMDNS::BrowserCallback sent gui update for path zeroconf://"); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Sources, "OnUpdated", + CVariant{"zeroconf://"}); + CLog::Log( + LOGDEBUG, + "ZeroconfBrowserMDNS::BrowserCallback sent source update announce for path zeroconf://"); } } else diff --git a/xbmc/platform/android/network/ZeroconfBrowserAndroid.cpp b/xbmc/platform/android/network/ZeroconfBrowserAndroid.cpp index dd6db2576b5a5..d31564f5ea6b7 100644 --- a/xbmc/platform/android/network/ZeroconfBrowserAndroid.cpp +++ b/xbmc/platform/android/network/ZeroconfBrowserAndroid.cpp @@ -8,11 +8,8 @@ #include "ZeroconfBrowserAndroid.h" -#include "GUIUserMessages.h" #include "ServiceBroker.h" -#include "guilib/GUIComponent.h" -#include "guilib/GUIMessage.h" -#include "guilib/GUIWindowManager.h" +#include "interfaces/AnnouncementManager.h" #include "network/DNSNameCache.h" #include "utils/log.h" @@ -241,10 +238,10 @@ void CZeroconfBrowserAndroidDiscover::onServiceFound(const jni::CJNINsdServiceIn s.GetName(), s.GetType(), s.GetDomain()); m_browser->addDiscoveredService(this, s); - CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH); - message.SetStringParam("zeroconf://"); - CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); - CLog::Log(LOGDEBUG, "CZeroconfBrowserAndroidDiscover::onServiceFound sent gui update for path zeroconf://"); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Sources, "OnUpdated", + CVariant{"zeroconf://"}); + CLog::Log(LOGDEBUG, "CZeroconfBrowserAndroidDiscover::onServiceFound sent source update announce " + "for path zeroconf://"); } void CZeroconfBrowserAndroidDiscover::onServiceLost(const jni::CJNINsdServiceInfo& serviceInfo) diff --git a/xbmc/platform/darwin/network/ZeroconfBrowserDarwin.cpp b/xbmc/platform/darwin/network/ZeroconfBrowserDarwin.cpp index 50515d0879855..e2078a352054a 100644 --- a/xbmc/platform/darwin/network/ZeroconfBrowserDarwin.cpp +++ b/xbmc/platform/darwin/network/ZeroconfBrowserDarwin.cpp @@ -8,11 +8,8 @@ #include "ZeroconfBrowserDarwin.h" -#include "GUIUserMessages.h" #include "ServiceBroker.h" -#include "guilib/GUIComponent.h" -#include "guilib/GUIMessage.h" -#include "guilib/GUIWindowManager.h" +#include "interfaces/AnnouncementManager.h" #include "utils/log.h" #include "platform/darwin/DarwinUtils.h" @@ -168,10 +165,10 @@ void CZeroconfBrowserDarwin::BrowserCallback(CFNetServiceBrowserRef browser, CFO } if (! (flags & kCFNetServiceFlagMoreComing) ) { - CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH); - message.SetStringParam("zeroconf://"); - CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); - CLog::Log(LOGDEBUG, "CZeroconfBrowserDarwin::BrowserCallback sent gui update for path zeroconf://"); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Sources, "OnUpdated", + CVariant{"zeroconf://"}); + CLog::Log(LOGDEBUG, "CZeroconfBrowserDarwin::BrowserCallback sent sources update " + "announcement for path zeroconf://"); } } else { From 2aba1fc4723e908fe75cbc261e71d6e313744c2b Mon Sep 17 00:00:00 2001 From: phunkyfish <phunkyfish@gmail.com> Date: Tue, 15 Oct 2024 17:03:49 +0100 Subject: [PATCH 553/651] [addons] compare file open flags to addon enum instead of Kodi core's --- xbmc/addons/interfaces/Filesystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/addons/interfaces/Filesystem.cpp b/xbmc/addons/interfaces/Filesystem.cpp index 9f22b67ecce0b..1056807056672 100644 --- a/xbmc/addons/interfaces/Filesystem.cpp +++ b/xbmc/addons/interfaces/Filesystem.cpp @@ -151,7 +151,7 @@ unsigned int Interface_Filesystem::TranslateFileReadBitsToKodi(unsigned int addo kodiFlags |= READ_AUDIO_VIDEO; if (addonFlags & ADDON_READ_AFTER_WRITE) kodiFlags |= READ_AFTER_WRITE; - if (addonFlags & READ_REOPEN) + if (addonFlags & ADDON_READ_REOPEN) kodiFlags |= READ_REOPEN; return kodiFlags; From 553a17fd3738756f27a413543d31e59ff9ad0d4d Mon Sep 17 00:00:00 2001 From: phunkyfish <phunkyfish@gmail.com> Date: Tue, 15 Oct 2024 16:42:00 +0100 Subject: [PATCH 554/651] [addons] add missing flag to filesystem translation from binary add-ons --- xbmc/addons/interfaces/Filesystem.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xbmc/addons/interfaces/Filesystem.cpp b/xbmc/addons/interfaces/Filesystem.cpp index 1056807056672..eb35cb7b5411e 100644 --- a/xbmc/addons/interfaces/Filesystem.cpp +++ b/xbmc/addons/interfaces/Filesystem.cpp @@ -153,6 +153,9 @@ unsigned int Interface_Filesystem::TranslateFileReadBitsToKodi(unsigned int addo kodiFlags |= READ_AFTER_WRITE; if (addonFlags & ADDON_READ_REOPEN) kodiFlags |= READ_REOPEN; + //! @todo Add ADDON_READ_NO_BUFFER to filesystem.h in the binary addon devkit + if (addonFlags & READ_NO_BUFFER) + kodiFlags |= READ_NO_BUFFER; return kodiFlags; } From f8e6acb4eb50ad87a15dfa1f375c28edfed7cd33 Mon Sep 17 00:00:00 2001 From: fritsch <Peter.Fruehberger@gmail.com> Date: Wed, 16 Oct 2024 19:38:18 +0200 Subject: [PATCH 555/651] MediaSession: Properly update when pressing stop --- xbmc/platform/android/activity/XBMCApp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/platform/android/activity/XBMCApp.cpp b/xbmc/platform/android/activity/XBMCApp.cpp index 10e00d014653d..da9c31456aa25 100644 --- a/xbmc/platform/android/activity/XBMCApp.cpp +++ b/xbmc/platform/android/activity/XBMCApp.cpp @@ -919,9 +919,9 @@ void CXBMCApp::OnPlayBackStopped() CLog::Log(LOGDEBUG, "{}", __PRETTY_FUNCTION__); m_playback_state = PLAYBACK_STATE_STOPPED; + m_mediaSessionUpdated = false; UpdateSessionState(); m_mediaSession->activate(false); - m_mediaSessionUpdated = false; RequestVisibleBehind(false); CAndroidKey::SetHandleMediaKeys(true); From fe88fd5a9c7726f8ec596c20fc78e44bead9c2e7 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 18 Oct 2024 08:08:20 +0200 Subject: [PATCH 556/651] [PVR] CAddonTimer: Fix memory corruption caused by freed string data transferred to add-on. --- xbmc/pvr/addons/PVRClient.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 8d56392567426..4e1fca84b91e8 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -269,7 +269,8 @@ class CAddonTimer : public PVR_TIMER prop.iKey = entry.first; prop.eType = entry.second.type; prop.iValue = entry.second.value.asInteger32(); - prop.strValue = entry.second.value.asString().c_str(); + m_customPropStringValues.emplace_back(entry.second.value.asString()); + prop.strValue = m_customPropStringValues.back().c_str(); ++idx; } customProps = m_customProps.get(); @@ -283,6 +284,7 @@ class CAddonTimer : public PVR_TIMER const std::string m_directory; const std::string m_summary; const std::string m_seriesLink; + std::vector<std::string> m_customPropStringValues; std::unique_ptr<PVR_SETTING_KEY_VALUE_PAIR[]> m_customProps; }; From 9949e67f219095f4fef835760041fa8e3ac32a81 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti <joseluis.marti@gmail.com> Date: Fri, 18 Oct 2024 18:18:03 +0200 Subject: [PATCH 557/651] The requestVisibleBehind() method has no effect since API level 26 --- xbmc/platform/android/activity/XBMCApp.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xbmc/platform/android/activity/XBMCApp.cpp b/xbmc/platform/android/activity/XBMCApp.cpp index 10e00d014653d..19aecc5c97857 100644 --- a/xbmc/platform/android/activity/XBMCApp.cpp +++ b/xbmc/platform/android/activity/XBMCApp.cpp @@ -631,7 +631,9 @@ void CXBMCApp::RequestVisibleBehind(bool requested) if (requested == m_hasReqVisible) return; - m_hasReqVisible = requestVisibleBehind(requested); + if (CJNIBuild::SDK_INT < 26) + m_hasReqVisible = requestVisibleBehind(requested); + CLog::Log(LOGDEBUG, "Visible Behind request: {}", m_hasReqVisible ? "true" : "false"); } From 0883d7de4991ee3498a903baadb2a8d79f20d706 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 19 Oct 2024 09:15:18 +0200 Subject: [PATCH 558/651] [cores] CInputStreamPVRBase: Optimize: Only close the stream if it is open. Only open the stream if it is not open. --- .../DVDInputStreams/InputStreamPVRBase.cpp | 13 +++++++++---- .../DVDInputStreams/InputStreamPVRBase.h | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp index 1f1622318d032..7b8be41763231 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp @@ -40,8 +40,9 @@ bool CInputStreamPVRBase::IsEOF() bool CInputStreamPVRBase::Open() { - if (CDVDInputStream::Open() && OpenPVRStream()) + if (!m_isOpen && CDVDInputStream::Open() && OpenPVRStream()) { + m_isOpen = true; m_eof = false; m_StreamProps->iStreamCount = 0; return true; @@ -54,9 +55,13 @@ bool CInputStreamPVRBase::Open() void CInputStreamPVRBase::Close() { - ClosePVRStream(); - CDVDInputStream::Close(); - m_eof = true; + if (m_isOpen) + { + ClosePVRStream(); + CDVDInputStream::Close(); + m_eof = true; + m_isOpen = false; + } } int CInputStreamPVRBase::Read(uint8_t* buf, int buf_size) diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h index 67b5d32d1cbf5..7a2d33651236b 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h @@ -80,4 +80,5 @@ class CInputStreamPVRBase std::shared_ptr<PVR_STREAM_PROPERTIES> m_StreamProps; std::map<int, std::shared_ptr<CDemuxStream>> m_streamMap; std::shared_ptr<PVR::CPVRClient> m_client; + bool m_isOpen{false}; }; From 7dbc4e2e5b9d7fe9ee6c21ac4f37c811987290e5 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 19 Oct 2024 13:12:59 +0200 Subject: [PATCH 559/651] [Android] Fix resuming paused media playback not working via play/pause media key press. --- xbmc/platform/android/activity/XBMCApp.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xbmc/platform/android/activity/XBMCApp.cpp b/xbmc/platform/android/activity/XBMCApp.cpp index 10e00d014653d..429a11fa06875 100644 --- a/xbmc/platform/android/activity/XBMCApp.cpp +++ b/xbmc/platform/android/activity/XBMCApp.cpp @@ -220,6 +220,11 @@ void CXBMCApp::Announce(ANNOUNCEMENT::AnnouncementFlag flag, m_mediaSessionUpdated = false; UpdateSessionState(); } + else if (message == "OnAVStart") + { + m_mediaSessionUpdated = false; + UpdateSessionState(); + } } else if (flag & Info) { From 846a98dc02532f4ebb3e0ea502c26e93e4c77f88 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 19 Oct 2024 16:41:27 +0200 Subject: [PATCH 560/651] [PVR] CAddEpgTag, CAddonRecording: Add missing init of some parental rating data members. --- xbmc/pvr/addons/PVRClient.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 4e1fca84b91e8..dd868f6292e29 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -133,9 +133,9 @@ class CAddonRecording : public PVR_RECORDING m_fanartPath(recording.ClientFanartPath()), m_firstAired(recording.FirstAired().IsValid() ? recording.FirstAired().GetAsW3CDate() : ""), m_providerName(recording.ProviderName()), - m_parentalRatingCode(""), //! @todo - m_parentalRatingIcon(""), //! @todo - m_parentalRatingSource("") //! @todo + m_parentalRatingCode(recording.GetParentalRatingCode()), + m_parentalRatingIcon(recording.GetParentalRatingIcon()), + m_parentalRatingSource(recording.GetParentalRatingSource()) { // zero-init base struct members PVR_RECORDING* base = static_cast<PVR_RECORDING*>(this); @@ -306,8 +306,8 @@ class CAddonEpgTag : public EPG_TAG m_genreDescription(tag.GenreDescription()), m_firstAired(GetFirstAired(tag)), m_parentalRatingCode(tag.ParentalRatingCode()), - m_parentalRatingIcon(""), //! @todo - m_parentalRatingSource("") //! @todo + m_parentalRatingIcon(tag.ParentalRatingIcon()), + m_parentalRatingSource(tag.ParentalRatingSource()) { // zero-init base struct members EPG_TAG* base = static_cast<EPG_TAG*>(this); From f48cefb0e6268c439dcbd8fa0826caf90d903178 Mon Sep 17 00:00:00 2001 From: sarbes <sarbes@kodi.tv> Date: Sun, 20 Oct 2024 08:47:19 +0200 Subject: [PATCH 561/651] Fix include for GCC14 (#25856) --- xbmc/cores/VideoPlayer/Interface/StreamInfo.h | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/cores/VideoPlayer/Interface/StreamInfo.h b/xbmc/cores/VideoPlayer/Interface/StreamInfo.h index 924931e9b6583..9f93e86b7f2c6 100644 --- a/xbmc/cores/VideoPlayer/Interface/StreamInfo.h +++ b/xbmc/cores/VideoPlayer/Interface/StreamInfo.h @@ -10,6 +10,7 @@ #include "utils/Geometry.h" +#include <cstdint> #include <string> template <typename T> class CRectGen; From e79f6ee14cb49b3828fa10af00e4c168bf4caeab Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sun, 20 Oct 2024 08:27:41 +0200 Subject: [PATCH 562/651] Fix art announce update after #25812 --- xbmc/video/VideoDatabase.cpp | 7 +++++-- xbmc/video/VideoDatabase.h | 2 ++ xbmc/video/VideoItemArtworkHandler.cpp | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 20b87249778fc..a610dbefa6c75 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -4956,6 +4956,11 @@ void CVideoDatabase::SetVideoSettings(int idFile, const CVideoSettings &setting) } } +void CVideoDatabase::UpdateArtForItem(int mediaId, const MediaType& mediaType) +{ + AnnounceUpdate(mediaType, mediaId); +} + void CVideoDatabase::SetArtForItem(int mediaId, const MediaType &mediaType, const std::map<std::string, std::string> &art) { for (const auto &i : art) @@ -4994,8 +4999,6 @@ void CVideoDatabase::SetArtForItem(int mediaId, const MediaType &mediaType, cons sql = PrepareSQL("INSERT INTO art(media_id, media_type, type, url) VALUES (%d, '%s', '%s', '%s')", mediaId, mediaType.c_str(), artType.c_str(), url.c_str()); m_pDS->exec(sql); } - - AnnounceUpdate(mediaType, mediaId); } catch (...) { diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index e8de69b88c057..2c8ea6357de4f 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -984,6 +984,8 @@ class CVideoDatabase : public CDatabase bool GetArtForItem(int mediaId, const MediaType &mediaType, std::map<std::string, std::string> &art); std::string GetArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType); + void UpdateArtForItem(int mediaId, const MediaType& mediaType); + /*! * \brief Retrieve all art for the given video asset, with optional fallback to the art of the * parent/owner of the asset diff --git a/xbmc/video/VideoItemArtworkHandler.cpp b/xbmc/video/VideoItemArtworkHandler.cpp index aee0401075d21..28fa95e504976 100644 --- a/xbmc/video/VideoItemArtworkHandler.cpp +++ b/xbmc/video/VideoItemArtworkHandler.cpp @@ -141,6 +141,8 @@ void CVideoItemArtworkHandler::PersistArt(const std::string& art) videodb.SetArtForItem(m_item->GetVideoInfoTag()->m_iDbId, m_item->GetVideoInfoTag()->m_type, m_artType, art); + + videodb.UpdateArtForItem(m_item->GetVideoInfoTag()->m_iDbId, m_artType); } void CVideoItemArtworkHandler::AddItemPathStringToFileBrowserSources( From 2df85a14adff4f2e1832a7d5fa025e90c9b87259 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti <joseluis.marti@gmail.com> Date: Sun, 20 Oct 2024 21:51:05 +0200 Subject: [PATCH 563/651] [videoplayer] Fix warning message MSGQ_NOT_INITIALIZED --- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index 5a20ca0fffc67..fe99187dad65b 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -5170,8 +5170,10 @@ void CVideoPlayer::UpdateVideoRender(bool video) void CVideoPlayer::OnLostDisplay() { CLog::Log(LOGINFO, "VideoPlayer: OnLostDisplay received"); - m_VideoPlayerAudio->SendMessage(std::make_shared<CDVDMsgBool>(CDVDMsg::GENERAL_PAUSE, true), 1); - m_VideoPlayerVideo->SendMessage(std::make_shared<CDVDMsgBool>(CDVDMsg::GENERAL_PAUSE, true), 1); + if (m_VideoPlayerAudio->IsInited()) + m_VideoPlayerAudio->SendMessage(std::make_shared<CDVDMsgBool>(CDVDMsg::GENERAL_PAUSE, true), 1); + if (m_VideoPlayerVideo->IsInited()) + m_VideoPlayerVideo->SendMessage(std::make_shared<CDVDMsgBool>(CDVDMsg::GENERAL_PAUSE, true), 1); m_clock.Pause(true); m_displayLost = true; FlushRenderer(); From 4ff0ba903bed472cddb0d6e5c53c8176cded6b09 Mon Sep 17 00:00:00 2001 From: Rudi Heitbaum <rudi@heitbaum.com> Date: Mon, 21 Oct 2024 22:10:29 +1100 Subject: [PATCH 564/651] [swig] Fix building with swig 4.3.0 swig 4.3.0 has dropped the -xmllang option used with -xml, which had no effect on the output. Ref: - https://github.com/swig/swig/commit/86498e46c6a6218a3d091c12513c40076ac2ce63 --- xbmc/interfaces/swig/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/interfaces/swig/CMakeLists.txt b/xbmc/interfaces/swig/CMakeLists.txt index 1951b2a336789..46c84c7f7efc5 100644 --- a/xbmc/interfaces/swig/CMakeLists.txt +++ b/xbmc/interfaces/swig/CMakeLists.txt @@ -22,7 +22,7 @@ function(generate_file file) add_custom_command(OUTPUT ${CPP_FILE} COMMAND ${SWIG_EXECUTABLE} - ARGS -w401 -c++ -o ${file}.xml -xml -I${CMAKE_SOURCE_DIR}/xbmc -xmllang python ${CMAKE_CURRENT_SOURCE_DIR}/../swig/${file} + ARGS -w401 -c++ -o ${file}.xml -xml -I${CMAKE_SOURCE_DIR}/xbmc ${CMAKE_CURRENT_SOURCE_DIR}/../swig/${file} COMMAND ${Java_JAVA_EXECUTABLE} ARGS ${JAVA_OPEN_OPTS} -cp "${classpath}" groovy.ui.GroovyMain ${CMAKE_SOURCE_DIR}/tools/codegenerator/Generator.groovy ${file}.xml ${CMAKE_CURRENT_SOURCE_DIR}/../python/PythonSwig.cpp.template ${file}.cpp > ${devnull} ${CLANG_FORMAT_COMMAND} From 07cd71ae9c893809d98494563739223e9c4c40b4 Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Mon, 21 Oct 2024 14:51:28 +0200 Subject: [PATCH 565/651] [tests] curl test to preserve slashes between protocol and path --- xbmc/test/TestURL.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xbmc/test/TestURL.cpp b/xbmc/test/TestURL.cpp index e74d3be009a61..16971eebf57d5 100644 --- a/xbmc/test/TestURL.cpp +++ b/xbmc/test/TestURL.cpp @@ -70,3 +70,10 @@ const TestURLGetWithoutUserDetailsData values[] = { }; INSTANTIATE_TEST_SUITE_P(URL, TestURLGetWithoutUserDetails, ValuesIn(values)); + +TEST(TestURLGetWithoutOptions, PreserveSlashesBetweenProtocolAndPath) +{ + std::string url{"https://example.com//stream//example/index.m3u8"}; + CURL input{url}; + EXPECT_EQ(input.GetWithoutOptions(), url); +} From 32ac6f34784cbd9ad62dbbdec382079bd908972c Mon Sep 17 00:00:00 2001 From: 78andyp <drandypowell@live.co.uk> Date: Mon, 21 Oct 2024 19:33:57 +0100 Subject: [PATCH 566/651] [Password] Fix #25826 - Improve media lock checks (including recently added videos and json-rpc). --- xbmc/GUIPassword.cpp | 26 +++++++++++++++++++++ xbmc/GUIPassword.h | 2 ++ xbmc/PlayListPlayer.cpp | 15 +++++++++++- xbmc/interfaces/builtins/PlayerBuiltins.cpp | 10 +++++++- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/xbmc/GUIPassword.cpp b/xbmc/GUIPassword.cpp index 2a0d5ec38e3a5..07b536756822a 100644 --- a/xbmc/GUIPassword.cpp +++ b/xbmc/GUIPassword.cpp @@ -479,6 +479,32 @@ bool CGUIPassword::CheckMenuLock(int iWindowID) return true; } +bool CGUIPassword::IsVideoUnlocked() +{ + const auto profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + const bool isLocked{profileManager->GetCurrentProfile().videoLocked()}; + if (!isLocked && !m_strMediaSourcePath.empty()) // check mediasource by path + return g_passwordManager.IsMediaPathUnlocked(profileManager, "video"); + + if (isLocked) + return IsMasterLockUnlocked(true); //Now let's check the PW if we need! + return true; +} + +bool CGUIPassword::IsMusicUnlocked() +{ + const auto profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + const bool isLocked{profileManager->GetCurrentProfile().musicLocked()}; + if (!isLocked && !m_strMediaSourcePath.empty()) // check mediasource by path + return g_passwordManager.IsMediaPathUnlocked(profileManager, "music"); + + if (isLocked) + return IsMasterLockUnlocked(true); //Now let's check the PW if we need! + return true; +} + bool CGUIPassword::LockSource(const std::string& strType, const std::string& strName, bool bState) { VECSOURCES* pShares = CMediaSourceSettings::GetInstance().GetSources(strType); diff --git a/xbmc/GUIPassword.h b/xbmc/GUIPassword.h index d2782485c5138..38e32000c6e37 100644 --- a/xbmc/GUIPassword.h +++ b/xbmc/GUIPassword.h @@ -60,6 +60,8 @@ class CGUIPassword : public ISettingCallback */ bool CheckSettingLevelLock(const SettingLevel& level, bool enforce = false); bool CheckMenuLock(int iWindowID); + bool IsVideoUnlocked(); + bool IsMusicUnlocked(); bool SetMasterLockMode(bool bDetails=true); bool LockSource(const std::string& strType, const std::string& strName, bool bState); void LockSources(bool lock); diff --git a/xbmc/PlayListPlayer.cpp b/xbmc/PlayListPlayer.cpp index 8d45e7f611bb8..f9aba3373345e 100644 --- a/xbmc/PlayListPlayer.cpp +++ b/xbmc/PlayListPlayer.cpp @@ -10,6 +10,7 @@ #include "FileItem.h" #include "FileItemList.h" +#include "GUIPassword.h" #include "GUIUserMessages.h" #include "PartyModeManager.h" #include "ServiceBroker.h" @@ -983,8 +984,20 @@ void PLAYLIST::CPlayListPlayer::OnApplicationMessage(KODI::MESSAGING::ThreadMess { return; } - if (MUSIC::IsAudio(*item) || IsVideo(*item)) + const bool isVideo{VIDEO::IsVideo(*item)}; + const bool isAudio{MUSIC::IsAudio(*item)}; + if (isAudio || isVideo) + { + if ((isVideo && !g_passwordManager.IsVideoUnlocked()) || + (isAudio && !g_passwordManager.IsMusicUnlocked())) + { + CLog::LogF(LOGERROR, + "MasterCode or MediaSource-code is wrong: {} will not be played.", + item->GetPath()); + return; + } Play(item, pMsg->strParam); + } else g_application.PlayMedia(*item, pMsg->strParam, playlistId); } diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.cpp b/xbmc/interfaces/builtins/PlayerBuiltins.cpp index 28b54ecd78d8c..fcface1ddb0d2 100644 --- a/xbmc/interfaces/builtins/PlayerBuiltins.cpp +++ b/xbmc/interfaces/builtins/PlayerBuiltins.cpp @@ -10,6 +10,7 @@ #include "FileItem.h" #include "FileItemList.h" +#include "GUIPassword.h" #include "GUIUserMessages.h" #include "PartyModeManager.h" #include "PlayListPlayer.h" @@ -17,7 +18,6 @@ #include "ServiceBroker.h" #include "Util.h" #include "application/Application.h" -#include "application/ApplicationComponents.h" #include "application/ApplicationPlayer.h" #include "application/ApplicationPowerHandling.h" #include "guilib/GUIComponent.h" @@ -474,6 +474,14 @@ int PlayOrQueueMedia(const std::vector<std::string>& params, bool forcePlay) // need some extended item properties to process resume successfully. Load them. item.LoadDetails(); + if ((VIDEO::IsVideo(item) && !g_passwordManager.IsVideoUnlocked()) || + (MUSIC::IsAudio(item) && !g_passwordManager.IsMusicUnlocked())) + { + CLog::LogF(LOGERROR, "MasterCode or MediaSource-code is wrong: {} will not be played.", + item.GetPath()); + return false; + } + // ask if we need to check guisettings to resume bool askToResume = true; int playOffset = 0; From 9a6358ee823a92a2126354e0e579965c773cdff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= <markus.haerer@gmx.net> Date: Tue, 22 Oct 2024 01:34:55 +0200 Subject: [PATCH 567/651] Revert "Merge pull request #24431 from hbiyik/gbm_drm_dynamic_afbc_video_planes" This reverts commit bf45dda56f7db137303db478073c25e5e0a99cbb, reversing changes made to b4e65aaef8ce82de38caaa3185fa4dad16bc93c2. --- .../VideoPlayer/Buffers/VideoBufferDRMPRIME.h | 2 - .../DVDCodecs/Video/DVDVideoCodec.cpp | 2 - .../DVDCodecs/Video/DVDVideoCodec.h | 2 - .../DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp | 2 - .../HwDecRender/RendererDRMPRIME.cpp | 2 +- .../HwDecRender/VideoLayerBridgeDRMPRIME.cpp | 11 +- xbmc/windowing/gbm/drm/DRMObject.cpp | 30 ---- xbmc/windowing/gbm/drm/DRMObject.h | 3 - xbmc/windowing/gbm/drm/DRMUtils.cpp | 169 ++++++------------ xbmc/windowing/gbm/drm/DRMUtils.h | 5 +- 10 files changed, 65 insertions(+), 163 deletions(-) diff --git a/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h index dca6e821775d0..b83ee8ca68a5f 100644 --- a/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h +++ b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h @@ -54,8 +54,6 @@ class CVideoBufferDRMPRIME : public CVideoBuffer virtual const VideoPicture& GetPicture() const { return m_picture; } virtual uint32_t GetWidth() const { return GetPicture().iWidth; } virtual uint32_t GetHeight() const { return GetPicture().iHeight; } - virtual uint32_t GetXOffset() const { return GetPicture().m_xOffset; } - virtual uint32_t GetYOffset() const { return GetPicture().m_yOffset; } virtual AVDRMFrameDescriptor* GetDescriptor() const = 0; virtual bool IsValid() const { return true; } diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp index a5468d12a0986..495d6a25f5813 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp @@ -58,8 +58,6 @@ void VideoPicture::Reset() iWidth = 0; iHeight = 0; - m_xOffset = 0; - m_yOffset = 0; iDisplayWidth = 0; iDisplayHeight = 0; } diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h index f3373612e876e..ca83b1a04fb47 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h @@ -75,8 +75,6 @@ struct VideoPicture unsigned int iWidth; unsigned int iHeight; - unsigned int m_xOffset{0}; - unsigned int m_yOffset{0}; unsigned int iDisplayWidth; //< width of the picture without black bars unsigned int iDisplayHeight; //< height of the picture without black bars diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp index 0d407043dda64..eb2943bb8ccb9 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp @@ -505,8 +505,6 @@ void CDVDVideoCodecDRMPRIME::SetPictureParams(VideoPicture* pVideoPicture) { pVideoPicture->iWidth = m_pFrame->width; pVideoPicture->iHeight = m_pFrame->height; - pVideoPicture->m_xOffset = m_pFrame->crop_left; - pVideoPicture->m_yOffset = m_pFrame->crop_top; double aspect_ratio = 0; AVRational pixel_aspect = m_pFrame->sample_aspect_ratio; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp index 2ef7f4d521a94..66df0e49c7355 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp @@ -77,7 +77,7 @@ CBaseRenderer* CRendererDRMPRIME::Create(CVideoBuffer* buffer) if (!plane) return nullptr; - if (!drm->FindVideoPlane(format, modifier)) + if (!plane->SupportsFormatAndModifier(format, modifier)) return nullptr; return new CRendererDRMPRIME(); diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp index 33db29b1408c2..34d1ab6235591 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp @@ -118,10 +118,9 @@ bool CVideoLayerBridgeDRMPRIME::Map(CVideoBufferDRMPRIME* buffer) flags = DRM_MODE_FB_MODIFIERS; // add the video frame FB - ret = drmModeAddFB2WithModifiers(m_DRM->GetFileDescriptor(), - buffer->GetWidth() + buffer->GetXOffset(), - buffer->GetHeight() + buffer->GetYOffset(), layer->format, - handles, pitches, offsets, modifier, &buffer->m_fb_id, flags); + ret = drmModeAddFB2WithModifiers(m_DRM->GetFileDescriptor(), buffer->GetWidth(), + buffer->GetHeight(), layer->format, handles, pitches, offsets, + modifier, &buffer->m_fb_id, flags); if (ret < 0) { CLog::Log(LOGERROR, "CVideoLayerBridgeDRMPRIME::{} - failed to add fb {}, ret = {}", @@ -189,8 +188,8 @@ void CVideoLayerBridgeDRMPRIME::SetVideoPlane(CVideoBufferDRMPRIME* buffer, cons auto plane = m_DRM->GetVideoPlane(); m_DRM->AddProperty(plane, "FB_ID", buffer->m_fb_id); m_DRM->AddProperty(plane, "CRTC_ID", m_DRM->GetCrtc()->GetCrtcId()); - m_DRM->AddProperty(plane, "SRC_X", buffer->GetXOffset() << 16); - m_DRM->AddProperty(plane, "SRC_Y", buffer->GetYOffset() << 16); + m_DRM->AddProperty(plane, "SRC_X", 0); + m_DRM->AddProperty(plane, "SRC_Y", 0); m_DRM->AddProperty(plane, "SRC_W", buffer->GetWidth() << 16); m_DRM->AddProperty(plane, "SRC_H", buffer->GetHeight() << 16); m_DRM->AddProperty(plane, "CRTC_X", static_cast<int32_t>(destRect.x1) & ~1); diff --git a/xbmc/windowing/gbm/drm/DRMObject.cpp b/xbmc/windowing/gbm/drm/DRMObject.cpp index 99dda24490a66..5ffce40fa31df 100644 --- a/xbmc/windowing/gbm/drm/DRMObject.cpp +++ b/xbmc/windowing/gbm/drm/DRMObject.cpp @@ -105,25 +105,6 @@ std::optional<uint64_t> CDRMObject::GetPropertyValue(std::string_view name, return {}; } -std::optional<std::span<uint64_t, 2>> CDRMObject::GetRangePropertyLimits(std::string_view name) -{ - auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(), - [&name](const auto& prop) { return prop->name == name; }); - - if (property == m_propsInfo.end()) - return {}; - - auto prop = property->get(); - - if (!static_cast<bool>(drm_property_type_is(prop, DRM_MODE_PROP_RANGE))) - return {}; - - if (prop->count_values != 2) - return {}; - - return std::make_optional<std::span<uint64_t, 2>>(prop->values, 2); -} - bool CDRMObject::SetProperty(const std::string& name, uint64_t value) { auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(), @@ -149,14 +130,3 @@ bool CDRMObject::SupportsProperty(const std::string& name) return false; } - -std::optional<bool> CDRMObject::IsPropertyImmutable(std::string_view name) -{ - auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(), - [&name](const auto& prop) { return prop->name == name; }); - - if (property == m_propsInfo.end()) - return {}; - - return static_cast<bool>(drm_property_type_is(property->get(), DRM_MODE_PROP_IMMUTABLE)); -} diff --git a/xbmc/windowing/gbm/drm/DRMObject.h b/xbmc/windowing/gbm/drm/DRMObject.h index 39ba28a004d7f..c4200b1a864dc 100644 --- a/xbmc/windowing/gbm/drm/DRMObject.h +++ b/xbmc/windowing/gbm/drm/DRMObject.h @@ -12,7 +12,6 @@ #include <cstdint> #include <memory> #include <optional> -#include <span> #include <string_view> #include <vector> @@ -41,8 +40,6 @@ class CDRMObject bool SetProperty(const std::string& name, uint64_t value); bool SupportsProperty(const std::string& name); - std::optional<bool> IsPropertyImmutable(std::string_view name); - std::optional<std::span<uint64_t, 2>> GetRangePropertyLimits(std::string_view name); protected: explicit CDRMObject(int fd); diff --git a/xbmc/windowing/gbm/drm/DRMUtils.cpp b/xbmc/windowing/gbm/drm/DRMUtils.cpp index 22db758ab6bb3..3dd4ee97831d3 100644 --- a/xbmc/windowing/gbm/drm/DRMUtils.cpp +++ b/xbmc/windowing/gbm/drm/DRMUtils.cpp @@ -181,130 +181,77 @@ bool CDRMUtils::FindPreferredMode() return true; } -bool CDRMUtils::FindGuiPlane() +bool CDRMUtils::FindPlanes() { - /* find the gui plane which support ARGB and 8bit or 10 bit XRGB - * prefer the one which does not support NV12, because it can be re-used in future for video - * prefer the highest id number because they are listed on top where zpos is not available - * and use the gui plane crtc as the crtc - * */ - CDRMPlane* gui_plane_nv12{nullptr}; - CDRMPlane* gui_plane{nullptr}; - CDRMCrtc* gui_crtc_nv12{nullptr}; - CDRMCrtc* gui_crtc{nullptr}; - - for (size_t crtc_offset = 0; crtc_offset < m_crtcs.size(); crtc_offset++) - { - if (!(m_encoder->GetPossibleCrtcs() & (1 << crtc_offset))) + for (size_t i = 0; i < m_crtcs.size(); i++) + { + if (!(m_encoder->GetPossibleCrtcs() & (1 << i))) continue; - for (auto& plane : m_planes) + auto videoPlane = std::find_if(m_planes.begin(), m_planes.end(), [&i](auto& plane) { + if (plane->GetPossibleCrtcs() & (1 << i)) + { + return plane->SupportsFormat(DRM_FORMAT_NV12); + } + return false; + }); + + uint32_t videoPlaneId{0}; + + if (videoPlane != m_planes.end()) + videoPlaneId = videoPlane->get()->GetPlaneId(); + + auto guiPlane = + std::find_if(m_planes.begin(), m_planes.end(), [&i, &videoPlaneId](auto& plane) { + if (plane->GetPossibleCrtcs() & (1 << i)) + { + return (plane->GetPlaneId() != videoPlaneId && + (videoPlaneId == 0 || plane->SupportsFormat(DRM_FORMAT_ARGB8888)) && + (plane->SupportsFormat(DRM_FORMAT_XRGB2101010) || + plane->SupportsFormat(DRM_FORMAT_XRGB8888))); + } + return false; + }); + + if (videoPlane != m_planes.end() && guiPlane != m_planes.end()) { - if (!(plane.get()->GetPossibleCrtcs() & (1 << crtc_offset))) - continue; + m_crtc = m_crtcs[i].get(); + m_video_plane = videoPlane->get(); + m_gui_plane = guiPlane->get(); + break; + } - if (plane.get()->SupportsFormat(DRM_FORMAT_ARGB8888) && - (plane.get()->SupportsFormat(DRM_FORMAT_XRGB2101010) || - plane.get()->SupportsFormat(DRM_FORMAT_XRGB8888))) + if (guiPlane != m_planes.end()) + { + if (!m_crtc && m_encoder->GetCrtcId() == m_crtcs[i]->GetCrtcId()) { - if (plane.get()->SupportsFormat(DRM_FORMAT_NV12) && - (gui_plane_nv12 == nullptr || gui_plane_nv12->GetId() < plane.get()->GetId())) - { - gui_plane_nv12 = plane.get(); - gui_crtc_nv12 = m_crtcs[crtc_offset].get(); - } - else if (!plane.get()->SupportsFormat(DRM_FORMAT_NV12) && - (gui_plane == nullptr || gui_plane->GetId() < plane.get()->GetId())) - { - gui_plane = plane.get(); - gui_crtc = m_crtcs[crtc_offset].get(); - } + m_crtc = m_crtcs[i].get(); + m_gui_plane = guiPlane->get(); + m_video_plane = nullptr; } } } - // fallback to NV12 supporting plane - if (gui_plane == nullptr) - { - gui_crtc = gui_crtc_nv12; - gui_plane = gui_plane_nv12; - } + CLog::Log(LOGINFO, "CDRMUtils::{} - using crtc: {}", __FUNCTION__, m_crtc->GetCrtcId()); - if (gui_plane != nullptr) - { - m_crtc = gui_crtc; - m_gui_plane = gui_plane; + // video plane may not be available + if (m_video_plane) + CLog::Log(LOGDEBUG, "CDRMUtils::{} - using video plane {}", __FUNCTION__, + m_video_plane->GetPlaneId()); - CLog::Log(LOGINFO, "CDRMUtils::{} - using crtc: {}", __FUNCTION__, m_crtc->GetCrtcId()); - if (m_gui_plane->SupportsFormat(DRM_FORMAT_XRGB2101010)) - { - m_gui_plane->SetFormat(DRM_FORMAT_XRGB2101010); - CLog::Log(LOGDEBUG, "CDRMUtils::{} - using 10bit gui plane {}", __FUNCTION__, - m_gui_plane->GetPlaneId()); - } - else - { - m_gui_plane->SetFormat(DRM_FORMAT_XRGB8888); - CLog::Log(LOGDEBUG, "CDRMUtils::{} - using gui plane {}", __FUNCTION__, - m_gui_plane->GetPlaneId()); - } - return true; + if (m_gui_plane->SupportsFormat(DRM_FORMAT_XRGB2101010)) + { + m_gui_plane->SetFormat(DRM_FORMAT_XRGB2101010); + CLog::Log(LOGDEBUG, "CDRMUtils::{} - using 10bit gui plane {}", __FUNCTION__, + m_gui_plane->GetPlaneId()); } - - CLog::Log(LOGERROR, "CDRMUtils::{} - Can not find a GUI plane", __FUNCTION__); - return false; -} - -bool CDRMUtils::FindVideoPlane(uint32_t format, uint64_t modifier) -{ - bool supports_zpos = m_gui_plane->SupportsProperty("zpos"); - bool zpos_immutable = supports_zpos && m_gui_plane->IsPropertyImmutable("zpos").value(); - - auto crtc_offset = std::distance( - m_crtcs.begin(), - std::find_if(m_crtcs.begin(), m_crtcs.end(), - [this](auto& crtc) { return crtc->GetCrtcId() == m_crtc->GetCrtcId(); })); - - auto guiplane_id = m_gui_plane->GetId(); - auto videoPlane = std::find_if(m_planes.begin(), m_planes.end(), - [&crtc_offset, &format, &modifier, &guiplane_id](auto& plane) - { - if (plane->GetPossibleCrtcs() & (1 << crtc_offset)) - { - return (guiplane_id != plane->GetPlaneId() && - plane->SupportsFormatAndModifier(format, modifier)); - } - return false; - }); - - if (videoPlane == m_planes.end()) - { - CLog::Log(LOGERROR, - "CDRMUtils::{} - Can not find a Video Plane plane for format {}, modifier {}", - __FUNCTION__, format, modifier); - return false; + else + { + m_gui_plane->SetFormat(DRM_FORMAT_XRGB8888); + CLog::Log(LOGDEBUG, "CDRMUtils::{} - using gui plane {}", __FUNCTION__, + m_gui_plane->GetPlaneId()); } - m_video_plane = videoPlane->get(); - CLog::Log(LOGDEBUG, "CDRMUtils::{} - using video plane {}", __FUNCTION__, - m_video_plane->GetPlaneId()); - - if (!supports_zpos || zpos_immutable) - return true; - - // re-sort the video and gui planes - auto limits = m_gui_plane->GetRangePropertyLimits("zpos"); - - if (!limits) - return true; - - m_gui_plane->SetProperty("zpos", limits.value()[1]); - m_video_plane->SetProperty("zpos", limits.value()[0]); - CLog::Log(LOGDEBUG, "CDRMUtils::{} - gui plane id,zpos: {}, {}", __FUNCTION__, - m_gui_plane->GetId(), limits.value()[1]); - CLog::Log(LOGDEBUG, "CDRMUtils::{} - video plane id,zpos: {}, {}", __FUNCTION__, - m_video_plane->GetId(), limits.value()[0]); - return true; } @@ -520,11 +467,9 @@ bool CDRMUtils::InitDrm() if (!FindCrtc()) return false; - if (!FindGuiPlane()) + if (!FindPlanes()) return false; - FindVideoPlane(DRM_FORMAT_NV12, DRM_FORMAT_MOD_LINEAR); - if (!FindPreferredMode()) return false; diff --git a/xbmc/windowing/gbm/drm/DRMUtils.h b/xbmc/windowing/gbm/drm/DRMUtils.h index b99a6dc4fe4ad..f92f716fc4f3b 100644 --- a/xbmc/windowing/gbm/drm/DRMUtils.h +++ b/xbmc/windowing/gbm/drm/DRMUtils.h @@ -64,8 +64,6 @@ class CDRMUtils static uint32_t FourCCWithoutAlpha(uint32_t fourcc); void SetInFenceFd(int fd) { m_inFenceFd = fd; } - bool FindVideoPlane(uint32_t format, uint64_t modifier); - bool FindGuiPlane(); int TakeOutFenceFd() { int fd{-1}; @@ -91,13 +89,13 @@ class CDRMUtils int m_inFenceFd{-1}; int m_outFenceFd{-1}; - std::vector<std::unique_ptr<CDRMCrtc>> m_crtcs; std::vector<std::unique_ptr<CDRMPlane>> m_planes; private: bool FindConnector(); bool FindEncoder(); bool FindCrtc(); + bool FindPlanes(); bool FindPreferredMode(); bool RestoreOriginalMode(); RESOLUTION_INFO GetResolutionInfo(drmModeModeInfoPtr mode); @@ -108,6 +106,7 @@ class CDRMUtils std::vector<std::unique_ptr<CDRMConnector>> m_connectors; std::vector<std::unique_ptr<CDRMEncoder>> m_encoders; + std::vector<std::unique_ptr<CDRMCrtc>> m_crtcs; }; } From e6129a0e9f6c236b40429f1a7bfc2c50d5729a10 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:28:30 +0200 Subject: [PATCH 568/651] [addons] PVR Add-on API version bump to 9.2.0 (incl. bump of min version). --- xbmc/addons/kodi-dev-kit/include/kodi/versions.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h index 34de2017ac7be..f0c7a81cf0468 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h @@ -130,8 +130,8 @@ #define ADDON_INSTANCE_VERSION_PERIPHERAL_DEPENDS "addon-instance/Peripheral.h" \ "addon-instance/PeripheralUtils.h" -#define ADDON_INSTANCE_VERSION_PVR "9.1.0" -#define ADDON_INSTANCE_VERSION_PVR_MIN "9.0.0" +#define ADDON_INSTANCE_VERSION_PVR "9.2.0" +#define ADDON_INSTANCE_VERSION_PVR_MIN "9.2.0" #define ADDON_INSTANCE_VERSION_PVR_XML_ID "kodi.binary.instance.pvr" #define ADDON_INSTANCE_VERSION_PVR_DEPENDS "c-api/addon-instance/pvr.h" \ "c-api/addon-instance/pvr/pvr_channel_groups.h" \ From e3302f9a13a8ed1017baf025bd93a56a304c1db1 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:33:36 +0200 Subject: [PATCH 569/651] [addons][cores][PVR] PVR Add-on API version 9.2.0: Def and impl of multiple recorded streams support. --- .../include/kodi/addon-instance/PVR.h | 138 ++++++++++++++++-- .../include/kodi/addon-instance/pvr/General.h | 13 ++ .../include/kodi/c-api/addon-instance/pvr.h | 19 ++- .../c-api/addon-instance/pvr/pvr_general.h | 1 + .../DVDInputStreams/InputStreamPVRBase.cpp | 24 +-- .../DVDInputStreams/InputStreamPVRBase.h | 3 + .../DVDInputStreams/InputStreamPVRChannel.cpp | 33 +++++ .../DVDInputStreams/InputStreamPVRChannel.h | 3 + .../InputStreamPVRRecording.cpp | 44 +++++- .../DVDInputStreams/InputStreamPVRRecording.h | 6 + xbmc/pvr/addons/PVRClient.cpp | 86 ++++++++--- xbmc/pvr/addons/PVRClient.h | 45 +++++- xbmc/pvr/addons/PVRClientCapabilities.h | 30 ++-- 13 files changed, 364 insertions(+), 81 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h index 83ca8b6ccb859..5d83cad226911 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h @@ -2585,27 +2585,36 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance /// @brief Open a stream to a recording on the backend. /// /// @param[in] recording The recording to open. + /// @param[out] streamId The id for the opened stream. /// @return True if the stream has been opened successfully, false otherwise. /// /// @remarks Optional, and only used if @ref PVRCapabilities::SetSupportsRecordings() - /// is set to true. @ref CloseRecordedStream() will always be called by Kodi - /// prior to calling this function. + /// is set to true. If @ref PVRCapabilities::SetSupportsMultipleRecordedStreams() is set to false, + /// @ref CloseRecordedStream() will be called by Kodi prior to calling this function. /// - virtual bool OpenRecordedStream(const kodi::addon::PVRRecording& recording) { return false; } + virtual bool OpenRecordedStream(const kodi::addon::PVRRecording& recording, int64_t& streamId) + { + return false; + } //---------------------------------------------------------------------------- //============================================================================ /// @brief Close an open stream from a recording. /// + /// @param[in] streamId The id of the stream to close, as returned by @ref OpenRecordedStream(). + /// Can be ignored, if @ref PVRCapabilities::SetSupportsMultipleRecordedStreams() is set to false. + /// /// @remarks Optional, and only used if @ref PVRCapabilities::SetSupportsRecordings() /// is set to true. /// - virtual void CloseRecordedStream() {} + virtual void CloseRecordedStream(int64_t streamId) {} //---------------------------------------------------------------------------- //============================================================================ /// @brief Read from a recording. /// + /// @param[in] streamId The id of the stream to read, as returned by @ref OpenRecordedStream(). + /// Can be ignored, if @ref PVRCapabilities::SetSupportsMultipleRecordedStreams() is set to false. /// @param[in] buffer The buffer to store the data in. /// @param[in] size The amount of bytes to read. /// @return The amount of bytes that were actually read from the stream. @@ -2613,12 +2622,17 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance /// @remarks Optional, and only used if @ref PVRCapabilities::SetSupportsRecordings() /// is set to true. /// - virtual int ReadRecordedStream(unsigned char* buffer, unsigned int size) { return 0; } + virtual int ReadRecordedStream(int64_t streamId, unsigned char* buffer, unsigned int size) + { + return 0; + } //---------------------------------------------------------------------------- //============================================================================ /// @brief Seek in a recorded stream. /// + /// @param[in] streamId The id of the stream to seek, as returned by @ref OpenRecordedStream(). + /// Can be ignored, if @ref PVRCapabilities::SetSupportsMultipleRecordedStreams() is set to false. /// @param[in] position The position to seek to. /// @param[in] whence [optional] offset relative to /// You can set the value of whence to one of three things: @@ -2633,20 +2647,79 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance /// @remarks Optional, and only used if @ref PVRCapabilities::SetSupportsRecordings() /// is set to true. /// - virtual int64_t SeekRecordedStream(int64_t position, int whence) { return 0; } + virtual int64_t SeekRecordedStream(int64_t streamId, int64_t position, int whence) { return 0; } //---------------------------------------------------------------------------- //============================================================================ /// @brief Obtain the length of a recorded stream. /// + /// @param[in] streamId The id of the stream to get the length for, as returned by + /// @ref OpenRecordedStream(). Can be ignored, if + /// @ref PVRCapabilities::SetSupportsMultipleRecordedStreams() is set to false. /// @return The total length of the stream that's currently being read. /// /// @remarks Optional, and only used if @ref PVRCapabilities::SetSupportsRecordings() - /// is true (=> @ref ReadRecordedStream). + /// is set to true. + /// + virtual int64_t LengthRecordedStream(int64_t streamId) { return 0; } + //---------------------------------------------------------------------------- + + //============================================================================ + /// @brief Check for real-time streaming. + /// + /// @param[in] streamId The id of the stream to check, as returned by @ref OpenRecordedStream(). + /// @param[out] isRealTime True if is real-time streaming, false otherwise. + /// @return @ref PVR_ERROR_NO_ERROR on success, respective error code otherwise. + /// + /// @remarks Optional, and only called if both @ref PVRCapabilities::SetSupportsRecordings() + /// and @ref PVRCapabilities::SetSupportsMultipleRecordedStreams() are set to true. If + /// @refPVRCapabilities::SetSupportsMultipleRecordedStreams() is set to false, + /// @ref IsRealTimeStream() will be called to check real time status for recorded streams. /// - virtual int64_t LengthRecordedStream() { return 0; } + virtual PVR_ERROR IsRecordedStreamRealTime(int64_t streamId, bool& isRealTime) + { + return PVR_ERROR_NOT_IMPLEMENTED; + } //---------------------------------------------------------------------------- + //============================================================================ + /// + /// @brief Notify the pvr addon that Kodi (un)paused the currently playing + /// stream. + /// + /// @param[in] streamId The id of the stream (un)paused, as returned by @ref OpenRecordedStream(). + /// @param[in] paused To inform by `true` is paused and with `false` playing + /// @return @ref PVR_ERROR_NO_ERROR on success, respective error code otherwise. + /// + /// @remarks Optional, and only called if both @ref PVRCapabilities::SetSupportsRecordings() + /// and @ref PVRCapabilities::SetSupportsMultipleRecordedStreams() are set to true. If + /// @refPVRCapabilities::SetSupportsMultipleRecordedStreams() is set to false, @ref PauseStream() + /// will be called to pause recorded streams. + /// + virtual PVR_ERROR PauseRecordedStream(int64_t streamId, bool paused) + { + return PVR_ERROR_NOT_IMPLEMENTED; + } + //---------------------------------------------------------------------------- + + //============================================================================ + /// + /// @brief Get stream times. + /// + /// @param[in] streamId The id of the stream to get times for, as returned by + /// @ref OpenRecordedStream(). + /// @param[out] times Data to be filled by the implementation. + /// @return @ref PVR_ERROR_NO_ERROR on success, respective error code otherwise. + /// + /// @remarks Optional, and only called if both @ref PVRCapabilities::SetSupportsRecordings() + /// and @ref PVRCapabilities::SetSupportsMultipleRecordedStreams() are set to true. If + /// @refPVRCapabilities::SetSupportsMultipleRecordedStreams() is set to false, + /// @ref GetStreamTimes() will be called to obtain times for recorded streams. + /// + virtual PVR_ERROR GetRecordedStreamTimes(int64_t streamId, kodi::addon::PVRStreamTimes& times) + { + return PVR_ERROR_NOT_IMPLEMENTED; + } ///@} //--==----==----==----==----==----==----==----==----==----==----==----==----== @@ -2831,6 +2904,9 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance instance->pvr->toAddon->ReadRecordedStream = ADDON_ReadRecordedStream; instance->pvr->toAddon->SeekRecordedStream = ADDON_SeekRecordedStream; instance->pvr->toAddon->LengthRecordedStream = ADDON_LengthRecordedStream; + instance->pvr->toAddon->IsRecordedStreamRealTime = ADDON_IsRecordedStreamRealTime; + instance->pvr->toAddon->PauseRecordedStream = ADDON_PauseRecordedStream; + instance->pvr->toAddon->GetRecordedStreamTimes = ADDON_GetRecordedStreamTimes; //--==----==----==----==----==----==----==----==----==----==----==----==----== instance->pvr->toAddon->DemuxReset = ADDON_DemuxReset; instance->pvr->toAddon->DemuxAbort = ADDON_DemuxAbort; @@ -3476,37 +3552,67 @@ class ATTR_DLL_LOCAL CInstancePVRClient : public IAddonInstance } inline static bool ADDON_OpenRecordedStream(const AddonInstance_PVR* instance, - const PVR_RECORDING* recording) + const PVR_RECORDING* recording, + int64_t* streamId) { return static_cast<CInstancePVRClient*>(instance->toAddon->addonInstance) - ->OpenRecordedStream(recording); + ->OpenRecordedStream(recording, *streamId); } - inline static void ADDON_CloseRecordedStream(const AddonInstance_PVR* instance) + inline static void ADDON_CloseRecordedStream(const AddonInstance_PVR* instance, int64_t streamId) { - static_cast<CInstancePVRClient*>(instance->toAddon->addonInstance)->CloseRecordedStream(); + static_cast<CInstancePVRClient*>(instance->toAddon->addonInstance) + ->CloseRecordedStream(streamId); } inline static int ADDON_ReadRecordedStream(const AddonInstance_PVR* instance, + int64_t streamId, unsigned char* buffer, unsigned int size) { return static_cast<CInstancePVRClient*>(instance->toAddon->addonInstance) - ->ReadRecordedStream(buffer, size); + ->ReadRecordedStream(streamId, buffer, size); } inline static int64_t ADDON_SeekRecordedStream(const AddonInstance_PVR* instance, + int64_t streamId, int64_t position, int whence) { return static_cast<CInstancePVRClient*>(instance->toAddon->addonInstance) - ->SeekRecordedStream(position, whence); + ->SeekRecordedStream(streamId, position, whence); } - inline static int64_t ADDON_LengthRecordedStream(const AddonInstance_PVR* instance) + inline static int64_t ADDON_LengthRecordedStream(const AddonInstance_PVR* instance, + int64_t streamId) { return static_cast<CInstancePVRClient*>(instance->toAddon->addonInstance) - ->LengthRecordedStream(); + ->LengthRecordedStream(streamId); + } + + inline static PVR_ERROR ADDON_IsRecordedStreamRealTime(const AddonInstance_PVR* instance, + int64_t streamId, + bool* isRealTime) + { + return static_cast<CInstancePVRClient*>(instance->toAddon->addonInstance) + ->IsRecordedStreamRealTime(streamId, *isRealTime); + } + + inline static PVR_ERROR ADDON_PauseRecordedStream(const AddonInstance_PVR* instance, + int64_t streamId, + bool paused) + { + return static_cast<CInstancePVRClient*>(instance->toAddon->addonInstance) + ->PauseRecordedStream(streamId, paused); + } + + inline static PVR_ERROR ADDON_GetRecordedStreamTimes(const AddonInstance_PVR* instance, + int64_t streamId, + PVR_STREAM_TIMES* times) + { + PVRStreamTimes cppTimes(times); + return static_cast<CInstancePVRClient*>(instance->toAddon->addonInstance) + ->GetRecordedStreamTimes(streamId, cppTimes); } inline static void ADDON_DemuxReset(const AddonInstance_PVR* instance) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h index cfd072791e243..61a2bd360b8e9 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h @@ -1112,6 +1112,7 @@ class PVRCapabilities : public DynamicCStructHdl<PVRCapabilities, PVR_ADDON_CAPA /// | **Supports async EPG transfer** | `boolean` | @ref PVRCapabilities::SetSupportsAsyncEPGTransfer "SetSupportsAsyncEPGTransfer" | @ref PVRCapabilities::GetSupportsAsyncEPGTransfer "GetSupportsAsyncEPGTransfer" /// | **Supports recording size** | `boolean` | @ref PVRCapabilities::SetSupportsRecordingSize "SetSupportsRecordingSize" | @ref PVRCapabilities::GetSupportsRecordingSize "GetSupportsRecordingSize" /// | **Supports recordings delete** | `boolean` | @ref PVRCapabilities::SetSupportsRecordingsDelete "SetSupportsRecordingsDelete" | @ref PVRCapabilities::GetSupportsRecordingsDelete "SetSupportsRecordingsDelete" + /// | **Supports multiple recorded streams** | `boolean` | @ref PVRCapabilities::SetSupportsMultipleRecordedStreams "SetSupportsMultipleRecordedStreams" | @ref PVRCapabilities::GetSupportsMultipleRecordedStreams "GetSupportsMultipleRecordedStreams" /// | **Recordings lifetime values** | @ref cpp_kodi_addon_pvr_Defs_PVRTypeIntValue "PVRTypeIntValue" | @ref PVRCapabilities::SetRecordingsLifetimeValues "SetRecordingsLifetimeValues" | @ref PVRCapabilities::GetRecordingsLifetimeValues "GetRecordingsLifetimeValues" /// /// @warning This class can not be used outside of @ref kodi::addon::CInstancePVRClient::GetCapabilities() @@ -1338,6 +1339,18 @@ class PVRCapabilities : public DynamicCStructHdl<PVRCapabilities, PVR_ADDON_CAPA /// @brief To get with @ref SetSupportsRecordingsDelete changed values. bool GetSupportsRecordingsDelete() const { return m_cStructure->bSupportsRecordingsDelete; } + /// @brief Set **true** if this add-on supports multiple streams for recordings at a time. + void SetSupportsMultipleRecordedStreams(bool supportsMultipleRecordedStreams) + { + m_cStructure->bSupportsMultipleRecordedStreams = supportsMultipleRecordedStreams; + } + + /// @brief To get with @ref SetSupportsMultiRecordedStreams changed values. + bool GetSupportsMultipleRecordedStreams() const + { + return m_cStructure->bSupportsMultipleRecordedStreams; + } + /// @brief **optional**\n /// Set array containing the possible values for @ref PVRRecording::SetLifetime(). /// diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h index e123ff414c693..fc02cba85505c 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h @@ -296,11 +296,20 @@ extern "C" //--==----==----==----==----==----==----==----==----==----==----==----==----== // Recording stream read interface functions - bool(__cdecl* OpenRecordedStream)(const struct AddonInstance_PVR*, const struct PVR_RECORDING*); - void(__cdecl* CloseRecordedStream)(const struct AddonInstance_PVR*); - int(__cdecl* ReadRecordedStream)(const struct AddonInstance_PVR*, unsigned char*, unsigned int); - int64_t(__cdecl* SeekRecordedStream)(const struct AddonInstance_PVR*, int64_t, int); - int64_t(__cdecl* LengthRecordedStream)(const struct AddonInstance_PVR*); + bool(__cdecl* OpenRecordedStream)(const struct AddonInstance_PVR*, + const struct PVR_RECORDING*, + int64_t*); + void(__cdecl* CloseRecordedStream)(const struct AddonInstance_PVR*, int64_t); + int(__cdecl* ReadRecordedStream)(const struct AddonInstance_PVR*, + int64_t, + unsigned char*, + unsigned int); + int64_t(__cdecl* SeekRecordedStream)(const struct AddonInstance_PVR*, int64_t, int64_t, int); + int64_t(__cdecl* LengthRecordedStream)(const struct AddonInstance_PVR*, int64_t); + PVR_ERROR(__cdecl* IsRecordedStreamRealTime)(const struct AddonInstance_PVR*, int64_t, bool*); + PVR_ERROR(__cdecl* PauseRecordedStream)(const struct AddonInstance_PVR*, int64_t, bool); + PVR_ERROR(__cdecl* GetRecordedStreamTimes) + (const struct AddonInstance_PVR*, int64_t, PVR_STREAM_TIMES*); //--==----==----==----==----==----==----==----==----==----==----==----==----== // Stream demux interface functions diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h index 4541f7ffe8704..5367d1989982e 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h @@ -370,6 +370,7 @@ extern "C" bool bSupportsRecordingSize; bool bSupportsProviders; bool bSupportsRecordingsDelete; + bool bSupportsMultipleRecordedStreams; unsigned int iRecordingsLifetimesSize; struct PVR_ATTRIBUTE_INT_VALUE* recordingsLifetimeValues; diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp index 7b8be41763231..7ec9ab307c4fd 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp @@ -108,19 +108,7 @@ int CInputStreamPVRBase::GetBlockSize() bool CInputStreamPVRBase::GetTimes(Times ×) { - PVR_STREAM_TIMES streamTimes = {}; - if (m_client && m_client->GetStreamTimes(&streamTimes) == PVR_ERROR_NO_ERROR) - { - times.startTime = streamTimes.startTime; - times.ptsStart = streamTimes.ptsStart; - times.ptsBegin = streamTimes.ptsBegin; - times.ptsEnd = streamTimes.ptsEnd; - return true; - } - else - { - return false; - } + return GetPVRStreamTimes(times); } CDVDInputStream::ENextStream CInputStreamPVRBase::NextStream() @@ -140,18 +128,12 @@ bool CInputStreamPVRBase::CanSeek() void CInputStreamPVRBase::Pause(bool bPaused) { - if (m_client) - m_client->PauseStream(bPaused); + PausePVRStream(bPaused); } bool CInputStreamPVRBase::IsRealtime() { - bool ret = false; - - if (m_client) - m_client->IsRealTimeStream(ret); - - return ret; + return IsRealtimePVRStream(); } bool CInputStreamPVRBase::OpenDemux() diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h index 7a2d33651236b..c060453959df3 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h @@ -75,6 +75,9 @@ class CInputStreamPVRBase virtual ENextStream NextPVRStream() = 0; virtual bool CanPausePVRStream() = 0; virtual bool CanSeekPVRStream() = 0; + virtual bool IsRealtimePVRStream() = 0; + virtual void PausePVRStream(bool paused) = 0; + virtual bool GetPVRStreamTimes(Times& times) = 0; bool m_eof = true; std::shared_ptr<PVR_STREAM_PROPERTIES> m_StreamProps; diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.cpp index 8ac46bdb1d6cc..ae798a1b03dc3 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.cpp @@ -122,3 +122,36 @@ bool CInputStreamPVRChannel::CanSeekPVRStream() return ret; } + +bool CInputStreamPVRChannel::IsRealtimePVRStream() +{ + bool ret = false; + + if (m_client) + m_client->IsRealTimeStream(ret); + + return ret; +} + +void CInputStreamPVRChannel::PausePVRStream(bool paused) +{ + if (m_client) + m_client->PauseStream(paused); +} + +bool CInputStreamPVRChannel::GetPVRStreamTimes(Times& times) +{ + PVR_STREAM_TIMES streamTimes = {}; + if (m_client && m_client->GetStreamTimes(&streamTimes) == PVR_ERROR_NO_ERROR) + { + times.startTime = streamTimes.startTime; + times.ptsStart = streamTimes.ptsStart; + times.ptsBegin = streamTimes.ptsBegin; + times.ptsEnd = streamTimes.ptsEnd; + return true; + } + else + { + return false; + } +} diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.h b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.h index e95fb208b2abb..7503441582ebc 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.h +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRChannel.h @@ -27,6 +27,9 @@ class CInputStreamPVRChannel : public CInputStreamPVRBase ENextStream NextPVRStream() override; bool CanPausePVRStream() override; bool CanSeekPVRStream() override; + bool IsRealtimePVRStream() override; + void PausePVRStream(bool paused) override; + bool GetPVRStreamTimes(Times& times) override; private: bool m_bDemuxActive = false; diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.cpp index 6c41ffb6d0a7f..a4921debf0840 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.cpp @@ -38,7 +38,8 @@ bool CInputStreamPVRRecording::OpenPVRStream() "CInputStreamPVRRecording - {} - unable to obtain recording instance for recording {}", __FUNCTION__, m_item.GetPath()); - if (recording && m_client && (m_client->OpenRecordedStream(recording) == PVR_ERROR_NO_ERROR)) + if (recording && m_client && + (m_client->OpenRecordedStream(recording, m_streamId) == PVR_ERROR_NO_ERROR)) { CLog::Log(LOGDEBUG, "CInputStreamPVRRecording - {} - opened recording stream {}", __FUNCTION__, m_item.GetPath()); @@ -49,7 +50,7 @@ bool CInputStreamPVRRecording::OpenPVRStream() void CInputStreamPVRRecording::ClosePVRStream() { - if (m_client && (m_client->CloseRecordedStream() == PVR_ERROR_NO_ERROR)) + if (m_client && (m_client->CloseRecordedStream(m_streamId) == PVR_ERROR_NO_ERROR)) { CLog::Log(LOGDEBUG, "CInputStreamPVRRecording - {} - closed recording stream {}", __FUNCTION__, m_item.GetPath()); @@ -61,7 +62,7 @@ int CInputStreamPVRRecording::ReadPVRStream(uint8_t* buf, int buf_size) int iRead = -1; if (m_client) - m_client->ReadRecordedStream(buf, buf_size, iRead); + m_client->ReadRecordedStream(m_streamId, buf, buf_size, iRead); return iRead; } @@ -71,7 +72,7 @@ int64_t CInputStreamPVRRecording::SeekPVRStream(int64_t offset, int whence) int64_t ret = -1; if (m_client) - m_client->SeekRecordedStream(offset, whence, ret); + m_client->SeekRecordedStream(m_streamId, offset, whence, ret); return ret; } @@ -81,7 +82,7 @@ int64_t CInputStreamPVRRecording::GetPVRStreamLength() int64_t ret = -1; if (m_client) - m_client->GetRecordedStreamLength(ret); + m_client->GetRecordedStreamLength(m_streamId, ret); return ret; } @@ -100,3 +101,36 @@ bool CInputStreamPVRRecording::CanSeekPVRStream() { return true; } + +bool CInputStreamPVRRecording::IsRealtimePVRStream() +{ + bool ret = false; + + if (m_client) + m_client->IsRecordedStreamRealTime(m_streamId, ret); + + return ret; +} + +void CInputStreamPVRRecording::PausePVRStream(bool paused) +{ + if (m_client) + m_client->PauseRecordedStream(m_streamId, paused); +} + +bool CInputStreamPVRRecording::GetPVRStreamTimes(Times& times) +{ + PVR_STREAM_TIMES streamTimes = {}; + if (m_client && m_client->GetRecordedStreamTimes(m_streamId, &streamTimes) == PVR_ERROR_NO_ERROR) + { + times.startTime = streamTimes.startTime; + times.ptsStart = streamTimes.ptsStart; + times.ptsBegin = streamTimes.ptsBegin; + times.ptsEnd = streamTimes.ptsEnd; + return true; + } + else + { + return false; + } +} diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.h b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.h index 2a873c9f33966..7d5e826301543 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.h +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRRecording.h @@ -25,4 +25,10 @@ class CInputStreamPVRRecording : public CInputStreamPVRBase ENextStream NextPVRStream() override; bool CanPausePVRStream() override; bool CanSeekPVRStream() override; + bool IsRealtimePVRStream() override; + void PausePVRStream(bool paused) override; + bool GetPVRStreamTimes(Times& times) override; + +private: + int64_t m_streamId{-1}; }; diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index dd868f6292e29..09db00dc268b6 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -1495,14 +1495,18 @@ PVR_ERROR CPVRClient::ReadLiveStream(void* lpBuf, int64_t uiBufSize, int& iRead) }); } -PVR_ERROR CPVRClient::ReadRecordedStream(void* lpBuf, int64_t uiBufSize, int& iRead) +PVR_ERROR CPVRClient::ReadRecordedStream(int64_t streamId, + void* lpBuf, + int64_t uiBufSize, + int& iRead) { iRead = -1; return DoAddonCall(__func__, - [&lpBuf, uiBufSize, &iRead](const AddonInstance* addon) + [streamId, &lpBuf, uiBufSize, &iRead](const AddonInstance* addon) { iRead = addon->toAddon->ReadRecordedStream( - addon, static_cast<unsigned char*>(lpBuf), static_cast<int>(uiBufSize)); + addon, streamId, static_cast<unsigned char*>(lpBuf), + static_cast<int>(uiBufSize)); return (iRead == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; }); } @@ -1518,14 +1522,17 @@ PVR_ERROR CPVRClient::SeekLiveStream(int64_t iFilePosition, int iWhence, int64_t }); } -PVR_ERROR CPVRClient::SeekRecordedStream(int64_t iFilePosition, int iWhence, int64_t& iPosition) +PVR_ERROR CPVRClient::SeekRecordedStream(int64_t streamId, + int64_t iFilePosition, + int iWhence, + int64_t& iPosition) { iPosition = -1; return DoAddonCall(__func__, - [iFilePosition, iWhence, &iPosition](const AddonInstance* addon) + [streamId, iFilePosition, iWhence, &iPosition](const AddonInstance* addon) { - iPosition = - addon->toAddon->SeekRecordedStream(addon, iFilePosition, iWhence); + iPosition = addon->toAddon->SeekRecordedStream(addon, streamId, + iFilePosition, iWhence); return (iPosition == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; }); } @@ -1552,13 +1559,13 @@ PVR_ERROR CPVRClient::GetLiveStreamLength(int64_t& iLength) const }); } -PVR_ERROR CPVRClient::GetRecordedStreamLength(int64_t& iLength) const +PVR_ERROR CPVRClient::GetRecordedStreamLength(int64_t streamId, int64_t& iLength) const { iLength = -1; return DoAddonCall(__func__, - [&iLength](const AddonInstance* addon) + [streamId, &iLength](const AddonInstance* addon) { - iLength = addon->toAddon->LengthRecordedStream(addon); + iLength = addon->toAddon->LengthRecordedStream(addon, streamId); return (iLength == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; }); } @@ -1820,21 +1827,24 @@ PVR_ERROR CPVRClient::OpenLiveStream(const std::shared_ptr<const CPVRChannel>& c }); } -PVR_ERROR CPVRClient::OpenRecordedStream(const std::shared_ptr<const CPVRRecording>& recording) +PVR_ERROR CPVRClient::OpenRecordedStream(const std::shared_ptr<const CPVRRecording>& recording, + int64_t& streamId) { if (!recording) return PVR_ERROR_INVALID_PARAMETERS; return DoAddonCall( __func__, - [this, recording](const AddonInstance* addon) + [this, recording, &streamId](const AddonInstance* addon) { - CloseRecordedStream(); + if (!m_clientCapabilities.SupportsMultipleRecordedStreams()) + CloseRecordedStream(streamId); const CAddonRecording tag(*recording); CLog::LogFC(LOGDEBUG, LOGPVR, "Opening stream for recording '{}'", recording->m_strTitle); - return addon->toAddon->OpenRecordedStream(addon, &tag) ? PVR_ERROR_NO_ERROR - : PVR_ERROR_NOT_IMPLEMENTED; + return addon->toAddon->OpenRecordedStream(addon, &tag, &streamId) + ? PVR_ERROR_NO_ERROR + : PVR_ERROR_NOT_IMPLEMENTED; }, m_clientCapabilities.SupportsRecordings()); } @@ -1849,16 +1859,56 @@ PVR_ERROR CPVRClient::CloseLiveStream() }); } -PVR_ERROR CPVRClient::CloseRecordedStream() +PVR_ERROR CPVRClient::CloseRecordedStream(int64_t streamId) { return DoAddonCall(__func__, - [](const AddonInstance* addon) + [streamId](const AddonInstance* addon) { - addon->toAddon->CloseRecordedStream(addon); + addon->toAddon->CloseRecordedStream(addon, streamId); return PVR_ERROR_NO_ERROR; }); } +PVR_ERROR CPVRClient::IsRecordedStreamRealTime(int64_t streamId, bool& isRealTime) const +{ + if (m_clientCapabilities.SupportsMultipleRecordedStreams()) + { + return DoAddonCall( + __func__, [streamId, &isRealTime](const AddonInstance* addon) + { return addon->toAddon->IsRecordedStreamRealTime(addon, streamId, &isRealTime); }); + } + else + { + return IsRealTimeStream(isRealTime); + } +} + +PVR_ERROR CPVRClient::PauseRecordedStream(int64_t streamId, bool paused) +{ + if (m_clientCapabilities.SupportsMultipleRecordedStreams()) + { + return DoAddonCall(__func__, [streamId, paused](const AddonInstance* addon) + { return addon->toAddon->PauseRecordedStream(addon, streamId, paused); }); + } + else + { + return PauseStream(paused); + } +} + +PVR_ERROR CPVRClient::GetRecordedStreamTimes(int64_t streamId, PVR_STREAM_TIMES* times) const +{ + if (m_clientCapabilities.SupportsMultipleRecordedStreams()) + { + return DoAddonCall(__func__, [streamId, ×](const AddonInstance* addon) + { return addon->toAddon->GetRecordedStreamTimes(addon, streamId, times); }); + } + else + { + return GetStreamTimes(times); + } +} + PVR_ERROR CPVRClient::PauseStream(bool bPaused) { return DoAddonCall(__func__, diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h index 2d111ebaefb1d..523cb10f6a055 100644 --- a/xbmc/pvr/addons/PVRClient.h +++ b/xbmc/pvr/addons/PVRClient.h @@ -650,40 +650,73 @@ class CPVRClient : public ADDON::IAddonInstanceHandler /*! * @brief Open a recording on the server. * @param recording The recording to open. + * @param streamId The id of the stream opened. * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. */ - PVR_ERROR OpenRecordedStream(const std::shared_ptr<const CPVRRecording>& recording); + PVR_ERROR OpenRecordedStream(const std::shared_ptr<const CPVRRecording>& recording, + int64_t& streamId); /*! * @brief Close an open recording stream. + * @param streamId The id of the stream to close, as returned by OpenRecordedStream. * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. */ - PVR_ERROR CloseRecordedStream(); + PVR_ERROR CloseRecordedStream(int64_t streamId); /*! * @brief Read from an open recording stream. + * @param streamId The id of the stream to read, as returned by OpenRecordedStream. * @param lpBuf The buffer to store the data in. * @param uiBufSize The amount of bytes to read. * @param iRead The amount of bytes that were actually read from the stream. * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. */ - PVR_ERROR ReadRecordedStream(void* lpBuf, int64_t uiBufSize, int& iRead); + PVR_ERROR ReadRecordedStream(int64_t streamId, void* lpBuf, int64_t uiBufSize, int& iRead); /*! * @brief Seek in a recording stream on a backend. + * @param streamId The id of the stream to seek, as returned by OpenRecordedStream. * @param iFilePosition The position to seek to. * @param iWhence ? * @param iPosition The new position or -1 on error. * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. */ - PVR_ERROR SeekRecordedStream(int64_t iFilePosition, int iWhence, int64_t& iPosition); + PVR_ERROR SeekRecordedStream(int64_t streamId, + int64_t iFilePosition, + int iWhence, + int64_t& iPosition); /*! - * @brief Get the length of the currently playing recording stream, if any. + * @brief Get the length of the given stream. + * @param streamId The id of the stream to get the length for, as returned by OpenRecordedStream. * @param iLength The total length of the stream that's currently being read or -1 on error. * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. */ - PVR_ERROR GetRecordedStreamLength(int64_t& iLength) const; + PVR_ERROR GetRecordedStreamLength(int64_t streamId, int64_t& iLength) const; + + /*! + * @brief Check whether the given stream is a real-time stream. + * @param streamId The id of the stream to check, as returned by OpenRecordedStream. + * @param isRealTime True if real-time, false otherwise. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR IsRecordedStreamRealTime(int64_t streamId, bool& isRealTime) const; + + /*! + * @brief (Un)Pause a stream. + * @param streamId The id of the stream to (un)pause, as returned by OpenRecordedStream. + * @param paused True to pause the stream, false to unpause. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR PauseRecordedStream(int64_t streamId, bool paused); + + /*! + * @brief Get stream times for the given stream. + * @param streamId The id of the stream to get times for, as returned by OpenRecordedStream. + * @param times The stream times. + * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise. + */ + PVR_ERROR GetRecordedStreamTimes(int64_t streamId, PVR_STREAM_TIMES* times) const; /*! * @brief Fill the given container with the properties required for playback of the given recording. Values are obtained from the PVR backend. diff --git a/xbmc/pvr/addons/PVRClientCapabilities.h b/xbmc/pvr/addons/PVRClientCapabilities.h index edf20900a0d54..8cd31bbccc74b 100644 --- a/xbmc/pvr/addons/PVRClientCapabilities.h +++ b/xbmc/pvr/addons/PVRClientCapabilities.h @@ -117,6 +117,16 @@ class CPVRClientCapabilities return m_addonCapabilities && m_addonCapabilities->bSupportsAsyncEPGTransfer; } + /*! + * @brief Check whether this add-on supports retrieving an edit decision list for epg tags. + * @return True if supported, false otherwise. + */ + bool SupportsEpgTagEdl() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsEPG && + m_addonCapabilities->bSupportsEPGEdl; + } + ///////////////////////////////////////////////////////////////////////////////// // // Timers @@ -187,16 +197,6 @@ class CPVRClientCapabilities m_addonCapabilities->bSupportsRecordingEdl; } - /*! - * @brief Check whether this add-on supports retrieving an edit decision list for epg tags. - * @return True if supported, false otherwise. - */ - bool SupportsEpgTagEdl() const - { - return m_addonCapabilities && m_addonCapabilities->bSupportsEPG && - m_addonCapabilities->bSupportsEPGEdl; - } - /*! * @brief Check whether this add-on supports renaming recordings.. * @return True if supported, false otherwise. @@ -243,6 +243,16 @@ class CPVRClientCapabilities m_addonCapabilities->bSupportsRecordingsDelete; } + /*! + * @brief Check whether this add-on supports multiple recorded streams at a time. + * @return True if supported, false otherwise. + */ + bool SupportsMultipleRecordedStreams() const + { + return m_addonCapabilities && m_addonCapabilities->bSupportsRecordings && + m_addonCapabilities->bSupportsMultipleRecordedStreams; + } + ///////////////////////////////////////////////////////////////////////////////// // // Streams From daefea21d9d1869b6298100244300e370712d552 Mon Sep 17 00:00:00 2001 From: Yu Xiao <1918256943@qq.com> Date: Fri, 11 Oct 2024 18:02:35 +0800 Subject: [PATCH 570/651] Fix some UB like memcpy(NULL, NULL, 0) --- lib/libUPnP/Neptune/Source/Core/NptUtils.cpp | 2 +- ...0055-libUPnP-Fix-some-UB-like-memcpy.patch | 25 +++++++++++++++++++ xbmc-xrandr.c | 3 ++- .../VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp | 3 ++- 4 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 lib/libUPnP/patches/0055-libUPnP-Fix-some-UB-like-memcpy.patch diff --git a/lib/libUPnP/Neptune/Source/Core/NptUtils.cpp b/lib/libUPnP/Neptune/Source/Core/NptUtils.cpp index f427f8b0b0338..b2a641c509160 100644 --- a/lib/libUPnP/Neptune/Source/Core/NptUtils.cpp +++ b/lib/libUPnP/Neptune/Source/Core/NptUtils.cpp @@ -376,7 +376,7 @@ NPT_HexString(const unsigned char* data, while (data_size--) { NPT_ByteToHex(*src++, dst, uppercase); dst += 2; - if (data_size) { + if (data_size && separator_length) { NPT_CopyMemory(dst, separator, separator_length); dst += separator_length; } diff --git a/lib/libUPnP/patches/0055-libUPnP-Fix-some-UB-like-memcpy.patch b/lib/libUPnP/patches/0055-libUPnP-Fix-some-UB-like-memcpy.patch new file mode 100644 index 0000000000000..7089b4e3f8f62 --- /dev/null +++ b/lib/libUPnP/patches/0055-libUPnP-Fix-some-UB-like-memcpy.patch @@ -0,0 +1,25 @@ +From 6b0bc3602465eede68f1e04cf4835d1347bc9d69 Mon Sep 17 00:00:00 2001 +From: Yu Xiao <1918256943@qq.com> +Date: Fri, 11 Oct 2024 18:02:35 +0800 +Subject: [PATCH] fix some UB like memcpy(NULL, NULL, 0) + +--- + lib/libUPnP/Neptune/Source/Core/NptUtils.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/libUPnP/Neptune/Source/Core/NptUtils.cpp b/lib/libUPnP/Neptune/Source/Core/NptUtils.cpp +index f427f8b..b2a641c 100644 +--- a/lib/libUPnP/Neptune/Source/Core/NptUtils.cpp ++++ b/lib/libUPnP/Neptune/Source/Core/NptUtils.cpp +@@ -376,7 +376,7 @@ NPT_HexString(const unsigned char* data, + while (data_size--) { + NPT_ByteToHex(*src++, dst, uppercase); + dst += 2; +- if (data_size) { ++ if (data_size && separator_length) { + NPT_CopyMemory(dst, separator, separator_length); + dst += separator_length; + } +-- +2.44.0.windows.1 + diff --git a/xbmc-xrandr.c b/xbmc-xrandr.c index 64438cdb00d60..70d912af70173 100644 --- a/xbmc-xrandr.c +++ b/xbmc-xrandr.c @@ -597,7 +597,8 @@ static void set_transform( dest->filter = strdup(filter); dest->nparams = nparams; dest->params = malloc(nparams * sizeof(XFixed)); - memcpy(dest->params, params, nparams * sizeof(XFixed)); + if (nparams) + memcpy(dest->params, params, nparams * sizeof(XFixed)); } static void copy_transform(transform_t* dest, transform_t* src) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp index d56086bf14e93..11c485d13756d 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxCC.cpp @@ -485,7 +485,8 @@ DemuxPacket* CDVDDemuxCC::Decode() pPacket = CDVDDemuxUtils::AllocateDemuxPacket(data.size()); pPacket->iSize = data.size(); - memcpy(pPacket->pData, data.c_str(), pPacket->iSize); + if (pPacket->iSize) + memcpy(pPacket->pData, data.c_str(), pPacket->iSize); pPacket->iStreamId = service; pPacket->pts = m_streamdata[i].pts; From 88193f72d05f1a4104ea334ac953f7ed242a8a93 Mon Sep 17 00:00:00 2001 From: Yu Xiao <1918256943@qq.com> Date: Wed, 23 Oct 2024 10:10:36 +0800 Subject: [PATCH 571/651] XBDateTime: Fix use of unintialized values --- xbmc/XBDateTime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/XBDateTime.cpp b/xbmc/XBDateTime.cpp index b0029af6816fc..57870d685220e 100644 --- a/xbmc/XBDateTime.cpp +++ b/xbmc/XBDateTime.cpp @@ -42,7 +42,7 @@ CDateTimeSpan::CDateTimeSpan(const CDateTimeSpan& span) m_timeSpan.lowDateTime = span.m_timeSpan.lowDateTime; } -CDateTimeSpan::CDateTimeSpan(int day, int hour, int minute, int second) +CDateTimeSpan::CDateTimeSpan(int day, int hour, int minute, int second) : CDateTimeSpan() { SetDateTimeSpan(day, hour, minute, second); } From 36cb2c569f622a5bd8d347dfb2fadec47d99eea5 Mon Sep 17 00:00:00 2001 From: Yu Xiao <1918256943@qq.com> Date: Wed, 23 Oct 2024 10:10:43 +0800 Subject: [PATCH 572/651] xbmc-xrandc: Fix malloc(0) behavior --- xbmc-xrandr.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/xbmc-xrandr.c b/xbmc-xrandr.c index 70d912af70173..8a95799db1442 100644 --- a/xbmc-xrandr.c +++ b/xbmc-xrandr.c @@ -596,9 +596,13 @@ static void set_transform( dest->transform = *transform; dest->filter = strdup(filter); dest->nparams = nparams; - dest->params = malloc(nparams * sizeof(XFixed)); if (nparams) + { + dest->params = malloc(nparams * sizeof(XFixed)); memcpy(dest->params, params, nparams * sizeof(XFixed)); + } + else + dest->params = NULL; } static void copy_transform(transform_t* dest, transform_t* src) From eb704d21c520fef13682823c5287b8635b0e6d83 Mon Sep 17 00:00:00 2001 From: Yu Xiao <1918256943@qq.com> Date: Wed, 23 Oct 2024 10:08:44 +0800 Subject: [PATCH 573/651] SqliteDataset: Fix memory leak in mysql_vmprintf() --- xbmc/dbwrappers/mysqldataset.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xbmc/dbwrappers/mysqldataset.cpp b/xbmc/dbwrappers/mysqldataset.cpp index e277eefd8f4d7..9d6f026c55a95 100644 --- a/xbmc/dbwrappers/mysqldataset.cpp +++ b/xbmc/dbwrappers/mysqldataset.cpp @@ -1634,7 +1634,10 @@ std::string MysqlDatabase::mysql_vmprintf(const char* zFormat, va_list ap) mysqlStrAccumInit(&acc, zBase, sizeof(zBase), MYSQL_MAX_LENGTH); mysqlVXPrintf(&acc, 0, zFormat, ap); - return mysqlStrAccumFinish(&acc); + std::string strResult = mysqlStrAccumFinish(&acc); + // Free acc.zText to avoid memory leak. + mysqlStrAccumReset(&acc); + return strResult; } //************* MysqlDataset implementation *************** From fb954ac8daf1ae3e31a150056e0e8de3f80591bb Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Wed, 23 Oct 2024 09:09:17 +0200 Subject: [PATCH 574/651] [Playlist][M3U] Fix long line reading --- xbmc/filesystem/File.cpp | 61 ++++++++++++++++++++++++++++++++++ xbmc/filesystem/File.h | 8 +++++ xbmc/playlists/PlayListM3U.cpp | 7 ++-- 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/xbmc/filesystem/File.cpp b/xbmc/filesystem/File.cpp index 317816d792d13..9bdb0d43d87ba 100644 --- a/xbmc/filesystem/File.cpp +++ b/xbmc/filesystem/File.cpp @@ -765,6 +765,67 @@ int64_t CFile::GetPosition() const return -1; } +bool XFILE::CFile::ReadString(std::vector<char>& line) +{ + if (!m_pFile) + return false; + + line.clear(); + + if (m_pBuffer) + { + using traits = CFileStreamBuffer::traits_type; + CFileStreamBuffer::int_type aByte = m_pBuffer->sgetc(); + + while (aByte != traits::eof()) + { + aByte = m_pBuffer->sbumpc(); + + if (aByte == traits::eof()) + break; + + if (aByte == traits::to_int_type('\n')) + { + if (m_pBuffer->sgetc() == traits::to_int_type('\r')) + m_pBuffer->sbumpc(); + break; + } + + if (aByte == traits::to_int_type('\r')) + { + if (m_pBuffer->sgetc() == traits::to_int_type('\n')) + m_pBuffer->sbumpc(); + break; + } + + line.emplace_back(traits::to_char_type(aByte)); + } + + return true; + } + + try + { + // Read by buffer chuncks until to EOL or EOF + do + { + char bufferLine[1025]; + if (!m_pFile->ReadString(bufferLine, sizeof(bufferLine))) // EOF or error + return !line.empty(); + + const size_t length = std::strlen(bufferLine); + line.insert(line.end(), bufferLine, bufferLine + length); + } while (line.back() != '\n' && line.back() != '\r'); + + return true; + } + XBMCCOMMONS_HANDLE_UNCHECKED + catch (...) + { + CLog::Log(LOGERROR, "{} - Unhandled exception", __FUNCTION__); + } + return false; +} //********************************************************************************************* bool CFile::ReadString(char *szLine, int iLineLength) diff --git a/xbmc/filesystem/File.h b/xbmc/filesystem/File.h index c08b80c5b60dc..95d1eb2c95d0b 100644 --- a/xbmc/filesystem/File.h +++ b/xbmc/filesystem/File.h @@ -72,6 +72,14 @@ class CFile * or undetectable error occur, -1 in case of any explicit error */ ssize_t Read(void* bufPtr, size_t bufSize); + + /*! + * \brief String reading by line + * \param line[OUT] The line read + * \return True if has success, otherwise false for EOF or error + */ + bool ReadString(std::vector<char>& line); + bool ReadString(char *szLine, int iLineLength); /** * Attempt to write bufSize bytes from buffer bufPtr into currently opened file. diff --git a/xbmc/playlists/PlayListM3U.cpp b/xbmc/playlists/PlayListM3U.cpp index 572bdced779c8..07927b00482cd 100644 --- a/xbmc/playlists/PlayListM3U.cpp +++ b/xbmc/playlists/PlayListM3U.cpp @@ -64,8 +64,7 @@ CPlayListM3U::~CPlayListM3U(void) = default; bool CPlayListM3U::Load(const std::string& strFileName) { - char szLine[4096]; - std::string strLine; + std::vector<char> vLine; std::string strInfo; std::vector<std::pair<std::string, std::string> > properties; @@ -89,9 +88,9 @@ bool CPlayListM3U::Load(const std::string& strFileName) return false; } - while (file.ReadString(szLine, 4095)) + while (file.ReadString(vLine)) { - strLine = szLine; + std::string strLine(vLine.begin(), vLine.end()); StringUtils::Trim(strLine); if (StringUtils::StartsWith(strLine, InfoMarker)) From 52c2e3ee4ba0ec96b1d454496cf734d2eae4c7f0 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:35:57 +0200 Subject: [PATCH 575/651] [cores][PVR] Enable thumbnail extraction for PVR recordings if multiple streams is supported by the providing add-on. --- xbmc/cores/VideoPlayer/DVDFileInfo.cpp | 6 ++---- xbmc/pvr/PVRThumbLoader.cpp | 19 ++++++++++++++++++- xbmc/pvr/PVRThumbLoader.h | 1 + 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp index 33ab24885aaeb..a6b09baf0f7b5 100644 --- a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp +++ b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp @@ -18,6 +18,7 @@ #include "network/NetworkFileItemClassify.h" #include "pictures/Picture.h" #include "playlists/PlayListFileItemClassify.h" +#include "pvr/PVRThumbLoader.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" #include "utils/MemUtils.h" @@ -258,10 +259,7 @@ bool CDVDFileInfo::CanExtract(const CFileItem& fileItem) if (fileItem.m_bIsFolder) return false; - if (fileItem.IsLiveTV() || - // Due to a pvr addon api design flaw (no support for multiple concurrent streams - // per addon instance), pvr recording thumbnail extraction does not work (reliably). - URIUtils::IsPVRRecording(fileItem.GetDynPath()) || + if ((URIUtils::IsPVR(fileItem.GetPath()) && !PVR::ProvidesStreamForThumbExtraction(fileItem)) || // plugin path not fully resolved URIUtils::IsPlugin(fileItem.GetDynPath()) || URIUtils::IsUPnP(fileItem.GetPath()) || NETWORK::IsInternetStream(fileItem) || VIDEO::IsDiscStub(fileItem) || diff --git a/xbmc/pvr/PVRThumbLoader.cpp b/xbmc/pvr/PVRThumbLoader.cpp index a4b725ae617cc..1ab7a07e59a62 100644 --- a/xbmc/pvr/PVRThumbLoader.cpp +++ b/xbmc/pvr/PVRThumbLoader.cpp @@ -14,16 +14,31 @@ #include "TextureCache.h" #include "imagefiles/ImageFileURL.h" #include "pvr/PVRManager.h" +#include "pvr/addons/PVRClient.h" #include "pvr/filesystem/PVRGUIDirectory.h" #include "settings/AdvancedSettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/StringUtils.h" +#include "utils/URIUtils.h" #include "utils/log.h" #include <ctime> -using namespace PVR; +namespace PVR +{ +bool ProvidesStreamForThumbExtraction(const CFileItem& item) +{ + // Note: We must not rely on presence of a recording info tag here, but path is always set. + if (URIUtils::IsPVRRecording(item.GetPath())) + { + const std::shared_ptr<const CPVRClient> client{CServiceBroker::GetPVRManager().GetClient(item)}; + if (client) + return client->GetClientCapabilities().SupportsMultipleRecordedStreams(); + } + + return false; +} bool CPVRThumbLoader::LoadItem(CFileItem* item) { @@ -105,3 +120,5 @@ std::string CPVRThumbLoader::CreateChannelGroupThumb(const CFileItem& channelGro IMAGE_FILES::URLFromFile(channelGroupItem.GetPath(), "pvr"), std::time(nullptr)); } + +} // namespace PVR diff --git a/xbmc/pvr/PVRThumbLoader.h b/xbmc/pvr/PVRThumbLoader.h index 33e1bd44e242e..2480447ef6511 100644 --- a/xbmc/pvr/PVRThumbLoader.h +++ b/xbmc/pvr/PVRThumbLoader.h @@ -14,6 +14,7 @@ namespace PVR { +bool ProvidesStreamForThumbExtraction(const CFileItem& item); class CPVRThumbLoader : public CThumbLoader { From 704309be4a5f3f4f1bff77085b5fbb329e931fb6 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 19 Oct 2024 16:36:59 +0200 Subject: [PATCH 576/651] [addons][PVR] PVR Add-on API version 9.2.0: Def and impl of title extra info for EPG tags and recordings.. --- .../include/kodi/addon-instance/pvr/EPG.h | 16 ++++++++++++++ .../kodi/addon-instance/pvr/Recordings.h | 16 ++++++++++++++ .../kodi/c-api/addon-instance/pvr/pvr_epg.h | 9 ++++---- .../c-api/addon-instance/pvr/pvr_recordings.h | 3 ++- xbmc/pvr/addons/PVRClient.cpp | 6 +++++ xbmc/pvr/epg/EpgDatabase.cpp | 22 +++++++++++++------ xbmc/pvr/epg/EpgDatabase.h | 2 +- xbmc/pvr/epg/EpgInfoTag.cpp | 7 +++++- xbmc/pvr/epg/EpgInfoTag.h | 7 ++++++ xbmc/pvr/recordings/PVRRecording.cpp | 14 +++++++++++- xbmc/pvr/recordings/PVRRecording.h | 7 ++++++ 11 files changed, 94 insertions(+), 15 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h index 1252bfbbb25bc..d825d3fc772ba 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h @@ -60,6 +60,7 @@ class PVREPGTag : public DynamicCStructHdl<PVREPGTag, EPG_TAG> /// | **Unique broadcast id** | `unsigned int` | @ref PVREPGTag::SetUniqueBroadcastId "SetUniqueBroadcastId" | @ref PVREPGTag::GetUniqueBroadcastId "GetUniqueBroadcastId" | *required to set* /// | **Unique channel id** | `unsigned int` | @ref PVREPGTag::SetUniqueChannelId "SetUniqueChannelId" | @ref PVREPGTag::GetUniqueChannelId "GetUniqueChannelId" | *required to set* /// | **Title** | `std::string` | @ref PVREPGTag::SetTitle "SetTitle" | @ref PVREPGTag::GetTitle "GetTitle" | *required to set* + /// | **Title extra info** | `std::string` | @ref PVREPGTag::SetTitleExtraInfo "SetTitleExtraInfo" | @ref PVREPGTag::GetTitleExtraInfo "GetTitleExtraInfo" | *optional* /// | **Start time** | `time_t` | @ref PVREPGTag::SetStartTime "SetStartTime" | @ref PVREPGTag::GetStartTime "GetStartTime" | *required to set* /// | **End time** | `time_t` | @ref PVREPGTag::SetEndTime "SetEndTime" | @ref PVREPGTag::GetEndTime "GetEndTime" | *required to set* /// | **Plot outline** | `std::string` | @ref PVREPGTag::SetPlotOutline "SetPlotOutline" | @ref PVREPGTag::GetPlotOutline "GetPlotOutline" | *optional* @@ -121,6 +122,19 @@ class PVREPGTag : public DynamicCStructHdl<PVREPGTag, EPG_TAG> /// @brief To get with @ref SetTitle changed values. std::string GetTitle() const { return m_cStructure->strTitle ? m_cStructure->strTitle : ""; } + /// @brief **optional**\n + /// This event's title extra information. + void SetTitleExtraInfo(const std::string& titleExtraInfo) + { + ReallocAndCopyString(&m_cStructure->strTitleExtraInfo, titleExtraInfo.c_str()); + } + + /// @brief To get with @ref SetTitleExtraInfo changed values. + std::string GetTitleExtraInfo() const + { + return m_cStructure->strTitleExtraInfo ? m_cStructure->strTitleExtraInfo : ""; + } + /// @brief **required**\n /// Start time in UTC. /// @@ -487,6 +501,7 @@ class PVREPGTag : public DynamicCStructHdl<PVREPGTag, EPG_TAG> static void AllocResources(const EPG_TAG* source, EPG_TAG* target) { target->strTitle = AllocAndCopyString(source->strTitle); + target->strTitleExtraInfo = AllocAndCopyString(source->strTitleExtraInfo); target->strPlotOutline = AllocAndCopyString(source->strPlotOutline); target->strPlot = AllocAndCopyString(source->strPlot); target->strOriginalTitle = AllocAndCopyString(source->strOriginalTitle); @@ -507,6 +522,7 @@ class PVREPGTag : public DynamicCStructHdl<PVREPGTag, EPG_TAG> static void FreeResources(EPG_TAG* target) { FreeString(target->strTitle); + FreeString(target->strTitleExtraInfo); FreeString(target->strPlotOutline); FreeString(target->strPlot); FreeString(target->strOriginalTitle); diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h index 1fd86166911cf..4d632c3ca75a6 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h @@ -69,6 +69,7 @@ class PVRRecording : public DynamicCStructHdl<PVRRecording, PVR_RECORDING> /// |------|------|----------|----------|----------- /// | **Recording id** | `std::string` | @ref PVRRecording::SetRecordingId "SetRecordingId" | @ref PVRRecording::GetRecordingId "GetRecordingId" | *required to set* /// | **Title** | `std::string` | @ref PVRRecording::SetTitle "SetTitle" | @ref PVRRecording::GetTitle "GetTitle" | *required to set* + /// | **Title extra info** | `std::string` | @ref PVRRecording::SetTitleExtraInfo "SetTitleExtraInfo" | @ref PVRRecording::GetTitleExtraInfo "GetTitleExtraInfo" | *optional* /// | **Episode name** | `std::string` | @ref PVRRecording::SetEpisodeName "SetEpisodeName" | @ref PVRRecording::GetEpisodeName "GetEpisodeName" | *optional* /// | **Series number** | `int` | @ref PVRRecording::SetSeriesNumber "SetSeriesNumber" | @ref PVRRecording::GetSeriesNumber "GetSeriesNumber" | *optional* /// | **Episode number** | `int` | @ref PVRRecording::SetEpisodeNumber "SetEpisodeNumber" | @ref PVRRecording::GetEpisodeNumber "GetEpisodeNumber" | *optional* @@ -130,6 +131,19 @@ class PVRRecording : public DynamicCStructHdl<PVRRecording, PVR_RECORDING> /// @brief To get with @ref SetTitle changed values. std::string GetTitle() const { return m_cStructure->strTitle ? m_cStructure->strTitle : ""; } + /// @brief **optional**\n + /// The title extra information of this recording. + void SetTitleExtraInfo(const std::string& titleExtraInfo) + { + ReallocAndCopyString(&m_cStructure->strTitleExtraInfo, titleExtraInfo.c_str()); + } + + /// @brief To get with @ref SetTitleExtraInfo changed values. + std::string GetTitleExtraInfo() const + { + return m_cStructure->strTitleExtraInfo ? m_cStructure->strTitleExtraInfo : ""; + } + /// @brief **optional**\n /// Episode name (also known as subtitle). void SetEpisodeName(const std::string& episodeName) @@ -582,6 +596,7 @@ class PVRRecording : public DynamicCStructHdl<PVRRecording, PVR_RECORDING> { target->strRecordingId = AllocAndCopyString(source->strRecordingId); target->strTitle = AllocAndCopyString(source->strTitle); + target->strTitleExtraInfo = AllocAndCopyString(source->strTitleExtraInfo); target->strEpisodeName = AllocAndCopyString(source->strEpisodeName); target->strDirectory = AllocAndCopyString(source->strDirectory); target->strPlotOutline = AllocAndCopyString(source->strPlotOutline); @@ -602,6 +617,7 @@ class PVRRecording : public DynamicCStructHdl<PVRRecording, PVR_RECORDING> { FreeString(target->strRecordingId); FreeString(target->strTitle); + FreeString(target->strTitleExtraInfo); FreeString(target->strEpisodeName); FreeString(target->strDirectory); FreeString(target->strPlotOutline); diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h index d41d017a93d93..8959658f28399 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h @@ -624,6 +624,11 @@ extern "C" unsigned int iUniqueBroadcastId; unsigned int iUniqueChannelId; const char* strTitle; + const char* strTitleExtraInfo; + int iSeriesNumber; + int iEpisodeNumber; + int iEpisodePartNumber; + const char* strEpisodeName; time_t startTime; time_t endTime; const char* strPlotOutline; @@ -644,10 +649,6 @@ extern "C" const char* strParentalRatingIcon; const char* strParentalRatingSource; int iStarRating; - int iSeriesNumber; - int iEpisodeNumber; - int iEpisodePartNumber; - const char* strEpisodeName; unsigned int iFlags; const char* strSeriesLink; } EPG_TAG; diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h index 8b0e19c5b390c..7ed206a8e7d24 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h @@ -111,10 +111,11 @@ extern "C" { const char* strRecordingId; const char* strTitle; - const char* strEpisodeName; + const char* strTitleExtraInfo; int iSeriesNumber; int iEpisodeNumber; int iEpisodePartNumber; + const char* strEpisodeName; int iYear; const char* strDirectory; const char* strPlotOutline; diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 09db00dc268b6..2affaf4053308 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -122,6 +122,7 @@ class CAddonRecording : public PVR_RECORDING explicit CAddonRecording(const CPVRRecording& recording) : m_recordingId(recording.ClientRecordingID()), m_title(recording.m_strTitle), + m_titleExtraInfo(recording.TitleExtraInfo()), m_episodeName(recording.m_strShowTitle), m_directory(recording.Directory()), m_plotOutline(recording.m_strPlotOutline), @@ -146,6 +147,7 @@ class CAddonRecording : public PVR_RECORDING strRecordingId = m_recordingId.c_str(); strTitle = m_title.c_str(); + strTitleExtraInfo = m_titleExtraInfo.c_str(); strEpisodeName = m_episodeName.c_str(); iSeriesNumber = recording.m_iSeason; iEpisodeNumber = recording.m_iEpisode; @@ -189,6 +191,7 @@ class CAddonRecording : public PVR_RECORDING private: const std::string m_recordingId; const std::string m_title; + const std::string m_titleExtraInfo; const std::string m_episodeName; const std::string m_directory; const std::string m_plotOutline; @@ -293,6 +296,7 @@ class CAddonEpgTag : public EPG_TAG public: explicit CAddonEpgTag(const CPVREpgInfoTag& tag) : m_title(tag.Title()), + m_titleExtraInfo(tag.TitleExtraInfo()), m_plotOutline(tag.PlotOutline()), m_plot(tag.Plot()), m_originalTitle(tag.OriginalTitle()), @@ -331,6 +335,7 @@ class CAddonEpgTag : public EPG_TAG iGenreType = tag.GenreType(); iGenreSubType = tag.GenreSubType(); strTitle = m_title.c_str(); + strTitleExtraInfo = m_titleExtraInfo.c_str(); strPlotOutline = m_plotOutline.c_str(); strPlot = m_plot.c_str(); strOriginalTitle = m_originalTitle.c_str(); @@ -360,6 +365,7 @@ class CAddonEpgTag : public EPG_TAG } const std::string m_title; + const std::string m_titleExtraInfo; const std::string m_plotOutline; const std::string m_plot; const std::string m_originalTitle; diff --git a/xbmc/pvr/epg/EpgDatabase.cpp b/xbmc/pvr/epg/EpgDatabase.cpp index 17c4d4354d88e..32150e434762a 100644 --- a/xbmc/pvr/epg/EpgDatabase.cpp +++ b/xbmc/pvr/epg/EpgDatabase.cpp @@ -96,7 +96,8 @@ void CPVREpgDatabase::CreateTables() "sSeriesLink varchar(255), " "sParentalRatingCode varchar(64)," "sParentalRatingIcon varchar(512)," - "sParentalRatingSource varchar(128)" + "sParentalRatingSource varchar(128)," + "sTitleExtraInfo varchar(128)" ")"); CLog::LogFC(LOGDEBUG, LOGEPG, "Creating table 'lastepgscan'"); @@ -347,6 +348,12 @@ void CPVREpgDatabase::UpdateTables(int iVersion) m_pDS->exec("UPDATE savedsearches SET bStartAnyTime = 1;"); m_pDS->exec("UPDATE savedsearches SET bEndAnyTime = 1;"); } + + if (iVersion < 20) + { + m_pDS->exec("ALTER TABLE epgtags ADD sTitleExtraInfo varchar(128);"); + m_pDS->exec("UPDATE epgtags SET sTitleExtraInfo = ''"); + } } bool CPVREpgDatabase::DeleteEpg() @@ -479,6 +486,7 @@ std::shared_ptr<CPVREpgInfoTag> CPVREpgDatabase::CreateEpgTag( newTag->m_iGenreType = m_pDS->fv("iGenreType").get_asInt(); newTag->m_iGenreSubType = m_pDS->fv("iGenreSubType").get_asInt(); newTag->m_strGenreDescription = m_pDS->fv("sGenre").get_asString(); + newTag->m_titleExtraInfo = m_pDS->fv("sTitleExtraInfo").get_asString(); return newTag; } @@ -1282,9 +1290,9 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag) "sIconPath, iGenreType, iGenreSubType, sGenre, sFirstAired, iParentalRating, iStarRating, " "iSeriesId, " "iEpisodeId, iEpisodePart, sEpisodeName, iFlags, sSeriesLink, sParentalRatingCode, " - "iBroadcastUid, sParentalRatingIcon, sParentalRatingSource) " + "iBroadcastUid, sParentalRatingIcon, sParentalRatingSource, sTitleExtraInfo) " "VALUES (%u, %u, %u, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %i, '%s', '%s', %i, %i, " - "'%s', '%s', %i, %i, %i, %i, %i, '%s', %i, '%s', '%s', %i, '%s', '%s');", + "'%s', '%s', %i, %i, %i, %i, %i, '%s', %i, '%s', '%s', %i, '%s', '%s', '%s');", tag.EpgID(), static_cast<unsigned int>(iStartTime), static_cast<unsigned int>(iEndTime), tag.Title().c_str(), tag.PlotOutline().c_str(), tag.Plot().c_str(), tag.OriginalTitle().c_str(), tag.DeTokenize(tag.Cast()).c_str(), @@ -1294,7 +1302,7 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag) tag.SeriesNumber(), tag.EpisodeNumber(), tag.EpisodePart(), tag.EpisodeName().c_str(), tag.Flags(), tag.SeriesLink().c_str(), tag.ParentalRatingCode().c_str(), tag.UniqueBroadcastID(), tag.ParentalRatingIcon().c_str(), - tag.ParentalRatingSource().c_str()); + tag.ParentalRatingSource().c_str(), tag.TitleExtraInfo().c_str()); } else { @@ -1305,9 +1313,9 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag) "sIconPath, iGenreType, iGenreSubType, sGenre, sFirstAired, iParentalRating, iStarRating, " "iSeriesId, " "iEpisodeId, iEpisodePart, sEpisodeName, iFlags, sSeriesLink, sParentalRatingCode, " - "iBroadcastUid, idBroadcast, sParentalRatingIcon, sParentalRatingSource) " + "iBroadcastUid, idBroadcast, sParentalRatingIcon, sParentalRatingSource, sTitleExtraInfo) " "VALUES (%u, %u, %u, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %i, '%s', '%s', %i, %i, " - "'%s', '%s', %i, %i, %i, %i, %i, '%s', %i, '%s', '%s', %i, %i, '%s', '%s');", + "'%s', '%s', %i, %i, %i, %i, %i, '%s', %i, '%s', '%s', %i, %i, '%s', '%s', '%s');", tag.EpgID(), static_cast<unsigned int>(iStartTime), static_cast<unsigned int>(iEndTime), tag.Title().c_str(), tag.PlotOutline().c_str(), tag.Plot().c_str(), tag.OriginalTitle().c_str(), tag.DeTokenize(tag.Cast()).c_str(), @@ -1317,7 +1325,7 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag) tag.SeriesNumber(), tag.EpisodeNumber(), tag.EpisodePart(), tag.EpisodeName().c_str(), tag.Flags(), tag.SeriesLink().c_str(), tag.ParentalRatingCode().c_str(), tag.UniqueBroadcastID(), iBroadcastId, tag.ParentalRatingIcon().c_str(), - tag.ParentalRatingSource().c_str()); + tag.ParentalRatingSource().c_str(), tag.TitleExtraInfo().c_str()); } QueueInsertQuery(strQuery); diff --git a/xbmc/pvr/epg/EpgDatabase.h b/xbmc/pvr/epg/EpgDatabase.h index 8ea846bf42383..077a7fbb37fff 100644 --- a/xbmc/pvr/epg/EpgDatabase.h +++ b/xbmc/pvr/epg/EpgDatabase.h @@ -66,7 +66,7 @@ namespace PVR * @brief Get the minimal database version that is required to operate correctly. * @return The minimal database version. */ - int GetSchemaVersion() const override { return 19; } + int GetSchemaVersion() const override { return 20; } /*! * @brief Get the default sqlite database filename. diff --git a/xbmc/pvr/epg/EpgInfoTag.cpp b/xbmc/pvr/epg/EpgInfoTag.cpp index b731f685c78fd..cafb7aceb4aab 100644 --- a/xbmc/pvr/epg/EpgInfoTag.cpp +++ b/xbmc/pvr/epg/EpgInfoTag.cpp @@ -110,6 +110,8 @@ CPVREpgInfoTag::CPVREpgInfoTag(const EPG_TAG& data, // explicit NULL check, because there is no implicit NULL constructor for std::string if (data.strTitle) m_strTitle = data.strTitle; + if (data.strTitleExtraInfo) + m_titleExtraInfo = data.strTitleExtraInfo; if (data.strGenreDescription) m_strGenreDescription = data.strGenreDescription; if (data.strPlotOutline) @@ -178,6 +180,7 @@ void CPVREpgInfoTag::Serialize(CVariant& value) const value["parentalratingsource"] = m_parentalRatingSource; value["rating"] = m_iStarRating; value["title"] = m_strTitle; + value["titleextrainfo"] = m_titleExtraInfo; value["plotoutline"] = m_strPlotOutline; value["plot"] = m_strPlot; value["originaltitle"] = m_strOriginalTitle; @@ -482,7 +485,8 @@ bool CPVREpgInfoTag::Update(const CPVREpgInfoTag& tag, bool bUpdateBroadcastId / m_iSeriesNumber != tag.m_iSeriesNumber || m_strEpisodeName != tag.m_strEpisodeName || m_iUniqueBroadcastID != tag.m_iUniqueBroadcastID || m_iEpgID != tag.m_iEpgID || m_genre != tag.m_genre || m_iconPath != tag.m_iconPath || m_iFlags != tag.m_iFlags || - m_strSeriesLink != tag.m_strSeriesLink || m_channelData != tag.m_channelData); + m_strSeriesLink != tag.m_strSeriesLink || m_channelData != tag.m_channelData || + m_titleExtraInfo != tag.m_titleExtraInfo); if (bUpdateBroadcastId) bChanged |= (m_iDatabaseID != tag.m_iDatabaseID); @@ -493,6 +497,7 @@ bool CPVREpgInfoTag::Update(const CPVREpgInfoTag& tag, bool bUpdateBroadcastId / m_iDatabaseID = tag.m_iDatabaseID; m_strTitle = tag.m_strTitle; + m_titleExtraInfo = tag.m_titleExtraInfo; m_strPlotOutline = tag.m_strPlotOutline; m_strPlot = tag.m_strPlot; m_strOriginalTitle = tag.m_strOriginalTitle; diff --git a/xbmc/pvr/epg/EpgInfoTag.h b/xbmc/pvr/epg/EpgInfoTag.h index d5ae0518ec607..65858dca2bfd9 100644 --- a/xbmc/pvr/epg/EpgInfoTag.h +++ b/xbmc/pvr/epg/EpgInfoTag.h @@ -195,6 +195,12 @@ class CPVREpgInfoTag final : public ISerializable, */ const std::string& Title() const { return m_strTitle; } + /*! + * @brief Get the title extra information of this event. + * @return The title extra info. + */ + const std::string& TitleExtraInfo() const { return m_titleExtraInfo; } + /*! * @brief Get the plot outline of this event. * @return The plot outline. @@ -505,6 +511,7 @@ class CPVREpgInfoTag final : public ISerializable, int m_iEpisodePart = -1; /*!< episode part number */ unsigned int m_iUniqueBroadcastID = 0; /*!< unique broadcast ID */ std::string m_strTitle; /*!< title */ + std::string m_titleExtraInfo; /*!< title extra info */ std::string m_strPlotOutline; /*!< plot outline */ std::string m_strPlot; /*!< plot */ std::string m_strOriginalTitle; /*!< original title */ diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp index 9c5f9d4751af3..ff07d4e61129a 100644 --- a/xbmc/pvr/recordings/PVRRecording.cpp +++ b/xbmc/pvr/recordings/PVRRecording.cpp @@ -86,6 +86,8 @@ CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClien m_strRecordingId = recording.strRecordingId; if (recording.strTitle) m_strTitle = recording.strTitle; + if (recording.strTitleExtraInfo) + m_titleExtraInfo = recording.strTitleExtraInfo; if (recording.strEpisodeName) m_strShowTitle = recording.strEpisodeName; m_iSeason = recording.iSeriesNumber; @@ -198,7 +200,8 @@ bool CPVRRecording::operator==(const CPVRRecording& right) const m_parentalRatingCode == right.m_parentalRatingCode && m_parentalRatingIcon == right.m_parentalRatingIcon && m_parentalRatingSource == right.m_parentalRatingSource && - m_episodePartNumber == right.m_episodePartNumber); + m_episodePartNumber == right.m_episodePartNumber && + m_titleExtraInfo == right.m_titleExtraInfo); } bool CPVRRecording::operator!=(const CPVRRecording& right) const @@ -227,6 +230,7 @@ void CPVRRecording::Serialize(CVariant& value) const value["parentalratingicon"] = m_parentalRatingIcon; value["parentalratingsource"] = m_parentalRatingSource; value["episodepart"] = m_episodePartNumber; + value["titleextrainfo"] = m_titleExtraInfo; if (!value.isMember("art")) value["art"] = CVariant(CVariant::VariantTypeObject); @@ -282,6 +286,7 @@ void CPVRRecording::Reset() m_parentalRatingCode.clear(); m_parentalRatingIcon.clear(); m_parentalRatingSource.clear(); + m_titleExtraInfo.clear(); CVideoInfoTag::Reset(); } @@ -444,6 +449,7 @@ void CPVRRecording::Update(const CPVRRecording& tag, const CPVRClient& client) m_strRecordingId = tag.m_strRecordingId; m_iClientId = tag.m_iClientId; m_strTitle = tag.m_strTitle; + m_titleExtraInfo = tag.m_titleExtraInfo; m_strShowTitle = tag.m_strShowTitle; m_iSeason = tag.m_iSeason; m_iEpisode = tag.m_iEpisode; @@ -762,3 +768,9 @@ int CPVRRecording::EpisodePart() const std::unique_lock<CCriticalSection> lock(m_critSection); return m_episodePartNumber; } + +const std::string& CPVRRecording::TitleExtraInfo() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_titleExtraInfo; +} diff --git a/xbmc/pvr/recordings/PVRRecording.h b/xbmc/pvr/recordings/PVRRecording.h index 4e70fc6ffeec3..49c18abfb4383 100644 --- a/xbmc/pvr/recordings/PVRRecording.h +++ b/xbmc/pvr/recordings/PVRRecording.h @@ -530,6 +530,12 @@ class CPVRRecording final : public CVideoInfoTag */ int EpisodePart() const; + /*! + * @brief Get the title extra information of this recording. + * @return The title extra info. + */ + const std::string& TitleExtraInfo() const; + private: CPVRRecording(const CPVRRecording& tag) = delete; CPVRRecording& operator=(const CPVRRecording& other) = delete; @@ -569,6 +575,7 @@ class CPVRRecording final : public CVideoInfoTag std::string m_parentalRatingIcon; /*!< parental rating icon path */ std::string m_parentalRatingSource; /*!< parental rating source */ int m_episodePartNumber{PVR_RECORDING_INVALID_SERIES_EPISODE}; /*!< episode part number */ + std::string m_titleExtraInfo; /*!< title extra info */ mutable CCriticalSection m_critSection; }; From ef380ed123c8abb43b31a929d8778fd8ebf15038 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 19 Oct 2024 18:55:39 +0200 Subject: [PATCH 577/651] [guilib][PVR] Add ListItem.TitleExtraInfo, VideoPlayer.TitleExtraInfo GUI labels and implementation for PVR recordings and EPG tags. --- xbmc/GUIInfoManager.cpp | 19 ++++++++++++++++++- xbmc/guilib/guiinfo/GUIInfoLabels.h | 2 ++ xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 22 ++++++++++++++++------ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index ee12786f60a66..6ade6e27938dc 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -4016,6 +4016,14 @@ const infomap musicplayer[] = {{ "title", MUSICPLAYER_TITLE }, /// @skinning_v22 **[New Infolabel]** \link VideoPlayer_MediaProviders `VideoPlayer.MediaProviders`\endlink /// <p> /// } +/// \table_row3{ <b>`VideoPlayer.TitleExtraInfo`</b>, +/// \anchor VideoPlayer_TitleExtraInfo +/// _string_, +/// @return string containing extra information, enriching the title of the currently playing media, if any. +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link VideoPlayer_TitleExtraInfo `VideoPlayer.TitleExtraInfo`\endlink +/// <p> +/// } /// \table_end /// /// ----------------------------------------------------------------------------- @@ -4101,6 +4109,7 @@ const infomap videoplayer[] = {{ "title", VIDEOPLAYER_TITLE }, { "hasvideoversions", VIDEOPLAYER_HAS_VIDEOVERSIONS}, { "episodepart", VIDEOPLAYER_EPISODEPART}, { "mediaproviders", VIDEOPLAYER_MEDIAPROVIDERS }, + { "titleextrainfo", VIDEOPLAYER_TITLE_EXTRAINFO }, }; // clang-format on @@ -7118,7 +7127,14 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @skinning_v22 **[New Infolabel]** \link ListItem_MediaProviders `ListItem.MediaProviders`\endlink /// <p> /// } -/// +/// \table_row3{ <b>`ListItem.TitleExtraInfo`</b>, +/// \anchor ListItem_TitleExtraInfo +/// _string_, +/// @return string containing extra information, enriching the title of the item. +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link ListItem_TitleExtraInfo `ListItem.TitleExtraInfo`\endlink +/// <p> +/// } /// \table_end /// /// ----------------------------------------------------------------------------- @@ -7348,6 +7364,7 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "pvrgrouporigin", LISTITEM_PVR_GROUP_ORIGIN }, { "episodepart", LISTITEM_EPISODEPART }, { "mediaproviders", LISTITEM_MEDIAPROVIDERS }, + { "titleextrainfo", LISTITEM_TITLE_EXTRAINFO }, }; // clang-format on diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index 3138987c6a4b3..4a688123f282b 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -298,6 +298,7 @@ #define VIDEOPLAYER_HAS_VIDEOVERSIONS 311 // PVR infolabels +#define VIDEOPLAYER_TITLE_EXTRAINFO 312 #define VIDEOPLAYER_EVENT 313 #define VIDEOPLAYER_EPISODENAME 314 #define VIDEOPLAYER_STARTTIME 315 @@ -1002,6 +1003,7 @@ static constexpr unsigned int SYSTEM_LOCALE = 1012; #define LISTITEM_PARENTAL_RATING_SOURCE (LISTITEM_START + 222) #define LISTITEM_EPISODEPART (LISTITEM_START + 223) #define LISTITEM_MEDIAPROVIDERS (LISTITEM_START + 224) +#define LISTITEM_TITLE_EXTRAINFO (LISTITEM_START + 225) #define LISTITEM_END (LISTITEM_START + 2500) diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index 2ec4b22947eed..dd9396a0ff2c2 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -481,8 +481,6 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, case VIDEOPLAYER_EPISODENAME: case LISTITEM_EPISODENAME: strValue = recording->EpisodeName(); - // fixup multiline episode name strings (which do not fit in any way in our GUI) - StringUtils::Replace(strValue, "\n", ", "); return true; case VIDEOPLAYER_CHANNEL_NAME: case LISTITEM_CHANNEL_NAME: @@ -617,6 +615,14 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, } } return false; + case LISTITEM_TITLE_EXTRAINFO: + case VIDEOPLAYER_TITLE_EXTRAINFO: + { + strValue = recording->TitleExtraInfo(); + // fixup multiline info strings (which do not fit in any way in our GUI) + StringUtils::Replace(strValue, "\n", ", "); + return true; + } } return false; } @@ -817,11 +823,7 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, case VIDEOPLAYER_EPISODENAME: case LISTITEM_EPISODENAME: if (!CServiceBroker::GetPVRManager().IsParentalLocked(epgTag)) - { strValue = epgTag->EpisodeName(); - // fixup multiline episode name strings (which do not fit in any way in our GUI) - StringUtils::Replace(strValue, "\n", ", "); - } return true; case VIDEOPLAYER_CAST: case LISTITEM_CAST: @@ -891,6 +893,14 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, } return false; } + case LISTITEM_TITLE_EXTRAINFO: + case VIDEOPLAYER_TITLE_EXTRAINFO: + { + strValue = epgTag->TitleExtraInfo(); + // fixup multiline info strings (which do not fit in any way in our GUI) + StringUtils::Replace(strValue, "\n", ", "); + return true; + } } } From e13c92902fd4be7300c7eac688417d61f37b7980 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 19 Oct 2024 23:12:54 +0200 Subject: [PATCH 578/651] [Estuary] Add support for ListItem.TitleExtraInfo as fallback for episode info. --- addons/skin.estuary/xml/DialogPVRInfo.xml | 2 +- addons/skin.estuary/xml/Home.xml | 8 ++++---- addons/skin.estuary/xml/Includes_PVR.xml | 14 +++++++------- addons/skin.estuary/xml/Variables.xml | 22 +++++++++++++++++----- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/addons/skin.estuary/xml/DialogPVRInfo.xml b/addons/skin.estuary/xml/DialogPVRInfo.xml index 561d898d966a7..f66419cc39e8b 100644 --- a/addons/skin.estuary/xml/DialogPVRInfo.xml +++ b/addons/skin.estuary/xml/DialogPVRInfo.xml @@ -117,7 +117,7 @@ </control> <include content="InfoDialogTopBarInfo"> <param name="main_label" value="$INFO[ListItem.Title] $INFO[ListItem.Year,([COLOR grey],[/COLOR])]" /> - <param name="sub_label" value="$VAR[FlagDashLabel]$VAR[SeasonEpisodeLabel,[COLOR grey],[/COLOR]]$INFO[ListItem.EpisodeName]" /> + <param name="sub_label" value="$VAR[FlagDashLabel]$VAR[SeasonEpisodeAndNameLabel]" /> <param name="posy" value="40" /> </include> </control> diff --git a/addons/skin.estuary/xml/Home.xml b/addons/skin.estuary/xml/Home.xml index 5ac611a33f088..386cdcde54767 100644 --- a/addons/skin.estuary/xml/Home.xml +++ b/addons/skin.estuary/xml/Home.xml @@ -415,7 +415,7 @@ <param name="item_limit" value="15"/> <param name="list_id" value="12300"/> <param name="label" value="$INFO[ListItem.Title]$INFO[ListItem.Date, (,)]"/> - <param name="label2" value="$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName]"/> + <param name="label2" value="$VAR[SeasonEpisodeAndNameLabel]"/> </include> <include content="WidgetListPVR" condition="System.HasPVRAddon + !Skin.HasSetting(home_no_tv_timers_widget)"> <param name="content_path" value="pvr://timers/tv/timers/?view=hidedisabled"/> @@ -426,7 +426,7 @@ <param name="item_limit" value="15"/> <param name="list_id" value="12400"/> <param name="label" value="$INFO[ListItem.Title]$INFO[ListItem.Date, (,)]"/> - <param name="label2" value="$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName]"/> + <param name="label2" value="$VAR[SeasonEpisodeAndNameLabel]"/> </include> <include content="WidgetListPVR" condition="System.HasPVRAddon + !Skin.HasSetting(home_no_tv_channelgroups_widget)"> <param name="content_path" value="pvr://channels/tv"/> @@ -498,7 +498,7 @@ <param name="item_limit" value="15"/> <param name="list_id" value="13300"/> <param name="label" value="$INFO[ListItem.Title]$INFO[ListItem.Date, (,)]"/> - <param name="label2" value="$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName]"/> + <param name="label2" value="$VAR[SeasonEpisodeAndNameLabel]"/> </include> <include content="WidgetListPVR" condition="System.HasPVRAddon + !Skin.HasSetting(home_no_radio_timers_widget)"> <param name="content_path" value="pvr://timers/radio/timers/?view=hidedisabled"/> @@ -509,7 +509,7 @@ <param name="item_limit" value="15"/> <param name="list_id" value="13400"/> <param name="label" value="$INFO[ListItem.Title]$INFO[ListItem.Date, (,)]"/> - <param name="label2" value="$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName]"/> + <param name="label2" value="$VAR[SeasonEpisodeAndNameLabel]"/> </include> <include content="WidgetListPVR" condition="System.HasPVRAddon + !Skin.HasSetting(home_no_radio_channelgroups_widget)"> <param name="content_path" value="pvr://channels/radio"/> diff --git a/addons/skin.estuary/xml/Includes_PVR.xml b/addons/skin.estuary/xml/Includes_PVR.xml index 75840d8b1cabe..674f0ba835641 100644 --- a/addons/skin.estuary/xml/Includes_PVR.xml +++ b/addons/skin.estuary/xml/Includes_PVR.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <includes> <expression name="listitem_has_episode_info">!String.IsEmpty(ListItem.Episode) | !String.IsEmpty(ListItem.EpisodeName) | !String.IsEmpty(ListItem.Season)</expression> - <expression name="listitem_has_epg_event_info">!String.IsEmpty(ListItem.EpgEventTitle) | $EXP[listitem_has_episode_info]</expression> + <expression name="listitem_has_epg_event_info">!String.IsEmpty(ListItem.EpgEventTitle) | !String.IsEmpty(ListItem.TitleExtraInfo) | $EXP[listitem_has_episode_info]</expression> <include name="PVRListItemLayout"> <definition> <control type="label"> @@ -430,7 +430,7 @@ <width>830</width> <height>70</height> <scroll>true</scroll> - <label>[I]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/I]</label> + <label>[I]$VAR[SeasonEpisodeAndNameLabel][/I]</label> </control> <control type="textbox"> <top>465</top> @@ -467,7 +467,7 @@ <height>90</height> <width>830</width> <aligny>center</aligny> - <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName, (,)]$INFO[ListItem.Property(totalcount), (, $LOCALIZE[31036])]</label> + <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$VAR[SeasonEpisodeAndNameLabel, (,)]$INFO[ListItem.Property(totalcount), (, $LOCALIZE[31036])]</label> <shadowcolor>text_shadow</shadowcolor> </control> <control type="label"> @@ -476,7 +476,7 @@ <height>90</height> <width>830</width> <aligny>center</aligny> - <label>[COLOR grey]$INFO[ListItem.ChannelName,,[CR]][/COLOR]$INFO[ListItem.EpgEventTitle]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName, (,)]</label> + <label>[COLOR grey]$INFO[ListItem.ChannelName,,[CR]][/COLOR]$INFO[ListItem.EpgEventTitle]$VAR[SeasonEpisodeAndNameLabel, (,)]</label> <shadowcolor>text_shadow</shadowcolor> </control> </focusedlayout> @@ -487,7 +487,7 @@ <height>90</height> <width>830</width> <aligny>center</aligny> - <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName, (,)]$INFO[ListItem.Property(totalcount), (, $LOCALIZE[31036])]</label> + <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$VAR[SeasonEpisodeAndNameLabel, (,)]$INFO[ListItem.Property(totalcount), (, $LOCALIZE[31036])]</label> <shadowcolor>text_shadow</shadowcolor> </control> <control type="label"> @@ -496,7 +496,7 @@ <height>90</height> <width>830</width> <aligny>center</aligny> - <label>[COLOR grey]$INFO[ListItem.ChannelName,,[CR]][/COLOR]$INFO[ListItem.EpgEventTitle]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName, (,)]</label> + <label>[COLOR grey]$INFO[ListItem.ChannelName,,[CR]][/COLOR]$INFO[ListItem.EpgEventTitle]$VAR[SeasonEpisodeAndNameLabel, (,)]</label> <shadowcolor>text_shadow</shadowcolor> </control> </itemlayout> @@ -557,7 +557,7 @@ <top>35</top> <width>100%</width> <height>30</height> - <label>$VAR[FlagDashLabel]$VAR[SeasonEpisodeLabel,[COLOR grey],[/COLOR]]$INFO[ListItem.EpisodeName]</label> + <label>$VAR[FlagDashLabel]$VAR[SeasonEpisodeLabel,[COLOR grey],[/COLOR]]$VAR[EpisodeNameLabel]</label> </control> </control> <control type="textbox"> diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 49382061a9d04..cc0a2c5b0960b 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -416,7 +416,7 @@ <value condition="VideoPlayer.Content(musicvideos)">$VAR[NowPlayingSublabelVar,,[CR]]$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]: [/COLOR]]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value> <value condition="VideoPlayer.Content(episodes) + !player.chaptercount + ![String.IsEmpty(VideoPlayer.Episode) | String.IsEmpty(VideoPlayer.Title)]">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.Title, - ]</value> <value condition="VideoPlayer.Content(episodes) + player.chaptercount + ![String.IsEmpty(VideoPlayer.Episode) | String.IsEmpty(VideoPlayer.Title)]">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.Title, - ,[CR]]$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value> - <value condition="[VideoPlayer.Content(LiveTV) | PVR.IsPlayingRecording | PVR.IsPlayingEpgTag] ">$VAR[VideoPlayerSeasonEpisodeLabel,[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR]]</value> + <value condition="[VideoPlayer.Content(LiveTV) | PVR.IsPlayingRecording | PVR.IsPlayingEpgTag] ">$VAR[VideoPlayerSeasonEpisodeAndNameLabel]</value> <value condition="player.chaptercount + [!VideoPlayer.Content(episodes) + !VideoPlayer.Content(LiveTV)]">$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value> <value>$INFO[VideoPlayer.Genre]</value> </variable> @@ -611,10 +611,18 @@ <value condition="!String.IsEmpty(ListItem.EpisodeName) + !String.IsEmpty(ListItem.EpisodePart)">$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E]$INFO[ListItem.EpisodePart,/,: ]</value> <value>$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E,: ]</value> </variable> - <variable name="VideoPlayerSeasonEpisodeLabel"> - <value condition="!String.IsEmpty(VideoPlayer.Season)"> $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.EpisodePart,/]$INFO[VideoPlayer.EpisodeName, - ]</value> - <value condition="!String.IsEmpty(VideoPlayer.Episode)"> $INFO[VideoPlayer.Episode]$INFO[VideoPlayer.EpisodePart,/]$INFO[VideoPlayer.EpisodeName, - ]</value> - <value condition="!String.IsEmpty(VideoPlayer.EpisodeName)"> $INFO[VideoPlayer.EpisodeName]</value> + <variable name="EpisodeNameLabel"> + <value condition="!String.IsEmpty(ListItem.EpisodeName)">$INFO[ListItem.EpisodeName]</value> + <value condition="String.IsEmpty(ListItem.Season) + String.IsEmpty(ListItem.Episode) + String.IsEmpty(ListItem.EpisodePart)">$INFO[ListItem.TitleExtraInfo]</value> + </variable> + <variable name="SeasonEpisodeAndNameLabel"> + <value>$VAR[SeasonEpisodeLabel]$VAR[EpisodeNameLabel]</value> + </variable> + <variable name="VideoPlayerSeasonEpisodeAndNameLabel"> + <value condition="!String.IsEmpty(VideoPlayer.Season)">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.EpisodePart,/]$INFO[VideoPlayer.EpisodeName, - ]</value> + <value condition="!String.IsEmpty(VideoPlayer.Episode)">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Episode]$INFO[VideoPlayer.EpisodePart,/]$INFO[VideoPlayer.EpisodeName, - ]</value> + <value condition="!String.IsEmpty(VideoPlayer.EpisodeName)">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.EpisodeName]</value> + <value>$INFO[VideoPlayer.TitleExtraInfo]</value> </variable> <variable name="FirstAiredLabel"> <value condition="String.IsEqual(ListItem.DBType,movie)">$LOCALIZE[20473]</value> @@ -664,12 +672,16 @@ <value condition="ListItem.IsFolder">[COLOR grey]$INFO[ListItem.Timertype][/COLOR]</value> <value condition="$EXP[listitem_has_episode_info] + !String.IsEmpty(ListItem.EpgEventTitle) + !String.StartsWith(ListItem.EpisodeName,ListItem.EpgEventTitle)">$INFO[ListItem.EpgEventTitle] | [COLOR grey]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/COLOR]</value> <value condition="$EXP[listitem_has_episode_info]">[COLOR grey]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/COLOR]</value> + <value condition="!String.IsEmpty(ListItem.TitleExtraInfo) + !String.StartsWith(ListItem.TitleExtraInfo,ListItem.EpgEventTitle)">$INFO[ListItem.EpgEventTitle]$INFO[ListItem.TitleExtraInfo, | [COLOR grey],[/COLOR]]</value> + <value condition="!String.IsEmpty(ListItem.TitleExtraInfo)">[COLOR grey]$INFO[ListItem.TitleExtraInfo][/COLOR]</value> <value>$INFO[ListItem.EpgEventTitle]</value> </variable> <variable name="PVRListItemSubLabelFocused"> <value condition="ListItem.IsFolder">$INFO[ListItem.Timertype]</value> <value condition="$EXP[listitem_has_episode_info] + !String.IsEmpty(ListItem.EpgEventTitle) + !String.StartsWith(ListItem.EpisodeName,ListItem.EpgEventTitle)">$INFO[ListItem.EpgEventTitle] | [COLOR grey]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/COLOR]</value> <value condition="$EXP[listitem_has_episode_info]">$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName]</value> + <value condition="!String.IsEmpty(ListItem.TitleExtraInfo) + !String.StartsWith(ListItem.TitleExtraInfo,ListItem.EpgEventTitle)">$INFO[ListItem.EpgEventTitle]$INFO[ListItem.TitleExtraInfo, | [COLOR grey],[/COLOR]]</value> + <value condition="!String.IsEmpty(ListItem.TitleExtraInfo)">$INFO[ListItem.TitleExtraInfo]</value> <value>$INFO[ListItem.EpgEventTitle]</value> </variable> <variable name="PVRInfoPanelDateDurationLabel"> From 5d0b01be14664e78b78557ab114224c8e790f412 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 20 Oct 2024 22:26:15 +0200 Subject: [PATCH 579/651] [PVR] Remove PVR Add-on API 9.1.0 compatibility code which is not needed anymore. --- xbmc/pvr/addons/PVRClient.cpp | 9 +++------ xbmc/pvr/timers/PVRTimerInfoTag.cpp | 25 +++++++++---------------- xbmc/pvr/timers/PVRTimerInfoTag.h | 8 +------- xbmc/pvr/timers/PVRTimerType.cpp | 13 ++----------- xbmc/pvr/timers/PVRTimerType.h | 9 +-------- 5 files changed, 16 insertions(+), 48 deletions(-) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 2affaf4053308..d80af88cd0567 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -10,7 +10,6 @@ #include "ServiceBroker.h" #include "addons/AddonManager.h" -#include "addons/AddonVersion.h" #include "addons/binary-addons/AddonDll.h" #include "cores/EdlEdit.h" #include "cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.h" @@ -1427,8 +1426,8 @@ PVR_ERROR CPVRClient::UpdateTimerTypes() CLog::LogF(LOGERROR, "Invalid timer type supplied by add-on {}.", GetID()); continue; } - timerTypes.emplace_back(std::make_shared<CPVRTimerType>( - *(types_array[i]), m_iClientId, Addon()->GetTypeVersionDll(ADDON_INSTANCE_PVR))); + timerTypes.emplace_back( + std::make_shared<CPVRTimerType>(*(types_array[i]), m_iClientId)); } } @@ -2338,9 +2337,7 @@ void CPVRClient::cb_transfer_timer_entry(void* kodiInstance, // transfer this entry to the timers container const std::shared_ptr<CPVRTimerInfoTag> transferTimer = - std::make_shared<CPVRTimerInfoTag>( - *timer, channel, client->GetID(), - client->Addon()->GetTypeVersionDll(ADDON_INSTANCE_PVR)); + std::make_shared<CPVRTimerInfoTag>(*timer, channel, client->GetID()); CPVRTimersContainer* timers = static_cast<CPVRTimersContainer*>(handle->dataAddress); timers->UpdateFromClient(transferTimer); diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.cpp b/xbmc/pvr/timers/PVRTimerInfoTag.cpp index 9c49d9d850a10..50cc5662d420b 100644 --- a/xbmc/pvr/timers/PVRTimerInfoTag.cpp +++ b/xbmc/pvr/timers/PVRTimerInfoTag.cpp @@ -9,7 +9,6 @@ #include "PVRTimerInfoTag.h" #include "ServiceBroker.h" -#include "addons/AddonVersion.h" #include "guilib/LocalizeStrings.h" #include "pvr/PVRDatabase.h" #include "pvr/PVRManager.h" @@ -90,8 +89,7 @@ CPVRTimerInfoTag::CPVRTimerInfoTag(bool bRadio /* = false */) CPVRTimerInfoTag::CPVRTimerInfoTag(const PVR_TIMER& timer, const std::shared_ptr<CPVRChannel>& channel, - unsigned int iClientId, - const ADDON::CAddonVersion& addonApiVersion) + unsigned int iClientId) : m_strTitle(timer.strTitle ? timer.strTitle : ""), m_strEpgSearchString(timer.strEpgSearchString ? timer.strEpgSearchString : ""), m_bFullTextEpgSearch(timer.bFullTextEpgSearch), @@ -135,20 +133,15 @@ CPVRTimerInfoTag::CPVRTimerInfoTag(const PVR_TIMER& timer, if (m_iClientIndex == PVR_TIMER_NO_CLIENT_INDEX) CLog::LogF(LOGERROR, "Invalid client index supplied by client {} (must be > 0)!", m_iClientId); - //! @todo version check can be removed with next incompatible API bump - static const ADDON::CAddonVersion customSettingsMinApiVersion{"9.1.0"}; - if (addonApiVersion >= customSettingsMinApiVersion) + for (unsigned int i = 0; i < timer.iCustomPropsSize; ++i) { - for (unsigned int i = 0; i < timer.iCustomPropsSize; ++i) - { - const PVR_SETTING_KEY_VALUE_PAIR& prop{timer.customProps[i]}; - if (prop.eType == PVR_SETTING_TYPE::INTEGER) - m_customProps.insert({prop.iKey, {prop.eType, CVariant{prop.iValue}}}); - else if (prop.eType == PVR_SETTING_TYPE::STRING) - m_customProps.insert({prop.iKey, {prop.eType, CVariant{prop.strValue}}}); - else - CLog::LogF(LOGERROR, "Unknown setting type for custom property"); - } + const PVR_SETTING_KEY_VALUE_PAIR& prop{timer.customProps[i]}; + if (prop.eType == PVR_SETTING_TYPE::INTEGER) + m_customProps.insert({prop.iKey, {prop.eType, CVariant{prop.iValue}}}); + else if (prop.eType == PVR_SETTING_TYPE::STRING) + m_customProps.insert({prop.iKey, {prop.eType, CVariant{prop.strValue}}}); + else + CLog::LogF(LOGERROR, "Unknown setting type for custom property"); } const std::shared_ptr<const CPVRClient> client = diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.h b/xbmc/pvr/timers/PVRTimerInfoTag.h index 4aa17696eb358..12beebb635816 100644 --- a/xbmc/pvr/timers/PVRTimerInfoTag.h +++ b/xbmc/pvr/timers/PVRTimerInfoTag.h @@ -21,11 +21,6 @@ struct PVR_TIMER; -namespace ADDON -{ -class CAddonVersion; -} - namespace PVR { class CPVRChannel; @@ -48,8 +43,7 @@ class CPVRTimerInfoTag final : public ISerializable explicit CPVRTimerInfoTag(bool bRadio = false); CPVRTimerInfoTag(const PVR_TIMER& timer, const std::shared_ptr<CPVRChannel>& channel, - unsigned int iClientId, - const ADDON::CAddonVersion& addonApiVersion); + unsigned int iClientId); bool operator==(const CPVRTimerInfoTag& right) const; bool operator!=(const CPVRTimerInfoTag& right) const; diff --git a/xbmc/pvr/timers/PVRTimerType.cpp b/xbmc/pvr/timers/PVRTimerType.cpp index 3df0f81808211..a803d92ad691f 100644 --- a/xbmc/pvr/timers/PVRTimerType.cpp +++ b/xbmc/pvr/timers/PVRTimerType.cpp @@ -9,7 +9,6 @@ #include "PVRTimerType.h" #include "ServiceBroker.h" -#include "addons/AddonVersion.h" #include "guilib/LocalizeStrings.h" #include "pvr/PVRManager.h" #include "pvr/addons/PVRClient.h" @@ -156,9 +155,7 @@ CPVRTimerType::CPVRTimerType() { } -CPVRTimerType::CPVRTimerType(const PVR_TIMER_TYPE& type, - int iClientId, - const ADDON::CAddonVersion& addonApiVersion) +CPVRTimerType::CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId) : m_iClientId(iClientId), m_iTypeId(type.iId), m_iAttributes(type.iAttributes), @@ -166,13 +163,7 @@ CPVRTimerType::CPVRTimerType(const PVR_TIMER_TYPE& type, { InitDescription(); InitAttributeValues(type); - - //! @todo version check can be removed with next incompatible API bump - static const ADDON::CAddonVersion customSettingsMinApiVersion{"9.1.0"}; - if (addonApiVersion >= customSettingsMinApiVersion) - { - InitCustomSettingDefinitions(type); - } + InitCustomSettingDefinitions(type); } CPVRTimerType::CPVRTimerType(unsigned int iTypeId, diff --git a/xbmc/pvr/timers/PVRTimerType.h b/xbmc/pvr/timers/PVRTimerType.h index 717576a774308..a00cefb82b102 100644 --- a/xbmc/pvr/timers/PVRTimerType.h +++ b/xbmc/pvr/timers/PVRTimerType.h @@ -19,11 +19,6 @@ struct PVR_TIMER_TYPE; -namespace ADDON -{ -class CAddonVersion; -} - namespace PVR { class CPVRClient; @@ -70,9 +65,7 @@ class CPVRTimerType int iClientId); CPVRTimerType(); - CPVRTimerType(const PVR_TIMER_TYPE& type, - int iClientId, - const ADDON::CAddonVersion& addonApiVersion); + CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId); CPVRTimerType(unsigned int iTypeId, uint64_t iAttributes, const std::string& strDescription = ""); virtual ~CPVRTimerType(); From 50b544d634b8f0ef1fe01064e7a9bb1a7dcfd500 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 20 Oct 2024 23:15:41 +0200 Subject: [PATCH 580/651] [PVR] Remove PVR Add-on API 9.0.1 compatibility code which is not needed anymore. --- xbmc/pvr/recordings/PVRRecording.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp index ff07d4e61129a..9994a0a621f71 100644 --- a/xbmc/pvr/recordings/PVRRecording.cpp +++ b/xbmc/pvr/recordings/PVRRecording.cpp @@ -122,11 +122,6 @@ CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClien m_strProviderName = recording.strProviderName; m_iClientProviderUid = recording.iClientProviderUid; - // Workaround for C++ PVR Add-on API wrapper not initializing this value correctly until API 9.0.1 - //! @todo Remove with next incompatible API bump. - if (m_iClientProviderUid == 0) - m_iClientProviderUid = PVR_PROVIDER_INVALID_UID; - SetGenre(recording.iGenreType, recording.iGenreSubType, recording.strGenreDescription ? recording.strGenreDescription : ""); CVideoInfoTag::SetPlayCount(recording.iPlayCount); From 7cab58fb6b56bffd88351403bb76618eea1f2b5e Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 15 Oct 2024 17:14:57 +0100 Subject: [PATCH 581/651] Annoucements: Add missing announces to ANNOUCE_ALL --- xbmc/interfaces/IAnnouncer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/interfaces/IAnnouncer.h b/xbmc/interfaces/IAnnouncer.h index 513fb25937e54..d1c2a7930e445 100644 --- a/xbmc/interfaces/IAnnouncer.h +++ b/xbmc/interfaces/IAnnouncer.h @@ -30,7 +30,7 @@ enum AnnouncementFlag }; const auto ANNOUNCE_ALL = (Player | Playlist | GUI | System | VideoLibrary | AudioLibrary | - Application | Input | ANNOUNCEMENT::PVR | Other); + Application | Input | ANNOUNCEMENT::PVR | Other | Info | Sources); /*! \brief Returns a string representation for the From c2d5fd51e64fb76273fc2b96bee0fe83330c4f00 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 15 Oct 2024 17:41:08 +0100 Subject: [PATCH 582/651] Announcements: Allow announcement receivers to register for specific types of events --- xbmc/interfaces/AnnouncementManager.cpp | 28 +++++++++++++------------ xbmc/interfaces/AnnouncementManager.h | 5 +++-- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/xbmc/interfaces/AnnouncementManager.cpp b/xbmc/interfaces/AnnouncementManager.cpp index 26836829f4307..5aacb974c8c89 100644 --- a/xbmc/interfaces/AnnouncementManager.cpp +++ b/xbmc/interfaces/AnnouncementManager.cpp @@ -54,12 +54,17 @@ void CAnnouncementManager::Deinitialize() } void CAnnouncementManager::AddAnnouncer(IAnnouncer *listener) +{ + return AddAnnouncer(listener, ANNOUNCE_ALL); +} + +void CAnnouncementManager::AddAnnouncer(IAnnouncer* listener, int flagMask) { if (!listener) return; std::unique_lock<CCriticalSection> lock(m_announcersCritSection); - m_announcers.push_back(listener); + m_announcers.emplace(listener, flagMask); } void CAnnouncementManager::RemoveAnnouncer(IAnnouncer *listener) @@ -68,14 +73,7 @@ void CAnnouncementManager::RemoveAnnouncer(IAnnouncer *listener) return; std::unique_lock<CCriticalSection> lock(m_announcersCritSection); - for (unsigned int i = 0; i < m_announcers.size(); i++) - { - if (m_announcers[i] == listener) - { - m_announcers.erase(m_announcers.begin() + i); - return; - } - } + m_announcers.erase(listener); } void CAnnouncementManager::Announce(AnnouncementFlag flag, const std::string& message) @@ -156,10 +154,14 @@ void CAnnouncementManager::DoAnnounce(AnnouncementFlag flag, std::unique_lock<CCriticalSection> lock(m_announcersCritSection); // Make a copy of announcers. They may be removed or even remove themselves during execution of IAnnouncer::Announce()! - - std::vector<IAnnouncer *> announcers(m_announcers); - for (unsigned int i = 0; i < announcers.size(); i++) - announcers[i]->Announce(flag, sender, message, data); + std::unordered_map<IAnnouncer*, int> announcers{m_announcers}; + for (const auto& [announcer, flagMask] : announcers) + { + if (flag & flagMask) + { + announcer->Announce(flag, sender, message, data); + } + } } void CAnnouncementManager::DoAnnounce(AnnouncementFlag flag, diff --git a/xbmc/interfaces/AnnouncementManager.h b/xbmc/interfaces/AnnouncementManager.h index 44b6d97071a3d..162a80d3bc66e 100644 --- a/xbmc/interfaces/AnnouncementManager.h +++ b/xbmc/interfaces/AnnouncementManager.h @@ -16,7 +16,7 @@ #include <list> #include <memory> -#include <vector> +#include <unordered_map> class CFileItem; class CVariant; @@ -33,6 +33,7 @@ namespace ANNOUNCEMENT void Deinitialize(); void AddAnnouncer(IAnnouncer *listener); + void AddAnnouncer(IAnnouncer* listener, int flagMask); void RemoveAnnouncer(IAnnouncer *listener); void Announce(AnnouncementFlag flag, const std::string& message); @@ -90,6 +91,6 @@ namespace ANNOUNCEMENT CCriticalSection m_announcersCritSection; CCriticalSection m_queueCritSection; - std::vector<IAnnouncer *> m_announcers; + std::unordered_map<IAnnouncer*, int> m_announcers; }; } From c270d15eb0854c1ca4f3652ca91ee7c9876c030f Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 15 Oct 2024 18:03:57 +0100 Subject: [PATCH 583/651] GUISourcesAnnouncementHandler: limit announces to ANNOUNCEMENT::Sources --- .../handlers/sources/GUISourcesAnnouncementHandler.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/xbmc/guilib/handlers/sources/GUISourcesAnnouncementHandler.cpp b/xbmc/guilib/handlers/sources/GUISourcesAnnouncementHandler.cpp index 251f0e14ec299..a3034e5e5ce71 100644 --- a/xbmc/guilib/handlers/sources/GUISourcesAnnouncementHandler.cpp +++ b/xbmc/guilib/handlers/sources/GUISourcesAnnouncementHandler.cpp @@ -16,7 +16,7 @@ CGUISourcesAnnouncementHandler::CGUISourcesAnnouncementHandler() { - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, ANNOUNCEMENT::Sources); } CGUISourcesAnnouncementHandler::~CGUISourcesAnnouncementHandler() @@ -29,10 +29,6 @@ void CGUISourcesAnnouncementHandler::Announce(ANNOUNCEMENT::AnnouncementFlag fla const std::string& message, const CVariant& data) { - // We are only interested in sources changes - if ((flag & ANNOUNCEMENT::Sources) == 0) - return; - if (message == "OnAdded" || message == "OnRemoved" || message == "OnUpdated") { CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH); From 78dc6ccc4effa82dcfbedd21564eed3534ec3be8 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 15 Oct 2024 18:06:47 +0100 Subject: [PATCH 584/651] DialogGameVolume: limit announces to applicationt type --- xbmc/games/dialogs/osd/DialogGameVolume.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/games/dialogs/osd/DialogGameVolume.cpp b/xbmc/games/dialogs/osd/DialogGameVolume.cpp index 84672695027dd..52cf638c42d2f 100644 --- a/xbmc/games/dialogs/osd/DialogGameVolume.cpp +++ b/xbmc/games/dialogs/osd/DialogGameVolume.cpp @@ -75,7 +75,7 @@ void CDialogGameVolume::OnInitWindow() if (dialogVolumeBar != nullptr) dialogVolumeBar->RegisterCallback(this); - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, ANNOUNCEMENT::Application); } void CDialogGameVolume::OnDeinitWindow(int nextWindowID) @@ -113,7 +113,7 @@ void CDialogGameVolume::Announce(ANNOUNCEMENT::AnnouncementFlag flag, const std::string& message, const CVariant& data) { - if (flag == ANNOUNCEMENT::Application && message == "OnVolumeChanged") + if (message == "OnVolumeChanged") { const float volumePercent = static_cast<float>(data["volume"].asDouble()); From e2e2aa5c384fe0efabeadfd9155d0fb271500d9c Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 15 Oct 2024 18:07:45 +0100 Subject: [PATCH 585/651] DirectoryProvider: limit announcement types --- xbmc/guilib/listproviders/DirectoryProvider.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/xbmc/guilib/listproviders/DirectoryProvider.cpp b/xbmc/guilib/listproviders/DirectoryProvider.cpp index e0cd42d039813..fceeec2732d6f 100644 --- a/xbmc/guilib/listproviders/DirectoryProvider.cpp +++ b/xbmc/guilib/listproviders/DirectoryProvider.cpp @@ -293,10 +293,6 @@ void CDirectoryProvider::Announce(ANNOUNCEMENT::AnnouncementFlag flag, const std::string& message, const CVariant& data) { - // we are only interested in library, player and GUI changes - if ((flag & (ANNOUNCEMENT::VideoLibrary | ANNOUNCEMENT::AudioLibrary | ANNOUNCEMENT::Player | ANNOUNCEMENT::GUI)) == 0) - return; - { std::unique_lock<CCriticalSection> lock(m_section); // we don't need to refresh anything if there are no fitting @@ -675,7 +671,9 @@ bool CDirectoryProvider::UpdateURL() if (!m_isSubscribed) { m_isSubscribed = true; - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer( + this, ANNOUNCEMENT::VideoLibrary | ANNOUNCEMENT::AudioLibrary | ANNOUNCEMENT::Player | + ANNOUNCEMENT::GUI); CServiceBroker::GetAddonMgr().Events().Subscribe(this, &CDirectoryProvider::OnAddonEvent); CServiceBroker::GetRepositoryUpdater().Events().Subscribe(this, &CDirectoryProvider::OnAddonRepositoryEvent); CServiceBroker::GetPVRManager().Events().Subscribe(this, &CDirectoryProvider::OnPVRManagerEvent); From 0b9689c14174612c36e5bd3bd79aec93e288d13a Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 15 Oct 2024 18:10:23 +0100 Subject: [PATCH 586/651] AirPlayServer: limit announcements to player --- xbmc/network/AirPlayServer.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/xbmc/network/AirPlayServer.cpp b/xbmc/network/AirPlayServer.cpp index 3924755197354..a722d579fc7ef 100644 --- a/xbmc/network/AirPlayServer.cpp +++ b/xbmc/network/AirPlayServer.cpp @@ -167,10 +167,6 @@ void CAirPlayServer::Announce(ANNOUNCEMENT::AnnouncementFlag flag, const std::string& message, const CVariant& data) { - // We are only interested in player changes - if ((flag & ANNOUNCEMENT::Player) == 0) - return; - std::unique_lock<CCriticalSection> lock(ServerInstanceLock); if (sender == ANNOUNCEMENT::CAnnouncementManager::ANNOUNCEMENT_SENDER && ServerInstance) @@ -323,7 +319,7 @@ CAirPlayServer::CAirPlayServer(int port, bool nonlocal) : CThread("AirPlayServer m_nonlocal = nonlocal; m_usePassword = false; m_origVolume = -1; - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, ANNOUNCEMENT::Player); } CAirPlayServer::~CAirPlayServer() From 43e986a46df91ca79da9a53333427e59a5f44021 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 15 Oct 2024 18:13:43 +0100 Subject: [PATCH 587/651] AirTunesServer: limit annoucement types --- xbmc/network/AirTunesServer.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/xbmc/network/AirTunesServer.cpp b/xbmc/network/AirTunesServer.cpp index 0add358ba30b6..c5b01303ac026 100644 --- a/xbmc/network/AirTunesServer.cpp +++ b/xbmc/network/AirTunesServer.cpp @@ -173,8 +173,7 @@ void CAirTunesServer::Announce(ANNOUNCEMENT::AnnouncementFlag flag, const std::string& message, const CVariant& data) { - if ((flag & ANNOUNCEMENT::Player) && - sender == ANNOUNCEMENT::CAnnouncementManager::ANNOUNCEMENT_SENDER) + if (sender == ANNOUNCEMENT::CAnnouncementManager::ANNOUNCEMENT_SENDER) { if ((message == "OnPlay" || message == "OnResume") && m_streamStarted) { @@ -685,7 +684,7 @@ void CAirTunesServer::RegisterActionListener(bool doRegister) if (doRegister) { - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, ANNOUNCEMENT::Player); appListener->RegisterActionListener(this); ServerInstance->Create(); } From 94512d89a5f7984e1f9a32b8c85bbfec4eb34276 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 15 Oct 2024 18:15:01 +0100 Subject: [PATCH 588/651] CUPnPRenderer: limit announcements --- xbmc/network/upnp/UPnPRenderer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xbmc/network/upnp/UPnPRenderer.cpp b/xbmc/network/upnp/UPnPRenderer.cpp index 196824fbeeaf8..974cd21505ae6 100644 --- a/xbmc/network/upnp/UPnPRenderer.cpp +++ b/xbmc/network/upnp/UPnPRenderer.cpp @@ -54,7 +54,8 @@ CUPnPRenderer::CUPnPRenderer(const char* friendly_name, unsigned int port /*= 0*/) : PLT_MediaRenderer(friendly_name, show_ip, uuid, port) { - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, ANNOUNCEMENT::Player | + ANNOUNCEMENT::Application); } /*---------------------------------------------------------------------- From a7891671da78abe2c475586d38dbfd178a2bd651 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Fri, 25 Oct 2024 08:59:41 +0100 Subject: [PATCH 589/651] [UPnPServer] Restrict announcements to videolibrary and audiolibrary --- xbmc/network/upnp/UPnPServer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xbmc/network/upnp/UPnPServer.cpp b/xbmc/network/upnp/UPnPServer.cpp index 340e308847a16..0f9cfd0cc7663 100644 --- a/xbmc/network/upnp/UPnPServer.cpp +++ b/xbmc/network/upnp/UPnPServer.cpp @@ -130,7 +130,8 @@ NPT_Result CUPnPServer::SetupServices() OnScanCompleted(VideoLibrary); // now safe to start passing on new notifications - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, ANNOUNCEMENT::VideoLibrary | + ANNOUNCEMENT::AudioLibrary); return result; } From 464529b269eaad04eb152fcab632f009ad2def17 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Fri, 25 Oct 2024 09:01:24 +0100 Subject: [PATCH 590/651] [Peripherals] Restrict annoucements to Player --- xbmc/peripherals/Peripherals.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/xbmc/peripherals/Peripherals.cpp b/xbmc/peripherals/Peripherals.cpp index 1276cf52d6763..9531b318cb0fd 100644 --- a/xbmc/peripherals/Peripherals.cpp +++ b/xbmc/peripherals/Peripherals.cpp @@ -136,7 +136,7 @@ void CPeripherals::Initialise() m_eventScanner->Start(); CServiceBroker::GetAppMessenger()->RegisterReceiver(this); - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, ANNOUNCEMENT::Player); } void CPeripherals::Clear() @@ -1019,8 +1019,7 @@ void CPeripherals::Announce(ANNOUNCEMENT::AnnouncementFlag flag, const std::string& message, const CVariant& data) { - if (flag == ANNOUNCEMENT::Player && - sender == ANNOUNCEMENT::CAnnouncementManager::ANNOUNCEMENT_SENDER) + if (sender == ANNOUNCEMENT::CAnnouncementManager::ANNOUNCEMENT_SENDER) { if (message == "OnQuit") { From 8409e1e1bd7b6264e53d454750867758c0d6c096 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Fri, 25 Oct 2024 09:03:38 +0100 Subject: [PATCH 591/651] [CEC] Restrict announcement registration --- xbmc/peripherals/devices/PeripheralCecAdapter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xbmc/peripherals/devices/PeripheralCecAdapter.cpp b/xbmc/peripherals/devices/PeripheralCecAdapter.cpp index 62383aa864e25..818f5c12b1bf4 100644 --- a/xbmc/peripherals/devices/PeripheralCecAdapter.cpp +++ b/xbmc/peripherals/devices/PeripheralCecAdapter.cpp @@ -370,7 +370,8 @@ void CPeripheralCecAdapter::Process(void) m_bActiveSourceBeforeStandby = false; } - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer( + this, ANNOUNCEMENT::System | ANNOUNCEMENT::GUI | ANNOUNCEMENT::Player); m_queryThread = new CPeripheralCecAdapterUpdateThread(this, &m_configuration); m_queryThread->Create(false); From cf02b3b07adb15981cf148f9a6ebbca4199e796c Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Fri, 25 Oct 2024 09:04:54 +0100 Subject: [PATCH 592/651] [GUIWindowSlideShow] Restrict announcements to Player --- xbmc/pictures/GUIWindowSlideShow.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/xbmc/pictures/GUIWindowSlideShow.cpp b/xbmc/pictures/GUIWindowSlideShow.cpp index fbe77a374fdc9..066b79da46bf6 100644 --- a/xbmc/pictures/GUIWindowSlideShow.cpp +++ b/xbmc/pictures/GUIWindowSlideShow.cpp @@ -152,7 +152,7 @@ CGUIWindowSlideShow::CGUIWindowSlideShow(void) m_loadType = KEEP_IN_MEMORY; m_bLoadNextPic = false; CServiceBroker::GetSlideShowDelegator().SetDelegate(this); - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, ANNOUNCEMENT::Player); Reset(); } @@ -167,14 +167,11 @@ void CGUIWindowSlideShow::Announce(ANNOUNCEMENT::AnnouncementFlag flag, const std::string& message, const CVariant& data) { - if (flag & ANNOUNCEMENT::Player) + if (message == "OnPlay" || message == "OnResume") { - if (message == "OnPlay" || message == "OnResume") - { - if (data.isMember("player") && data["player"].isMember("playerid") && - data["player"]["playerid"] == static_cast<int>(PLAYLIST::Id::TYPE_VIDEO)) - Close(); - } + if (data.isMember("player") && data["player"].isMember("playerid") && + data["player"]["playerid"] == static_cast<int>(PLAYLIST::Id::TYPE_VIDEO)) + Close(); } } From 80942654608721196049dbc604ac024b92b16fe5 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Fri, 25 Oct 2024 09:07:27 +0100 Subject: [PATCH 593/651] [GUIWindowHome] Restrict announcement processing to library --- xbmc/windows/GUIWindowHome.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/xbmc/windows/GUIWindowHome.cpp b/xbmc/windows/GUIWindowHome.cpp index c94c9e6be20e5..6505db0356c16 100644 --- a/xbmc/windows/GUIWindowHome.cpp +++ b/xbmc/windows/GUIWindowHome.cpp @@ -32,7 +32,8 @@ CGUIWindowHome::CGUIWindowHome(void) : CGUIWindow(WINDOW_HOME, "Home.xml") m_updateRA = (Audio | Video | Totals); m_loadType = KEEP_IN_MEMORY; - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, ANNOUNCEMENT::VideoLibrary | + ANNOUNCEMENT::AudioLibrary); } CGUIWindowHome::~CGUIWindowHome(void) @@ -81,10 +82,6 @@ void CGUIWindowHome::Announce(ANNOUNCEMENT::AnnouncementFlag flag, CLog::Log(LOGDEBUG, LOGANNOUNCE, "GOT ANNOUNCEMENT, type: {}, from {}, message {}", AnnouncementFlagToString(flag), sender, message); - // we are only interested in library changes - if ((flag & (ANNOUNCEMENT::VideoLibrary | ANNOUNCEMENT::AudioLibrary)) == 0) - return; - if (data.isMember("transaction") && data["transaction"].asBoolean()) return; From b225f8e9dea6d815be838a1ffaf6c3d23166aa4b Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Fri, 25 Oct 2024 09:08:35 +0100 Subject: [PATCH 594/651] [Windows10Events] Restrict announcement reception to Player --- xbmc/windowing/win10/WinEventsWin10.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/windowing/win10/WinEventsWin10.cpp b/xbmc/windowing/win10/WinEventsWin10.cpp index b534ff56609f8..3abd9d9dfc59b 100644 --- a/xbmc/windowing/win10/WinEventsWin10.cpp +++ b/xbmc/windowing/win10/WinEventsWin10.cpp @@ -157,7 +157,7 @@ void CWinEventsWin10::InitEventHandlers(const CoreWindow& window) m_smtc.ButtonPressed(CWinEventsWin10::OnSystemMediaButtonPressed); } m_smtc.IsEnabled(true);; - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, ANNOUNCEMENT::Player); } if (CSysInfo::GetWindowsDeviceFamily() == CSysInfo::WindowsDeviceFamily::Xbox) { From ac809bcdeeb6632bdd077713285fca97eeb32c40 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Fri, 25 Oct 2024 09:09:41 +0100 Subject: [PATCH 595/651] [PVR] Restrict announcement reception to GUI/Power --- xbmc/pvr/PVRManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/pvr/PVRManager.cpp b/xbmc/pvr/PVRManager.cpp index 24ee777ad1c73..f0d25dc654477 100644 --- a/xbmc/pvr/PVRManager.cpp +++ b/xbmc/pvr/PVRManager.cpp @@ -210,7 +210,7 @@ CPVRManager::CPVRManager() CSettings::SETTING_PVRPOWERMANAGEMENT_SETWAKEUPCMD, CSettings::SETTING_PVRPARENTAL_ENABLED, CSettings::SETTING_PVRPARENTAL_DURATION}) { - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, ANNOUNCEMENT::GUI); m_actionListener.Init(*this); CLog::LogFC(LOGDEBUG, LOGPVR, "PVR Manager instance created"); From 696adc9c8ca4e387b562b767a8abe4427ec6114a Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Fri, 25 Oct 2024 09:10:44 +0100 Subject: [PATCH 596/651] [Input] Restrict event reception to System --- xbmc/platform/linux/input/LibInputHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/platform/linux/input/LibInputHandler.cpp b/xbmc/platform/linux/input/LibInputHandler.cpp index bce9f78dc1bec..9d548face099d 100644 --- a/xbmc/platform/linux/input/LibInputHandler.cpp +++ b/xbmc/platform/linux/input/LibInputHandler.cpp @@ -99,7 +99,7 @@ CLibInputHandler::CLibInputHandler() : CThread("libinput") m_touch = std::make_unique<CLibInputTouch>(); m_settings = std::make_unique<CLibInputSettings>(this); - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, ANNOUNCEMENT::System); } CLibInputHandler::~CLibInputHandler() From aeb316c28d901bc6055277ee179c7f4db044f8ee Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Fri, 25 Oct 2024 09:11:45 +0100 Subject: [PATCH 597/651] [macOS] Restrict hotkey controller to either Player or Power announcements --- xbmc/platform/darwin/osx/HotKeyController.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xbmc/platform/darwin/osx/HotKeyController.mm b/xbmc/platform/darwin/osx/HotKeyController.mm index bc5e0a21f9111..d122fe27eda12 100644 --- a/xbmc/platform/darwin/osx/HotKeyController.mm +++ b/xbmc/platform/darwin/osx/HotKeyController.mm @@ -16,7 +16,8 @@ CHotKeyController::CHotKeyController() { m_mediaKeytap = [CMediaKeyTap new]; - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, + ANNOUNCEMENT::GUI | ANNOUNCEMENT::Player); } CHotKeyController::~CHotKeyController() From 8febdc2b098989cea58929777c6c20af1d4bc293 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Fri, 25 Oct 2024 09:15:37 +0100 Subject: [PATCH 598/651] [Android] Restrict announcement sending --- xbmc/platform/android/activity/XBMCApp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xbmc/platform/android/activity/XBMCApp.cpp b/xbmc/platform/android/activity/XBMCApp.cpp index c67ed9e8cc3f2..10075dc331dd7 100644 --- a/xbmc/platform/android/activity/XBMCApp.cpp +++ b/xbmc/platform/android/activity/XBMCApp.cpp @@ -474,7 +474,8 @@ void CXBMCApp::UnregisterDisplayListener() void CXBMCApp::Initialize() { - CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CServiceBroker::GetAnnouncementManager()->AddAnnouncer( + this, ANNOUNCEMENT::Input | ANNOUNCEMENT::Player | ANNOUNCEMENT::Info); } void CXBMCApp::Deinitialize() From c28ae7852ef1fcd295c75b53f2ade0367f187700 Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Fri, 25 Oct 2024 17:29:37 +0200 Subject: [PATCH 599/651] [subtitles][libass] Fallback value for no PlayResY --- .../DVDSubtitles/DVDSubtitlesLibass.cpp | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp index 4fcf2dc2363af..e7a50e2d2d748 100644 --- a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp +++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp @@ -365,12 +365,31 @@ void CDVDSubtitlesLibass::ApplyStyle(const std::shared_ptr<struct style>& subSty free(style->Name); style->Name = strdup("KodiDefault"); + // PlayResY and PlayResX are mandatory but some out-of-spec files do not specify them + // if both PlayRes are not specified libass fallback to 288x384 + double playResY = static_cast<double>(m_track->PlayResY); + if (m_track->PlayResY == 0 && m_track->PlayResX == 0) + { + CLog::LogF(LOGWARNING, "PlayResX and PlayResY are not defined in subtitle file. This may " + "cause unexpected rendering issues."); + playResY = 288.0; + } + else if (m_track->PlayResY == 0 && m_track->PlayResX > 0) + { + // This use case depend strictly on library implementation anyway + // the common behavior of the library is to calculate with an aspect ratio of 4/3 + CLog::LogF( + LOGWARNING, + "PlayResY is not defined in subtitle file. This may cause unexpected rendering issues."); + playResY = std::max(1.0, static_cast<double>(m_track->PlayResX) * 3 / 4); + } + // Calculate the scale // Font size, borders, etc... are specified in pixel unit in scaled // for a window height of 720, so we need to rescale to our PlayResY - double playResY{static_cast<double>(m_track->PlayResY)}; double scaleDefault{playResY / 720}; double scale{scaleDefault}; + if (m_subtitleType == NATIVE && (subStyle->assOverrideStyles == OverrideStyles::STYLES || subStyle->assOverrideStyles == OverrideStyles::STYLES_POSITIONS || From edb9c9052ab2996227dedb36183d99a882b0b3f7 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Mon, 14 Oct 2024 17:02:59 +0100 Subject: [PATCH 600/651] VideoPlayer: move part of the GUI messages to a GUI handler via annoucements --- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 60 ++++++------- xbmc/guilib/handlers/CMakeLists.txt | 6 +- .../GUIAnnouncementHandlerContainer.cpp | 2 + .../player/GUIPlayerAnnouncementHandler.cpp | 86 +++++++++++++++++++ .../player/GUIPlayerAnnouncementHandler.h | 26 ++++++ 5 files changed, 142 insertions(+), 38 deletions(-) create mode 100644 xbmc/guilib/handlers/player/GUIPlayerAnnouncementHandler.cpp create mode 100644 xbmc/guilib/handlers/player/GUIPlayerAnnouncementHandler.h diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index fe99187dad65b..a441b556ea6ed 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -25,7 +25,6 @@ #include "DVDInputStreams/InputStreamPVRBase.h" #include "DVDMessage.h" #include "FileItem.h" -#include "GUIUserMessages.h" #include "LangInfo.h" #include "ServiceBroker.h" #include "URL.h" @@ -39,13 +38,12 @@ #include "cores/FFmpeg.h" #include "cores/VideoPlayer/Process/ProcessInfo.h" #include "cores/VideoPlayer/VideoRenderers/RenderManager.h" -#include "dialogs/GUIDialogKaiToast.h" #include "guilib/GUIComponent.h" -#include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" #include "guilib/StereoscopicsManager.h" #include "input/actions/Action.h" #include "input/actions/ActionIDs.h" +#include "interfaces/AnnouncementManager.h" #include "messaging/ApplicationMessenger.h" #include "network/NetworkFileItemClassify.h" #include "settings/AdvancedSettings.h" @@ -63,6 +61,7 @@ #include "utils/log.h" #include "video/Bookmark.h" #include "video/VideoInfoTag.h" +#include "windowing/GraphicContext.h" #include "windowing/WinSystem.h" #include <chrono> @@ -1324,14 +1323,10 @@ void CVideoPlayer::Prepare() __FUNCTION__, starttime.count()); } - const std::shared_ptr<CAdvancedSettings> advancedSettings = - CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); - if (advancedSettings && advancedSettings->m_EdlDisplayCommbreakNotifications) - { - const std::string timeString = - StringUtils::SecondsToTimeString(edit->end.count(), TIME_FORMAT_MM_SS); - CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25011), timeString); - } + CVariant announcement( + StringUtils::SecondsToTimeString(edit->end.count(), TIME_FORMAT_MM_SS)); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnCommercial", + announcement); } } } @@ -1891,7 +1886,7 @@ void CVideoPlayer::HandlePlaySpeed() { if (cache.level < 0.0) { - CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(21454), g_localizeStrings.Get(21455)); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "SourceSlow"); SetCaching(CACHESTATE_INIT); } // Note: Previously used cache.level >= 1 would keep video stalled @@ -2464,15 +2459,11 @@ void CVideoPlayer::CheckAutoSceneSkip() // marker for commbreak may be inaccurate. allow user to skip into break from the back if (m_playSpeed >= 0 && m_Edl.GetLastEditTime() != edit->start && clock < edit->end - 1s) { - const std::shared_ptr<CAdvancedSettings> advancedSettings = - CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); - if (advancedSettings && advancedSettings->m_EdlDisplayCommbreakNotifications) - { - const std::string timeString = StringUtils::SecondsToTimeString( - std::chrono::duration_cast<std::chrono::seconds>(edit->end - edit->start).count(), - TIME_FORMAT_MM_SS); - CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25011), timeString); - } + CVariant announcement{StringUtils::SecondsToTimeString( + std::chrono::duration_cast<std::chrono::seconds>(edit->end - edit->start).count(), + TIME_FORMAT_MM_SS)}; + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnCommercial", + announcement); m_Edl.SetLastEditTime(edit->start); m_Edl.SetLastEditActionType(edit->action); @@ -4133,14 +4124,15 @@ int CVideoPlayer::OnDiscNavResult(void* pData, int iMessage) { m_dvd.state = DVDSTATE_NORMAL; CLog::Log(LOGDEBUG, "CVideoPlayer::OnDiscNavResult - libbluray menu not supported (DVDSTATE_NORMAL)"); - CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25008), g_localizeStrings.Get(25009)); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnBlurayMenuError"); } break; case BD_EVENT_ENC_ERROR: { m_dvd.state = DVDSTATE_NORMAL; CLog::Log(LOGDEBUG, "CVideoPlayer::OnDiscNavResult - libbluray the disc/file is encrypted and can't be played (DVDSTATE_NORMAL)"); - CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(16026), g_localizeStrings.Get(29805)); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, + "OnBlurayEncryptedError"); } break; default: @@ -4304,8 +4296,8 @@ int CVideoPlayer::OnDiscNavResult(void* pData, int iMessage) { CLog::Log(LOGDEBUG, "DVDNAV_ERROR"); m_dvd.state = DVDSTATE_NORMAL; - CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(16026), - g_localizeStrings.Get(16029)); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, + "OnPlaybackFailed"); } break; default: @@ -4395,9 +4387,8 @@ bool CVideoPlayer::OnAction(const CAction &action) m_callback.OnPlayBackResumed(); } - // send a message to everyone that we've gone to the menu - CGUIMessage msg(GUI_MSG_VIDEO_MENU_STARTED, 0, 0); - CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + // Let everyone know that we've gone to the menu + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnMenu"); } return true; } @@ -4559,9 +4550,10 @@ bool CVideoPlayer::OnAction(const CAction &action) break; case ACTION_TOGGLE_COMMSKIP: m_SkipCommercials = !m_SkipCommercials; - CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25011), - g_localizeStrings.Get(m_SkipCommercials ? 25013 : 25012)); + CServiceBroker::GetAnnouncementManager()->Announce( + ANNOUNCEMENT::Player, "OnToggleSkipCommercials", CVariant{m_SkipCommercials}); break; + case ACTION_PLAYER_DEBUG: m_renderManager.ToggleDebug(); break; @@ -4570,12 +4562,8 @@ bool CVideoPlayer::OnAction(const CAction &action) break; case ACTION_PLAYER_PROCESS_INFO: - if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_DIALOG_PLAYER_PROCESS_INFO) - { - CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_DIALOG_PLAYER_PROCESS_INFO); - return true; - } - break; + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnProcessInfo"); + return true; } // return false to inform the caller we didn't handle the message diff --git a/xbmc/guilib/handlers/CMakeLists.txt b/xbmc/guilib/handlers/CMakeLists.txt index fc052e35dfc97..e7ac9e0eb925d 100644 --- a/xbmc/guilib/handlers/CMakeLists.txt +++ b/xbmc/guilib/handlers/CMakeLists.txt @@ -1,7 +1,9 @@ set(SOURCES GUIAnnouncementHandlerContainer.cpp - sources/GUISourcesAnnouncementHandler.cpp) + sources/GUISourcesAnnouncementHandler.cpp + player/GUIPlayerAnnouncementHandler.cpp) set(HEADERS GUIAnnouncementHandlerContainer.h - sources/GUISourcesAnnouncementHandler.h) + sources/GUISourcesAnnouncementHandler.h + player/GUIPlayerAnnouncementHandler.h) core_add_library(guilib_announcement_handlers) diff --git a/xbmc/guilib/handlers/GUIAnnouncementHandlerContainer.cpp b/xbmc/guilib/handlers/GUIAnnouncementHandlerContainer.cpp index f7a5fbae4af9a..ebe3910461630 100644 --- a/xbmc/guilib/handlers/GUIAnnouncementHandlerContainer.cpp +++ b/xbmc/guilib/handlers/GUIAnnouncementHandlerContainer.cpp @@ -8,9 +8,11 @@ #include "GUIAnnouncementHandlerContainer.h" +#include "player/GUIPlayerAnnouncementHandler.h" #include "sources/GUISourcesAnnouncementHandler.h" CGUIAnnouncementHandlerContainer::CGUIAnnouncementHandlerContainer() { m_announcementHandlers.emplace_back(std::make_unique<CGUISourcesAnnouncementHandler>()); + m_announcementHandlers.emplace_back(std::make_unique<CGUIPlayerAnnouncementHandler>()); } diff --git a/xbmc/guilib/handlers/player/GUIPlayerAnnouncementHandler.cpp b/xbmc/guilib/handlers/player/GUIPlayerAnnouncementHandler.cpp new file mode 100644 index 0000000000000..6c77523b25fc0 --- /dev/null +++ b/xbmc/guilib/handlers/player/GUIPlayerAnnouncementHandler.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 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 "GUIPlayerAnnouncementHandler.h" + +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "interfaces/AnnouncementManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" + +CGUIPlayerAnnouncementHandler::CGUIPlayerAnnouncementHandler() +{ + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this, ANNOUNCEMENT::Player); +} + +CGUIPlayerAnnouncementHandler::~CGUIPlayerAnnouncementHandler() +{ + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); +} + +void CGUIPlayerAnnouncementHandler::Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) +{ + if (message == "OnCommercial") + { + const std::shared_ptr<CAdvancedSettings> advancedSettings = + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); + if (advancedSettings && advancedSettings->m_EdlDisplayCommbreakNotifications) + { + CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25011), data.asString()); + } + } + else if (message == "SourceSlow") + { + CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(21454), + g_localizeStrings.Get(21455)); + } + else if (message == "OnToggleSkipCommercials") + { + CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25011), + g_localizeStrings.Get(data.asBoolean() ? 25013 : 25012)); + } + else if (message == "OnProcessInfo") + { + if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != + WINDOW_DIALOG_PLAYER_PROCESS_INFO) + { + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow( + WINDOW_DIALOG_PLAYER_PROCESS_INFO); + } + } + else if (message == "OnPlaybackFailed") + { + CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(16026), + g_localizeStrings.Get(16029)); + } +#if defined(HAVE_LIBBLURAY) + else if (message == "OnBlurayMenuError") + { + CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(25008), + g_localizeStrings.Get(25009)); + } + else if (message == "OnBlurayEncryptedError") + { + CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(16026), + g_localizeStrings.Get(29805)); + } +#endif + else if (message == "OnMenu") + { + CGUIMessage msg(GUI_MSG_VIDEO_MENU_STARTED, 0, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + } +} diff --git a/xbmc/guilib/handlers/player/GUIPlayerAnnouncementHandler.h b/xbmc/guilib/handlers/player/GUIPlayerAnnouncementHandler.h new file mode 100644 index 0000000000000..26cf75374e0ec --- /dev/null +++ b/xbmc/guilib/handlers/player/GUIPlayerAnnouncementHandler.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 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 "interfaces/IAnnouncer.h" + +/*! +\brief Handler for announcements of type player +*/ +class CGUIPlayerAnnouncementHandler : public ANNOUNCEMENT::IAnnouncer +{ +public: + CGUIPlayerAnnouncementHandler(); + ~CGUIPlayerAnnouncementHandler(); + + void Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) override; +}; From b1f92c2127fc55063eef4122352a18e45c54f860 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti <joseluis.marti@gmail.com> Date: Fri, 25 Oct 2024 22:17:48 +0200 Subject: [PATCH 601/651] Setting to ignore the capabilities reported by the codec component --- .../Video/DVDVideoCodecAndroidMediaCodec.cpp | 12 ++++++++++-- xbmc/settings/AdvancedSettings.cpp | 1 + xbmc/settings/AdvancedSettings.h | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp index 4844bf07343b5..c747645f5c195 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp @@ -23,6 +23,7 @@ #include "cores/VideoPlayer/VideoRenderers/RenderManager.h" #include "media/decoderfilter/DecoderFilterManager.h" #include "messaging/ApplicationMessenger.h" +#include "settings/AdvancedSettings.h" #include "settings/SettingUtils.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" @@ -828,9 +829,16 @@ bool CDVDVideoCodecAndroidMediaCodec::Open(CDVDStreamInfo &hints, CDVDCodecOptio return profileLevel.profile() == profile; }) == profileLevels.cend()) { - CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Open profile not supported: {}", + CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Open: Profile {} not supported", profile); - continue; + if (CServiceBroker::GetSettingsComponent() + ->GetAdvancedSettings() + ->m_videoBypassCodecProfile) + CLog::Log( + LOGINFO, + "CDVDVideoCodecAndroidMediaCodec::Open: Ignore profile not supported for the codec"); + else + continue; } } diff --git a/xbmc/settings/AdvancedSettings.cpp b/xbmc/settings/AdvancedSettings.cpp index c4e8d9fc58b8f..527c1158b7514 100644 --- a/xbmc/settings/AdvancedSettings.cpp +++ b/xbmc/settings/AdvancedSettings.cpp @@ -647,6 +647,7 @@ void CAdvancedSettings::ParseSettingsFile(const std::string &file) XMLUtils::GetInt(pElement, "useocclusionquery", m_videoCaptureUseOcclusionQuery, -1, 1); XMLUtils::GetBoolean(pElement,"vdpauInvTelecine",m_videoVDPAUtelecine); XMLUtils::GetBoolean(pElement,"vdpauHDdeintSkipChroma",m_videoVDPAUdeintSkipChromaHD); + XMLUtils::GetBoolean(pElement, "bypasscodecprofile", m_videoBypassCodecProfile); TiXmlElement* pAdjustRefreshrate = pElement->FirstChildElement("adjustrefreshrate"); if (pAdjustRefreshrate) diff --git a/xbmc/settings/AdvancedSettings.h b/xbmc/settings/AdvancedSettings.h index 051636cb05fd4..a31e88bd4900c 100644 --- a/xbmc/settings/AdvancedSettings.h +++ b/xbmc/settings/AdvancedSettings.h @@ -175,6 +175,7 @@ class CAdvancedSettings : public ISettingCallback, public ISettingsHandler std::string m_videoDefaultPlayer; float m_videoPlayCountMinimumPercent; + bool m_videoBypassCodecProfile = false; // Android only to bypass reported codec capabilities float m_slideshowBlackBarCompensation; float m_slideshowZoomAmount; From 7ebad49ad41ee7e298c1b39db52957811b96902a Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Sat, 26 Oct 2024 03:21:10 -0400 Subject: [PATCH 602/651] [videodb] Remove nested transaction in AddNewMovie Nested transactions are not supported by Sqlite or MySql/MariaDB. Fixes the performance of library scans with a sqlite db on hdd (all the following insert statements were running in individual automatic transactions, each with sync to disk). --- xbmc/video/VideoDatabase.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index a610dbefa6c75..2068480e24db1 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -1464,6 +1464,8 @@ int CVideoDatabase::GetMusicVideoId(const std::string& strFilenameAndPath) //******************************************************************************************************************************** int CVideoDatabase::AddNewMovie(CVideoInfoTag& details) { + assert(m_pDB->in_transaction()); + const auto filePath = details.GetPath(); try @@ -1480,8 +1482,6 @@ int CVideoDatabase::AddNewMovie(CVideoInfoTag& details) return -1; } - BeginTransaction(); - m_pDS->exec( PrepareSQL("INSERT INTO movie (idMovie, idFile) VALUES (NULL, %i)", details.m_iFileId)); details.m_iDbId = static_cast<int>(m_pDS->lastinsertid()); @@ -1491,14 +1491,11 @@ int CVideoDatabase::AddNewMovie(CVideoInfoTag& details) details.m_iFileId, details.m_iDbId, MediaTypeMovie, VideoAssetType::VERSION, VIDEO_VERSION_ID_DEFAULT)); - CommitTransaction(); - return details.m_iDbId; } catch (...) { CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath); - RollbackTransaction(); } return -1; } From 75aed983def45586a18923eaf0f353f2e6fb425c Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Sat, 26 Oct 2024 04:47:59 -0400 Subject: [PATCH 603/651] [texturedb] Add DB transaction when caching a texture Needed for data integrity and 2x speedup with sqlite due to less single statement implicit transactions. --- xbmc/TextureDatabase.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xbmc/TextureDatabase.cpp b/xbmc/TextureDatabase.cpp index aa851c13018e6..6647f99f2966f 100644 --- a/xbmc/TextureDatabase.cpp +++ b/xbmc/TextureDatabase.cpp @@ -362,6 +362,8 @@ bool CTextureDatabase::AddCachedTexture(const std::string &url, const CTextureDe if (!m_pDS) return false; + BeginTransaction(); + std::string sql = PrepareSQL("DELETE FROM texture WHERE url='%s'", url.c_str()); m_pDS->exec(sql); @@ -373,10 +375,13 @@ bool CTextureDatabase::AddCachedTexture(const std::string &url, const CTextureDe // set the size information sql = PrepareSQL("INSERT INTO sizes (idtexture, size, usecount, lastusetime, width, height) VALUES(%u, 1, 1, CURRENT_TIMESTAMP, %u, %u)", textureID, details.width, details.height); m_pDS->exec(sql); + + CommitTransaction(); } catch (...) { CLog::Log(LOGERROR, "{} failed on url '{}'", __FUNCTION__, url); + RollbackTransaction(); } return true; } From 68b6240bcc22d62c71c2b177216bfa7bdc5a3cd2 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti <joseluis.marti@gmail.com> Date: Sun, 27 Oct 2024 11:22:04 +0100 Subject: [PATCH 604/651] Use the @SuppressWarnings annotation to suppress the related warning when code is compiled --- tools/android/packaging/xbmc/src/Main.java.in | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/android/packaging/xbmc/src/Main.java.in b/tools/android/packaging/xbmc/src/Main.java.in index 698b56083a3bc..683796e1ac15f 100644 --- a/tools/android/packaging/xbmc/src/Main.java.in +++ b/tools/android/packaging/xbmc/src/Main.java.in @@ -309,6 +309,7 @@ public class Main extends NativeActivity implements Choreographer.FrameCallback } @Override + @SuppressWarnings("deprecation") public void onVisibleBehindCanceled() { _onVisibleBehindCanceled(); From 4e7e23cda02fd9ac778749a62e0bfdda5e0b7d23 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 22 Oct 2024 22:38:42 +0200 Subject: [PATCH 605/651] [Estuary] Channel Guide Breadcrumb: Add channel number and name, align formatting with the other breadcrumbs. --- addons/skin.estuary/xml/DialogPVRChannelGuide.xml | 2 +- addons/skin.estuary/xml/Variables.xml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/skin.estuary/xml/DialogPVRChannelGuide.xml b/addons/skin.estuary/xml/DialogPVRChannelGuide.xml index dc08d91d8196f..7a197742a1d9c 100644 --- a/addons/skin.estuary/xml/DialogPVRChannelGuide.xml +++ b/addons/skin.estuary/xml/DialogPVRChannelGuide.xml @@ -135,7 +135,7 @@ <height>70</height> <font>font45</font> <aligny>center</aligny> - <label>$LOCALIZE[19069]$INFO[Container(11).ListItem.StartDate, - ]</label> + <label>$VAR[BreadcrumbsPVRChannelGuideVar]$INFO[Container(11).ListItem.StartDate, / ]</label> <shadowcolor>black</shadowcolor> </control> </control> diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index cc0a2c5b0960b..d9afddd031f1d 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -543,6 +543,10 @@ <value condition="PVR.IsPlayingRadio">$LOCALIZE[19021] / $LOCALIZE[19019] / $INFO[VideoPlayer.ChannelGroup]</value> <value>$LOCALIZE[19019] / $INFO[VideoPlayer.ChannelGroup]</value> </variable> + <variable name="BreadcrumbsPVRChannelGuideVar"> + <value condition="PVR.IsPlayingTV">$LOCALIZE[19020] / $LOCALIZE[19069] / $INFO[VideoPlayer.ChannelName]</value> + <value>$LOCALIZE[19021] / $LOCALIZE[19069] / $INFO[VideoPlayer.ChannelName]</value> + </variable> <variable name="BreadcrumbsGameVar"> <value>$LOCALIZE[15016]</value> </variable> From a4235b85149e2377640f1609d8bc21d91b0c568f Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Mon, 28 Oct 2024 10:35:25 +0100 Subject: [PATCH 606/651] [FileSystem] Return false when buffer is EOF --- xbmc/filesystem/File.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xbmc/filesystem/File.cpp b/xbmc/filesystem/File.cpp index 9bdb0d43d87ba..7e36cc5eb1008 100644 --- a/xbmc/filesystem/File.cpp +++ b/xbmc/filesystem/File.cpp @@ -777,6 +777,9 @@ bool XFILE::CFile::ReadString(std::vector<char>& line) using traits = CFileStreamBuffer::traits_type; CFileStreamBuffer::int_type aByte = m_pBuffer->sgetc(); + if (aByte == traits::eof()) + return false; + while (aByte != traits::eof()) { aByte = m_pBuffer->sbumpc(); From 77678dc33e91ef0e09c2098182a2cf9cdd3ba364 Mon Sep 17 00:00:00 2001 From: CastagnaIT <gottardo.stefano.83@gmail.com> Date: Mon, 28 Oct 2024 18:10:18 +0100 Subject: [PATCH 607/651] [msvcrt] Fix buffer overflow due to fixed "var" size --- xbmc/cores/DllLoader/exports/emu_msvcrt.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/DllLoader/exports/emu_msvcrt.cpp b/xbmc/cores/DllLoader/exports/emu_msvcrt.cpp index b86fa654141db..0eb042d122400 100644 --- a/xbmc/cores/DllLoader/exports/emu_msvcrt.cpp +++ b/xbmc/cores/DllLoader/exports/emu_msvcrt.cpp @@ -1819,7 +1819,11 @@ extern "C" if (value_start != NULL) { - char var[64]; + const size_t varSize = value_start - envstring; + char* var = static_cast<char*>(std::malloc(varSize + 1)); + if (!var) + return -1; + int size = strlen(envstring) + 1; char *value = (char*)malloc(size); @@ -1828,7 +1832,7 @@ extern "C" value[0] = 0; memcpy(var, envstring, value_start - envstring); - var[value_start - envstring] = 0; + var[varSize] = 0; char* temp = var; while (*temp) { @@ -1881,6 +1885,7 @@ extern "C" } free(value); + std::free(var); } } From 85e1b603eec1a6f82a50fbe3495764a3557126bf Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Mon, 28 Oct 2024 11:23:53 -0400 Subject: [PATCH 608/651] [dbwrappers] Rework low-level database logging - more consistency between Sqlite and Mysql/MariaDB - categorize the frequent logging under LOGDATABASE component --- xbmc/dbwrappers/mysqldataset.cpp | 23 +++++++++++++++++------ xbmc/dbwrappers/sqlitedataset.cpp | 15 +++++++++++---- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/xbmc/dbwrappers/mysqldataset.cpp b/xbmc/dbwrappers/mysqldataset.cpp index 9d6f026c55a95..6607dc59592af 100644 --- a/xbmc/dbwrappers/mysqldataset.cpp +++ b/xbmc/dbwrappers/mysqldataset.cpp @@ -308,6 +308,8 @@ int MysqlDatabase::copy(const char* backup_name) if (!active || conn == NULL) throw DbErrors("Can't copy database: no active connection..."); + CLog::LogF(LOGDEBUG, "Copying from {} to {} at {}", db, backup_name, host); + char sql[4096]; int ret; @@ -385,6 +387,8 @@ int MysqlDatabase::drop_analytics(void) if ((ret = mysql_select_db(conn, db.c_str())) != MYSQL_OK) throw DbErrors("Can't connect to database: '%s'", db.c_str()); + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning indexes from database {} at {}", db, host); + // getting a list of indexes in the database snprintf(sql, sizeof(sql), "SELECT DISTINCT table_name, index_name " @@ -413,6 +417,8 @@ int MysqlDatabase::drop_analytics(void) mysql_free_result(res); } + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning views from database {} at {}", db, host); + // next topic is a views list snprintf(sql, sizeof(sql), "SELECT table_name FROM information_schema.views WHERE table_schema = '%s'", db.c_str()); @@ -437,6 +443,8 @@ int MysqlDatabase::drop_analytics(void) mysql_free_result(res); } + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning triggers from database {} at {}", db, host); + // triggers snprintf(sql, sizeof(sql), "SELECT trigger_name FROM information_schema.triggers WHERE event_object_schema = '%s'", @@ -461,6 +469,8 @@ int MysqlDatabase::drop_analytics(void) mysql_free_result(res); } + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning functions from database {} at {}", db, host); + // Native functions snprintf(sql, sizeof(sql), "SELECT routine_name FROM information_schema.routines " @@ -510,7 +520,8 @@ int MysqlDatabase::query_with_reconnect(const char* query) long MysqlDatabase::nextid(const char* sname) { - CLog::Log(LOGDEBUG, "MysqlDatabase::nextid for {}", sname); + CLog::LogFC(LOGDEBUG, LOGDATABASE, "nextid for {}", sname); + if (!active) return DB_UNEXPECTED_RESULT; const char* seq_table = "sys_seq"; @@ -518,7 +529,7 @@ long MysqlDatabase::nextid(const char* sname) MYSQL_RES* res; char sqlcmd[512]; snprintf(sqlcmd, sizeof(sqlcmd), "SELECT nextid FROM %s WHERE seq_name = '%s'", seq_table, sname); - CLog::Log(LOGDEBUG, "MysqlDatabase::nextid will request"); + CLog::LogFC(LOGDEBUG, LOGDATABASE, "MysqlDatabase::nextid will request"); if ((last_err = query_with_reconnect(sqlcmd)) != 0) { return DB_UNEXPECTED_RESULT; @@ -557,7 +568,7 @@ void MysqlDatabase::start_transaction() if (active) { mysql_autocommit(conn, false); - CLog::Log(LOGDEBUG, "Mysql Start transaction"); + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Mysql Start transaction"); _in_transaction = true; } } @@ -568,7 +579,7 @@ void MysqlDatabase::commit_transaction() { mysql_commit(conn); mysql_autocommit(conn, true); - CLog::Log(LOGDEBUG, "Mysql commit transaction"); + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Mysql commit transaction"); _in_transaction = false; } } @@ -579,7 +590,7 @@ void MysqlDatabase::rollback_transaction() { mysql_rollback(conn); mysql_autocommit(conn, true); - CLog::Log(LOGDEBUG, "Mysql rollback transaction"); + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Mysql rollback transaction"); _in_transaction = false; } } @@ -1837,7 +1848,7 @@ int MysqlDataset::exec(const std::string& sql) qry += " CHARACTER SET utf8 COLLATE utf8_general_ci"; } - CLog::Log(LOGDEBUG, "Mysql execute: {}", qry); + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Mysql execute: {}", qry); if (db->setErr(static_cast<MysqlDatabase*>(db)->query_with_reconnect(qry.c_str()), qry.c_str()) != MYSQL_OK) diff --git a/xbmc/dbwrappers/sqlitedataset.cpp b/xbmc/dbwrappers/sqlitedataset.cpp index 254286833505b..ab5619bb92e57 100644 --- a/xbmc/dbwrappers/sqlitedataset.cpp +++ b/xbmc/dbwrappers/sqlitedataset.cpp @@ -397,7 +397,7 @@ int SqliteDatabase::copy(const char* backup_name) if (active == false) throw DbErrors("Can't copy database: no active connection..."); - CLog::Log(LOGDEBUG, "Copying from {} to {} at {}", db, backup_name, host); + CLog::LogF(LOGDEBUG, "Copying from {} to {} at {}", db, backup_name, host); int rc; std::string backup_db = backup_name; @@ -449,7 +449,7 @@ int SqliteDatabase::drop_analytics(void) char sqlcmd[4096]; result_set res; - CLog::Log(LOGDEBUG, "Cleaning indexes from database {} at {}", db, host); + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning indexes from database {} at {}", db, host); snprintf(sqlcmd, sizeof(sqlcmd), "SELECT name FROM sqlite_master WHERE type == 'index' AND sql IS NOT NULL"); if ((last_err = sqlite3_exec(conn, sqlcmd, &callback, &res, NULL)) != SQLITE_OK) @@ -464,7 +464,7 @@ int SqliteDatabase::drop_analytics(void) } res.clear(); - CLog::Log(LOGDEBUG, "Cleaning views from database {} at {}", db, host); + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning views from database {} at {}", db, host); snprintf(sqlcmd, sizeof(sqlcmd), "SELECT name FROM sqlite_master WHERE type == 'view'"); if ((last_err = sqlite3_exec(conn, sqlcmd, &callback, &res, NULL)) != SQLITE_OK) return DB_UNEXPECTED_RESULT; @@ -478,7 +478,7 @@ int SqliteDatabase::drop_analytics(void) } res.clear(); - CLog::Log(LOGDEBUG, "Cleaning triggers from database {} at {}", db, host); + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning triggers from database {} at {}", db, host); snprintf(sqlcmd, sizeof(sqlcmd), "SELECT name FROM sqlite_master WHERE type == 'trigger'"); if ((last_err = sqlite3_exec(conn, sqlcmd, &callback, &res, NULL)) != SQLITE_OK) return DB_UNEXPECTED_RESULT; @@ -510,6 +510,8 @@ int SqliteDatabase::drop() long SqliteDatabase::nextid(const char* sname) { + CLog::LogFC(LOGDEBUG, LOGDATABASE, "nextid for {}", sname); + if (!active) return DB_UNEXPECTED_RESULT; int id; /*,nrow,ncol;*/ @@ -549,6 +551,7 @@ void SqliteDatabase::start_transaction() if (active) { sqlite3_exec(conn, "begin IMMEDIATE", NULL, NULL, NULL); + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Sqlite start transaction"); _in_transaction = true; } } @@ -558,6 +561,7 @@ void SqliteDatabase::commit_transaction() if (active) { sqlite3_exec(conn, "commit", NULL, NULL, NULL); + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Sqlite commit transaction"); _in_transaction = false; } } @@ -567,6 +571,7 @@ void SqliteDatabase::rollback_transaction() if (active) { sqlite3_exec(conn, "rollback", NULL, NULL, NULL); + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Sqlite rollback transaction"); _in_transaction = false; } } @@ -846,6 +851,8 @@ int SqliteDataset::exec(const std::string& sql) qry.resize(pos); } + CLog::LogFC(LOGDEBUG, LOGDATABASE, "Sqlite execute: {}", qry); + char* errmsg; if ((res = db->setErr(sqlite3_exec(handle(), qry.c_str(), &callback, &exec_res, &errmsg), qry.c_str())) == SQLITE_OK) From ad6462d1d09a1438d15e2de955d977a587b3e61a Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Mon, 28 Oct 2024 14:20:32 -0400 Subject: [PATCH 609/651] [dbwrappers] Log execution time of exec() --- xbmc/dbwrappers/mysqldataset.cpp | 15 +++++++++++---- xbmc/dbwrappers/sqlitedataset.cpp | 17 +++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/xbmc/dbwrappers/mysqldataset.cpp b/xbmc/dbwrappers/mysqldataset.cpp index 6607dc59592af..5595760d9aaf1 100644 --- a/xbmc/dbwrappers/mysqldataset.cpp +++ b/xbmc/dbwrappers/mysqldataset.cpp @@ -1822,7 +1822,7 @@ int MysqlDataset::exec(const std::string& sql) if (!handle()) throw DbErrors("No Database Connection"); std::string qry = sql; - int res = 0; + exec_res.clear(); // enforce the "auto_increment" keyword to be appended to "integer primary key" @@ -1848,10 +1848,17 @@ int MysqlDataset::exec(const std::string& sql) qry += " CHARACTER SET utf8 COLLATE utf8_general_ci"; } - CLog::LogFC(LOGDEBUG, LOGDATABASE, "Mysql execute: {}", qry); + const auto start = std::chrono::steady_clock::now(); - if (db->setErr(static_cast<MysqlDatabase*>(db)->query_with_reconnect(qry.c_str()), qry.c_str()) != - MYSQL_OK) + const int res = + db->setErr(static_cast<MysqlDatabase*>(db)->query_with_reconnect(qry.c_str()), qry.c_str()); + + const auto end = std::chrono::steady_clock::now(); + const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); + + CLog::LogFC(LOGDEBUG, LOGDATABASE, "{} ms for query: {}", duration.count(), qry); + + if (res != MYSQL_OK) { throw DbErrors(db->getErrorMsg()); } diff --git a/xbmc/dbwrappers/sqlitedataset.cpp b/xbmc/dbwrappers/sqlitedataset.cpp index ab5619bb92e57..4ee5e0938564d 100644 --- a/xbmc/dbwrappers/sqlitedataset.cpp +++ b/xbmc/dbwrappers/sqlitedataset.cpp @@ -810,7 +810,7 @@ int SqliteDataset::exec(const std::string& sql) if (!handle()) throw DbErrors("No Database Connection"); std::string qry = sql; - int res; + exec_res.clear(); // Strip size constraints from indexes (not supported in sqlite) @@ -851,12 +851,21 @@ int SqliteDataset::exec(const std::string& sql) qry.resize(pos); } - CLog::LogFC(LOGDEBUG, LOGDATABASE, "Sqlite execute: {}", qry); + const auto start = std::chrono::steady_clock::now(); char* errmsg; - if ((res = db->setErr(sqlite3_exec(handle(), qry.c_str(), &callback, &exec_res, &errmsg), - qry.c_str())) == SQLITE_OK) + const int res = + db->setErr(sqlite3_exec(handle(), qry.c_str(), &callback, &exec_res, &errmsg), qry.c_str()); + + const auto end = std::chrono::steady_clock::now(); + const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); + + CLog::LogFC(LOGDEBUG, LOGDATABASE, "{} ms for query: {}", duration.count(), qry); + + if (res == SQLITE_OK) + { return res; + } else { if (errmsg) From f0d3bfd6b1d0aaaf89a5345b686c79b5482b735c Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Mon, 28 Oct 2024 15:39:39 -0400 Subject: [PATCH 610/651] [dbwrappers] Move sqlite-only post connection actions into Sqlite class cleaner and avoids uselessly logging the three pragma directives for each sqlite connection creation. --- xbmc/dbwrappers/Database.cpp | 9 +-------- xbmc/dbwrappers/dataset.h | 1 + xbmc/dbwrappers/sqlitedataset.cpp | 16 ++++++++++++++++ xbmc/dbwrappers/sqlitedataset.h | 3 ++- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/xbmc/dbwrappers/Database.cpp b/xbmc/dbwrappers/Database.cpp index dc17d4d843cef..fa233e43da4a3 100644 --- a/xbmc/dbwrappers/Database.cpp +++ b/xbmc/dbwrappers/Database.cpp @@ -644,14 +644,7 @@ bool CDatabase::Connect(const std::string& dbName, const DatabaseSettings& dbSet } CreateDatabase(); } - - // sqlite3 post connection operations - if (dbSettings.type == "sqlite3") - { - m_pDS->exec("PRAGMA cache_size=4096\n"); - m_pDS->exec("PRAGMA synchronous='NORMAL'\n"); - m_pDS->exec("PRAGMA count_changes='OFF'\n"); - } + m_pDB->postconnect(); } catch (DbErrors& error) { diff --git a/xbmc/dbwrappers/dataset.h b/xbmc/dbwrappers/dataset.h index 03ced6394a346..2bdf98b4bd460 100644 --- a/xbmc/dbwrappers/dataset.h +++ b/xbmc/dbwrappers/dataset.h @@ -127,6 +127,7 @@ class Database const char* newCiphers = NULL, bool newCompression = false); virtual void disconnect(void) { active = false; } + virtual int postconnect() { return DB_COMMAND_OK; } virtual int reset(void) { return DB_COMMAND_OK; } virtual int create(void) { return DB_COMMAND_OK; } virtual int drop(void) { return DB_COMMAND_OK; } diff --git a/xbmc/dbwrappers/sqlitedataset.cpp b/xbmc/dbwrappers/sqlitedataset.cpp index 4ee5e0938564d..cee89f4a9c14e 100644 --- a/xbmc/dbwrappers/sqlitedataset.cpp +++ b/xbmc/dbwrappers/sqlitedataset.cpp @@ -387,6 +387,22 @@ void SqliteDatabase::disconnect(void) active = false; } +int SqliteDatabase::postconnect() +{ + if (!active) + throw DbErrors("Cannot execute postconnect actions: no active connection..."); + + const std::string cmd{ + "PRAGMA cache_size=4096; PRAGMA synchronous='NORMAL'; PRAGMA count_changes='OFF';"}; + + if (setErr(sqlite3_exec(getHandle(), cmd.c_str(), NULL, NULL, NULL), cmd.c_str()) != SQLITE_OK) + { + throw DbErrors("%s", getErrorMsg()); + } + + return DB_COMMAND_OK; +} + int SqliteDatabase::create() { return connect(true); diff --git a/xbmc/dbwrappers/sqlitedataset.h b/xbmc/dbwrappers/sqlitedataset.h index 4695dfd74344c..033893f351d31 100644 --- a/xbmc/dbwrappers/sqlitedataset.h +++ b/xbmc/dbwrappers/sqlitedataset.h @@ -54,10 +54,11 @@ class SqliteDatabase : public Database void setDatabase(const char* newDb) override; /* func. connects to database-server */ - int connect(bool create) override; /* func. disconnects from database-server */ void disconnect() override; + /* func. post-connection operations */ + int postconnect() override; /* func. creates new database */ int create() override; /* func. deletes database */ From 0144d5d9472e3f9c603bd3a66af912e75ed50c65 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Tue, 15 Oct 2024 19:38:55 -0400 Subject: [PATCH 611/651] [Windows] Synchronize dxerr.h/dxerr.cpp with upstream - minimized the differences with upstream - disabled clang formatting to minimizes diffs with upstream - kept the changes required for successful build on desktop and uwp - kept the typo fixes - removed codes and descriptions for DirectInput and Direct3D9 - DirectSound messages moved to WinErrorUtils --- xbmc/platform/win32/dxerr.cpp | 297 +++++----------------------------- xbmc/platform/win32/dxerr.h | 25 ++- 2 files changed, 60 insertions(+), 262 deletions(-) diff --git a/xbmc/platform/win32/dxerr.cpp b/xbmc/platform/win32/dxerr.cpp index acf3e6a507696..f3c74406e8038 100644 --- a/xbmc/platform/win32/dxerr.cpp +++ b/xbmc/platform/win32/dxerr.cpp @@ -3,36 +3,40 @@ * * DirectX Error Library * - * Copyright (C) Microsoft Corporation. All rights reserved. + * Copyright (c) Microsoft Corporation. * * SPDX-License-Identifier: MIT * See LICENSES/README.md for more information. + * + * source https://github.com/microsoft/DXUT/blob/main/Core/dxerr.cpp + * last synchronized 2024-10-15 + * differences with source: + * - removed redundant _HRESULT_TYPEDEF_() casts in DXGetErrorStringW() > caused wrong code text + * - a few typos corrected in DXGetErrorDescriptionW() + * - return empty text when no code or description is found. The caller provides its own fallback. + */ +// clang-format off + // This version only supports UNICODE. #include "dxerr.h" -#include <stdio.h> -#include <algorithm> +#include <cstdio> #if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP) #include <ddraw.h> -#include <d3d9.h> #include <mmsystem.h> #include <dsound.h> - -#define DIRECTINPUT_VERSION 0x800 -#include <dinput.h> -#include <dinputd.h> #endif #include <d3d10_1.h> -#include <d3d11_1.h> +#include <d3d11.h> #if !defined(WINAPI_FAMILY) || WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP #include <wincodec.h> -#include <d2derr.h> +#include <d2d1.h> #include <dwrite.h> #endif @@ -61,19 +65,19 @@ //-------------------------------------------------------------------------------------- #define CHK_ERR(hrchk, strOut) \ - case hrchk: \ + case static_cast<HRESULT>(hrchk): \ return L##strOut; #define CHK_ERRA(hrchk) \ - case hrchk: \ - return L#hrchk; + case static_cast<HRESULT>(hrchk): \ + return L## #hrchk; #define HRESULT_FROM_WIN32b(x) ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) : ((HRESULT) (((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000))) #define CHK_ERR_WIN32A(hrchk) \ case HRESULT_FROM_WIN32b(hrchk): \ case hrchk: \ - return L#hrchk; + return L## #hrchk; #define CHK_ERR_WIN32_ONLY(hrchk, strOut) \ case HRESULT_FROM_WIN32b(hrchk): \ @@ -3132,116 +3136,6 @@ const WCHAR* WINAPI DXGetErrorStringW( _In_ HRESULT hr ) CHK_ERRA(DDERR_NODRIVERSUPPORT) CHK_ERRA(DDERR_DEVICEDOESNTOWNSURFACE) -// ------------------------------------------------------------- -// dinput.h error codes -// ------------------------------------------------------------- -// CHK_ERRA(DI_OK) -// CHK_ERRA(DI_NOTATTACHED) -// CHK_ERRA(DI_BUFFEROVERFLOW) -// CHK_ERRA(DI_PROPNOEFFECT) -// CHK_ERRA(DI_NOEFFECT) -// CHK_ERRA(DI_POLLEDDEVICE) -// CHK_ERRA(DI_DOWNLOADSKIPPED) -// CHK_ERRA(DI_EFFECTRESTARTED) -// CHK_ERRA(DI_SETTINGSNOTSAVED_ACCESSDENIED) -// CHK_ERRA(DI_SETTINGSNOTSAVED_DISKFULL) -// CHK_ERRA(DI_TRUNCATED) -// CHK_ERRA(DI_TRUNCATEDANDRESTARTED) -// CHK_ERRA(DI_WRITEPROTECT) - CHK_ERR(DIERR_INSUFFICIENTPRIVS, "DIERR_INSUFFICIENTPRIVS & VFW_E_INVALIDMEDIATYPE") - CHK_ERR(DIERR_DEVICEFULL, "DIERR_DEVICEFULL & VFW_E_INVALIDSUBTYPE & DMO_E_INVALIDSTREAMINDEX") - CHK_ERR(DIERR_MOREDATA, "DIERR_MOREDATA & VFW_E_NEED_OWNER & DMO_E_INVALIDTYPE") - CHK_ERR(DIERR_NOTDOWNLOADED, "DIERR_NOTDOWNLOADED & VFW_E_ENUM_OUT_OF_SYNC & DMO_E_TYPE_NOT_SET") - CHK_ERR(DIERR_HASEFFECTS, "DIERR_HASEFFECTS & VFW_E_ALREADY_CONNECTED & DMO_E_NOTACCEPTING") - CHK_ERR(DIERR_NOTEXCLUSIVEACQUIRED, "DIERR_NOTEXCLUSIVEACQUIRED & VFW_E_FILTER_ACTIVE & DMO_E_TYPE_NOT_ACCEPTED") - CHK_ERR(DIERR_INCOMPLETEEFFECT, "DIERR_INCOMPLETEEFFECT & VFW_E_NO_TYPES & DMO_E_NO_MORE_ITEMS") - CHK_ERR(DIERR_NOTBUFFERED, "DIERR_NOTBUFFERED & VFW_E_NO_ACCEPTABLE_TYPES") - CHK_ERR(DIERR_EFFECTPLAYING, "DIERR_EFFECTPLAYING & VFW_E_INVALID_DIRECTION") - CHK_ERR(DIERR_UNPLUGGED, "DIERR_UNPLUGGED & VFW_E_NOT_CONNECTED") - CHK_ERR(DIERR_REPORTFULL, "DIERR_REPORTFULL & VFW_E_NO_ALLOCATOR") - CHK_ERR(DIERR_MAPFILEFAIL, "DIERR_MAPFILEFAIL & VFW_E_RUNTIME_ERROR") -// CHK_ERRA(DIERR_OLDDIRECTINPUTVERSION) -// CHK_ERRA(DIERR_GENERIC) -// CHK_ERRA(DIERR_OLDDIRECTINPUTVERSION) -// CHK_ERRA(DIERR_BETADIRECTINPUTVERSION) -// CHK_ERRA(DIERR_BADDRIVERVER) -// CHK_ERRA(DIERR_DEVICENOTREG) -// CHK_ERRA(DIERR_NOTFOUND) -// CHK_ERRA(DIERR_OBJECTNOTFOUND) -// CHK_ERRA(DIERR_INVALIDPARAM) -// CHK_ERRA(DIERR_NOINTERFACE) -// CHK_ERRA(DIERR_GENERIC) -// CHK_ERRA(DIERR_OUTOFMEMORY) -// CHK_ERRA(DIERR_UNSUPPORTED) -// CHK_ERRA(DIERR_NOTINITIALIZED) -// CHK_ERRA(DIERR_ALREADYINITIALIZED) -// CHK_ERRA(DIERR_NOAGGREGATION) -// CHK_ERRA(DIERR_OTHERAPPHASPRIO) -// CHK_ERRA(DIERR_INPUTLOST) -// CHK_ERRA(DIERR_ACQUIRED) -// CHK_ERRA(DIERR_NOTACQUIRED) -// CHK_ERRA(DIERR_READONLY) -// CHK_ERRA(DIERR_HANDLEEXISTS) - - -// ------------------------------------------------------------- -// dinputd.h error -// ------------------------------------------------------------- -// CHK_ERRA(DIERR_NOMOREITEMS) - CHK_ERRA(DIERR_DRIVERFIRST) - CHK_ERR(DIERR_DRIVERFIRST+1, "DIERR_DRIVERFIRST+1") - CHK_ERR(DIERR_DRIVERFIRST+2, "DIERR_DRIVERFIRST+2") - CHK_ERR(DIERR_DRIVERFIRST+3, "DIERR_DRIVERFIRST+3") - CHK_ERR(DIERR_DRIVERFIRST+4, "DIERR_DRIVERFIRST+4") - CHK_ERR(DIERR_DRIVERFIRST+5, "DIERR_DRIVERFIRST+5") - CHK_ERRA(DIERR_DRIVERLAST) - CHK_ERR(DIERR_INVALIDCLASSINSTALLER, "DIERR_INVALIDCLASSINSTALLER") - CHK_ERR(DIERR_CANCELLED, "DIERR_CANCELLED & MS_E_SAMPLEALLOC") - CHK_ERRA(DIERR_BADINF) - -// ------------------------------------------------------------- -// d3d9.h error codes -// ------------------------------------------------------------- -// CHK_ERRA(D3D_OK) - CHK_ERRA(D3DERR_WRONGTEXTUREFORMAT) - CHK_ERRA(D3DERR_UNSUPPORTEDCOLOROPERATION) - CHK_ERRA(D3DERR_UNSUPPORTEDCOLORARG) - CHK_ERRA(D3DERR_UNSUPPORTEDALPHAOPERATION) - CHK_ERRA(D3DERR_UNSUPPORTEDALPHAARG) - CHK_ERRA(D3DERR_TOOMANYOPERATIONS) - CHK_ERRA(D3DERR_CONFLICTINGTEXTUREFILTER) - CHK_ERRA(D3DERR_UNSUPPORTEDFACTORVALUE) - CHK_ERRA(D3DERR_CONFLICTINGRENDERSTATE) - CHK_ERRA(D3DERR_UNSUPPORTEDTEXTUREFILTER) - CHK_ERRA(D3DERR_CONFLICTINGTEXTUREPALETTE) - CHK_ERRA(D3DERR_DRIVERINTERNALERROR) - CHK_ERRA(D3DERR_NOTFOUND) - CHK_ERRA(D3DERR_MOREDATA) - CHK_ERRA(D3DERR_DEVICELOST) - CHK_ERRA(D3DERR_DEVICENOTRESET) - CHK_ERRA(D3DERR_NOTAVAILABLE) - CHK_ERRA(D3DERR_OUTOFVIDEOMEMORY) - CHK_ERRA(D3DERR_INVALIDDEVICE) - CHK_ERRA(D3DERR_INVALIDCALL) - CHK_ERRA(D3DERR_DRIVERINVALIDCALL) - //CHK_ERRA(D3DERR_WASSTILLDRAWING) - CHK_ERRA(D3DOK_NOAUTOGEN) - - // Extended for Windows Vista - CHK_ERRA(D3DERR_DEVICEREMOVED) - CHK_ERRA(S_NOT_RESIDENT) - CHK_ERRA(S_RESIDENT_IN_SHARED_MEMORY) - CHK_ERRA(S_PRESENT_MODE_CHANGED) - CHK_ERRA(S_PRESENT_OCCLUDED) - CHK_ERRA(D3DERR_DEVICEHUNG) - - // Extended for Windows 7 - CHK_ERRA(D3DERR_UNSUPPORTEDOVERLAY) - CHK_ERRA(D3DERR_UNSUPPORTEDOVERLAYFORMAT) - CHK_ERRA(D3DERR_CANNOTPROTECTCONTENT) - CHK_ERRA(D3DERR_UNSUPPORTEDCRYPTO) - CHK_ERRA(D3DERR_PRESENT_STATISTICS_DISJOINT) - // ------------------------------------------------------------- // dsound.h error codes // ------------------------------------------------------------- @@ -3269,8 +3163,7 @@ const WCHAR* WINAPI DXGetErrorStringW( _In_ HRESULT hr ) CHK_ERRA(DSERR_SENDLOOP) CHK_ERRA(DSERR_BADSENDBUFFERGUID) CHK_ERRA(DSERR_OBJECTNOTFOUND) - - CHK_ERRA(DSERR_FXUNAVAILABLE) + CHK_ERRA(DSERR_FXUNAVAILABLE) #endif // !WINAPI_FAMILY || WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP @@ -3319,6 +3212,7 @@ const WCHAR* WINAPI DXGetErrorStringW( _In_ HRESULT hr ) // ------------------------------------------------------------- // Direct2D error codes // ------------------------------------------------------------- +// D2DERR_UNSUPPORTED_PIXEL_FORMAT is not defined for UWP. WINCODEC_ERR_UNSUPPORTEDPIXELFORMAT is and has the same value. // CHK_ERRA(D2DERR_UNSUPPORTED_PIXEL_FORMAT) // CHK_ERRA(D2DERR_INSUFFICIENT_BUFFER) CHK_ERRA(D2DERR_WRONG_STATE) @@ -3434,9 +3328,9 @@ const WCHAR* WINAPI DXGetErrorStringW( _In_ HRESULT hr ) // xapo.h error codes // ------------------------------------------------------------- CHK_ERRA(XAPO_E_FORMAT_UNSUPPORTED) - } - return L"Unknown"; + default: return L""; + } } //-------------------------------------------------------------------------------------- @@ -3447,14 +3341,12 @@ const WCHAR* WINAPI DXGetErrorStringW( _In_ HRESULT hr ) #undef CHK_ERR_WIN32_ONLY #define CHK_ERRA(hrchk) \ - case hrchk: \ - wcscpy_s( desc, count, L#hrchk ); \ - break; + case static_cast<HRESULT>(hrchk): \ + wcscpy_s( desc, count, L## #hrchk ); break; #define CHK_ERR(hrchk, strOut) \ - case hrchk: \ - wcscpy_s( desc, count, L##strOut ); \ - break; + case static_cast<HRESULT>(hrchk): \ + wcscpy_s( desc, count, L##strOut ); break; //-------------------------------------------------------------------------------------- @@ -3466,13 +3358,21 @@ void WINAPI DXGetErrorDescriptionW( _In_ HRESULT hr, _Out_cap_(count) WCHAR* des *desc = 0; // First try to see if FormatMessage knows this hr - UINT icount = static_cast<UINT>( std::min<size_t>( count, 32767 ) ); + LPWSTR errorText = nullptr; + + DWORD result = FormatMessageW( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS| FORMAT_MESSAGE_ALLOCATE_BUFFER, + nullptr, static_cast<DWORD>(hr), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPWSTR>(&errorText), 0, nullptr ); + + if (result > 0 && errorText) + { + wcscpy_s( desc, count, errorText ); - DWORD result = FormatMessageW( FORMAT_MESSAGE_FROM_SYSTEM, nullptr, hr, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), desc, icount, nullptr ); + if ( errorText ) + LocalFree( errorText ); - if (result > 0) return; + } switch (hr) { @@ -3602,117 +3502,6 @@ void WINAPI DXGetErrorDescriptionW( _In_ HRESULT hr, _Out_cap_(count) WCHAR* des CHK_ERR(DDERR_NODRIVERSUPPORT, "The driver does not enumerate display mode refresh rates.") CHK_ERR(DDERR_DEVICEDOESNTOWNSURFACE, "Surfaces created by one direct draw device cannot be used directly by another direct draw device.") - -// ------------------------------------------------------------- -// dinput.h error codes -// ------------------------------------------------------------- -// CHK_ERR(DI_OK, "DI_OK") -// CHK_ERR(DI_NOTATTACHED, "DI_NOTATTACHED") -// CHK_ERR(DI_BUFFEROVERFLOW, "DI_BUFFEROVERFLOW") -// CHK_ERR(DI_PROPNOEFFECT, "DI_PROPNOEFFECT") -// CHK_ERR(DI_NOEFFECT, "DI_NOEFFECT") -// CHK_ERR(DI_POLLEDDEVICE, "DI_POLLEDDEVICE") -// CHK_ERR(DI_DOWNLOADSKIPPED, "DI_DOWNLOADSKIPPED") -// CHK_ERR(DI_EFFECTRESTARTED, "DI_EFFECTRESTARTED") -// CHK_ERR(DI_SETTINGSNOTSAVED_ACCESSDENIED, "DI_SETTINGSNOTSAVED_ACCESSDENIED") -// CHK_ERR(DI_SETTINGSNOTSAVED_DISKFULL, "DI_SETTINGSNOTSAVED_DISKFULL") -// CHK_ERR(DI_TRUNCATED, "DI_TRUNCATED") -// CHK_ERR(DI_TRUNCATEDANDRESTARTED, "DI_TRUNCATEDANDRESTARTED") -// CHK_ERR(DI_WRITEPROTECT, "DI_WRITEPROTECT") - CHK_ERR(DIERR_OLDDIRECTINPUTVERSION, "The application requires a newer version of DirectInput.") -// CHK_ERR(DIERR_GENERIC, "DIERR_GENERIC") -// CHK_ERR(DIERR_OLDDIRECTINPUTVERSION, "DIERR_OLDDIRECTINPUTVERSION") - CHK_ERR(DIERR_BETADIRECTINPUTVERSION, "The application was written for an unsupported prerelease version of DirectInput.") - CHK_ERR(DIERR_BADDRIVERVER, "The object could not be created due to an incompatible driver version or mismatched or incomplete driver components.") -// CHK_ERR(DIERR_DEVICENOTREG, "DIERR_DEVICENOTREG") -// CHK_ERR(DIERR_NOTFOUND, "The requested object does not exist.") -// CHK_ERR(DIERR_OBJECTNOTFOUND, "DIERR_OBJECTNOTFOUND") -// CHK_ERR(DIERR_INVALIDPARAM, "DIERR_INVALIDPARAM") -// CHK_ERR(DIERR_NOINTERFACE, "DIERR_NOINTERFACE") -// CHK_ERR(DIERR_GENERIC, "DIERR_GENERIC") -// CHK_ERR(DIERR_OUTOFMEMORY, "DIERR_OUTOFMEMORY") -// CHK_ERR(DIERR_UNSUPPORTED, "DIERR_UNSUPPORTED") - CHK_ERR(DIERR_NOTINITIALIZED, "This object has not been initialized") - CHK_ERR(DIERR_ALREADYINITIALIZED, "This object is already initialized") -// CHK_ERR(DIERR_NOAGGREGATION, "DIERR_NOAGGREGATION") -// CHK_ERR(DIERR_OTHERAPPHASPRIO, "DIERR_OTHERAPPHASPRIO") - CHK_ERR(DIERR_INPUTLOST, "Access to the device has been lost. It must be re-acquired.") - CHK_ERR(DIERR_ACQUIRED, "The operation cannot be performed while the device is acquired.") - CHK_ERR(DIERR_NOTACQUIRED, "The operation cannot be performed unless the device is acquired.") -// CHK_ERR(DIERR_READONLY, "DIERR_READONLY") -// CHK_ERR(DIERR_HANDLEEXISTS, "DIERR_HANDLEEXISTS") - CHK_ERR(DIERR_INSUFFICIENTPRIVS, "Unable to IDirectInputJoyConfig_Acquire because the user does not have sufficient privileges to change the joystick configuration. & An invalid media type was specified") - CHK_ERR(DIERR_DEVICEFULL, "The device is full. & An invalid media subtype was specified.") - CHK_ERR(DIERR_MOREDATA, "Not all the requested information fit into the buffer. & This object can only be created as an aggregated object.") - CHK_ERR(DIERR_NOTDOWNLOADED, "The effect is not downloaded. & The enumerator has become invalid.") - CHK_ERR(DIERR_HASEFFECTS, "The device cannot be reinitialized because there are still effects attached to it. & At least one of the pins involved in the operation is already connected.") - CHK_ERR(DIERR_NOTEXCLUSIVEACQUIRED, "The operation cannot be performed unless the device is acquired in DISCL_EXCLUSIVE mode. & This operation cannot be performed because the filter is active.") - CHK_ERR(DIERR_INCOMPLETEEFFECT, "The effect could not be downloaded because essential information is missing. For example, no axes have been associated with the effect, or no type-specific information has been created. & One of the specified pins supports no media types.") - CHK_ERR(DIERR_NOTBUFFERED, "Attempted to read buffered device data from a device that is not buffered. & There is no common media type between these pins.") - CHK_ERR(DIERR_EFFECTPLAYING, "An attempt was made to modify parameters of an effect while it is playing. Not all hardware devices support altering the parameters of an effect while it is playing. & Two pins of the same direction cannot be connected together.") - CHK_ERR(DIERR_UNPLUGGED, "The operation could not be completed because the device is not plugged in. & The operation cannot be performed because the pins are not connected.") - CHK_ERR(DIERR_REPORTFULL, "SendDeviceData failed because more information was requested to be sent than can be sent to the device. Some devices have restrictions on how much data can be sent to them. (For example, there might be a limit on the number of buttons that can be pressed at once.) & No sample buffer allocator is available.") - CHK_ERR(DIERR_MAPFILEFAIL, "A mapper file function failed because reading or writing the user or IHV settings file failed. & A run-time error occurred.") - -// ------------------------------------------------------------- -// dinputd.h error codes -// ------------------------------------------------------------- - CHK_ERR(DIERR_NOMOREITEMS, "No more items.") - CHK_ERR(DIERR_DRIVERFIRST, "Device driver-specific codes. Unless the specific driver has been precisely identified, no meaning should be attributed to these values other than that the driver originated the error.") - CHK_ERR(DIERR_DRIVERFIRST+1, "DIERR_DRIVERFIRST+1") - CHK_ERR(DIERR_DRIVERFIRST+2, "DIERR_DRIVERFIRST+2") - CHK_ERR(DIERR_DRIVERFIRST+3, "DIERR_DRIVERFIRST+3") - CHK_ERR(DIERR_DRIVERFIRST+4, "DIERR_DRIVERFIRST+4") - CHK_ERR(DIERR_DRIVERFIRST+5, "DIERR_DRIVERFIRST+5") - CHK_ERR(DIERR_DRIVERLAST, "Device installer errors.") - CHK_ERR(DIERR_INVALIDCLASSINSTALLER, "Registry entry or DLL for class installer invalid or class installer not found.") - CHK_ERR(DIERR_CANCELLED, "The user cancelled the install operation. & The stream already has allocated samples and the surface doesn't match the sample format.") - CHK_ERR(DIERR_BADINF, "The INF file for the selected device could not be found or is invalid or is damaged. & The specified purpose ID can't be used for the call.") - -// ------------------------------------------------------------- -// d3d9.h error codes -// ------------------------------------------------------------- -// CHK_ERR(D3D_OK, "Ok") - CHK_ERR(D3DERR_WRONGTEXTUREFORMAT, "Wrong texture format") - CHK_ERR(D3DERR_UNSUPPORTEDCOLOROPERATION, "Unsupported color operation") - CHK_ERR(D3DERR_UNSUPPORTEDCOLORARG, "Unsupported color arg") - CHK_ERR(D3DERR_UNSUPPORTEDALPHAOPERATION, "Unsupported alpha operation") - CHK_ERR(D3DERR_UNSUPPORTEDALPHAARG, "Unsupported alpha arg") - CHK_ERR(D3DERR_TOOMANYOPERATIONS, "Too many operations") - CHK_ERR(D3DERR_CONFLICTINGTEXTUREFILTER, "Conflicting texture filter") - CHK_ERR(D3DERR_UNSUPPORTEDFACTORVALUE, "Unsupported factor value") - CHK_ERR(D3DERR_CONFLICTINGRENDERSTATE, "Conflicting render state") - CHK_ERR(D3DERR_UNSUPPORTEDTEXTUREFILTER, "Unsupported texture filter") - CHK_ERR(D3DERR_CONFLICTINGTEXTUREPALETTE, "Conflicting texture palette") - CHK_ERR(D3DERR_DRIVERINTERNALERROR, "Driver internal error") - CHK_ERR(D3DERR_NOTFOUND, "Not found") - CHK_ERR(D3DERR_MOREDATA, "More data") - CHK_ERR(D3DERR_DEVICELOST, "Device lost") - CHK_ERR(D3DERR_DEVICENOTRESET, "Device not reset") - CHK_ERR(D3DERR_NOTAVAILABLE, "Not available") - CHK_ERR(D3DERR_OUTOFVIDEOMEMORY, "Out of video memory") - CHK_ERR(D3DERR_INVALIDDEVICE, "Invalid device") - CHK_ERR(D3DERR_INVALIDCALL, "Invalid call") - CHK_ERR(D3DERR_DRIVERINVALIDCALL, "Driver invalid call") - //CHK_ERR(D3DERR_WASSTILLDRAWING, "Was Still Drawing") - CHK_ERR(D3DOK_NOAUTOGEN, "The call succeeded but there won't be any mipmaps generated") - - // Extended for Windows Vista - CHK_ERR(D3DERR_DEVICEREMOVED, "Hardware device was removed") - CHK_ERR(S_NOT_RESIDENT, "Resource not resident in memory") - CHK_ERR(S_RESIDENT_IN_SHARED_MEMORY, "Resource resident in shared memory") - CHK_ERR(S_PRESENT_MODE_CHANGED, "Desktop display mode has changed") - CHK_ERR(S_PRESENT_OCCLUDED, "Client window is occluded (minimized or other fullscreen)") - CHK_ERR(D3DERR_DEVICEHUNG, "Hardware adapter reset by OS") - - // Extended for Windows 7 - CHK_ERR(D3DERR_UNSUPPORTEDOVERLAY, "Overlay is not supported" ) - CHK_ERR(D3DERR_UNSUPPORTEDOVERLAYFORMAT, "Overlay format is not supported" ) - CHK_ERR(D3DERR_CANNOTPROTECTCONTENT, "Contect protection not available" ) - CHK_ERR(D3DERR_UNSUPPORTEDCRYPTO, "Unsupported cryptographic system" ) - CHK_ERR(D3DERR_PRESENT_STATISTICS_DISJOINT, "Presentation statistics are disjoint" ) - - // ------------------------------------------------------------- // dsound.h error codes // ------------------------------------------------------------- @@ -3740,7 +3529,6 @@ void WINAPI DXGetErrorDescriptionW( _In_ HRESULT hr, _Out_cap_(count) WCHAR* des CHK_ERR(DSERR_SENDLOOP, "A circular loop of send effects was detected") CHK_ERR(DSERR_BADSENDBUFFERGUID, "The GUID specified in an audiopath file does not match a valid MIXIN buffer") CHK_ERR(DSERR_OBJECTNOTFOUND, "The object requested was not found (numerically equal to DMUS_E_NOT_FOUND)") - CHK_ERR(DSERR_FXUNAVAILABLE, "Requested effects are not available") #endif // !WINAPI_FAMILY || WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP @@ -3790,6 +3578,7 @@ void WINAPI DXGetErrorDescriptionW( _In_ HRESULT hr, _Out_cap_(count) WCHAR* des // ------------------------------------------------------------- // Direct2D error codes // ------------------------------------------------------------- +// D2DERR_UNSUPPORTED_PIXEL_FORMAT is not defined for UWP. WINCODEC_ERR_UNSUPPORTEDPIXELFORMAT is and has the same value. // CHK_ERR(D2DERR_UNSUPPORTED_PIXEL_FORMAT, "The pixel format is not supported.") // CHK_ERR(D2DERR_INSUFFICIENT_BUFFER, "The supplied buffer was too small to accomodate the data.") CHK_ERR(D2DERR_WRONG_STATE, "The object was not in the correct state to process the method.") @@ -3896,10 +3685,10 @@ void WINAPI DXGetErrorDescriptionW( _In_ HRESULT hr, _Out_cap_(count) WCHAR* des // ------------------------------------------------------------- // xaudio2.h error codes // ------------------------------------------------------------- - CHK_ERR(XAUDIO2_E_INVALID_CALL, "Invalid XAudio2 API call or arguments") - CHK_ERR(XAUDIO2_E_XMA_DECODER_ERROR, "Hardware XMA decoder error") - CHK_ERR(XAUDIO2_E_XAPO_CREATION_FAILED, "Failed to create an audio effect") - CHK_ERR(XAUDIO2_E_DEVICE_INVALIDATED, "Device invalidated (unplugged, disabled, etc)") + CHK_ERR(_HRESULT_TYPEDEF_(XAUDIO2_E_INVALID_CALL), "Invalid XAudio2 API call or arguments") + CHK_ERR(_HRESULT_TYPEDEF_(XAUDIO2_E_XMA_DECODER_ERROR), "Hardware XMA decoder error") + CHK_ERR(_HRESULT_TYPEDEF_(XAUDIO2_E_XAPO_CREATION_FAILED), "Failed to create an audio effect") + CHK_ERR(_HRESULT_TYPEDEF_(XAUDIO2_E_DEVICE_INVALIDATED), "Device invalidated (unplugged, disabled, etc)") // ------------------------------------------------------------- // xapo.h error codes diff --git a/xbmc/platform/win32/dxerr.h b/xbmc/platform/win32/dxerr.h index f6956a611c2ea..d1d1bca38b824 100644 --- a/xbmc/platform/win32/dxerr.h +++ b/xbmc/platform/win32/dxerr.h @@ -7,17 +7,18 @@ * * SPDX-License-Identifier: MIT * See LICENSES/README.md for more information. + * + * source https://github.com/microsoft/DXUT/blob/main/Core/dxerr.h + * last synchronized 2024-10-15 */ +// clang-format off + // This version only supports UNICODE. #pragma once -#if !defined(NOMINMAX) -#define NOMINMAX -#endif - -#include <windows.h> +#include <Windows.h> #include <sal.h> #ifdef __cplusplus @@ -44,15 +45,15 @@ void WINAPI DXGetErrorDescriptionW( _In_ HRESULT hr, _Out_cap_(count) WCHAR* des // // Desc: Outputs a formatted error message to the debug stream // -// Args: WCHAR* strFile The current file, typically passed in using the +// Args: WCHAR* strFile The current file, typically passed in using the // __FILEW__ macro. -// DWORD dwLine The current line number, typically passed in using the +// DWORD dwLine The current line number, typically passed in using the // __LINE__ macro. // HRESULT hr An HRESULT that will be traced to the debug stream. // CHAR* strMsg A string that will be traced to the debug stream (may be NULL) // BOOL bPopMsgBox If TRUE, then a message box will popup also containing the passed info. // -// Return: The hr that was passed in. +// Return: The hr that was passed in. //-------------------------------------------------------------------------------------- HRESULT WINAPI DXTraceW( _In_z_ const WCHAR* strFile, _In_ DWORD dwLine, _In_ HRESULT hr, _In_opt_ const WCHAR* strMsg, _In_ bool bPopMsgBox ); @@ -64,10 +65,18 @@ HRESULT WINAPI DXTraceW( _In_z_ const WCHAR* strFile, _In_ DWORD dwLine, _In_ HR // //-------------------------------------------------------------------------------------- #if defined(DEBUG) || defined(_DEBUG) +#ifdef _MSC_VER #define DXTRACE_MSG(str) DXTrace( __FILEW__, (DWORD)__LINE__, 0, str, false ) #define DXTRACE_ERR(str,hr) DXTrace( __FILEW__, (DWORD)__LINE__, hr, str, false ) #define DXTRACE_ERR_MSGBOX(str,hr) DXTrace( __FILEW__, (DWORD)__LINE__, hr, str, true ) #else +#define DXUT_PASTE(x, y) x##y +#define DXUT_MAKEWIDE(x) DXUT_PASTE(L,x) +#define DXTRACE_MSG(str) DXTrace( DXUT_MAKEWIDE(__FILE__), (DWORD)__LINE__, 0, str, false ) +#define DXTRACE_ERR(str,hr) DXTrace( DXUT_MAKEWIDE(__FILE__), (DWORD)__LINE__, hr, str, false ) +#define DXTRACE_ERR_MSGBOX(str,hr) DXTrace( DXUT_MAKEWIDE(__FILE__), (DWORD)__LINE__, hr, str, true ) +#endif +#else #define DXTRACE_MSG(str) (0L) #define DXTRACE_ERR(str,hr) (hr) #define DXTRACE_ERR_MSGBOX(str,hr) (hr) From d258e3ae3d719d9959492c74b58fe25c44f689ab Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Sat, 19 Oct 2024 14:39:13 -0400 Subject: [PATCH 612/651] [Windows] Move DX::GetErrorDescription to WIN32Util --- .../VideoPlayer/DVDCodecs/Video/DXVA.cpp | 41 +++++++++++-------- .../HwDecRender/DXVAEnumeratorHD.cpp | 26 ++++++------ .../VideoRenderers/HwDecRender/DXVAHD.cpp | 16 ++++---- .../VideoRenderers/RenderCaptureDX.cpp | 4 +- xbmc/platform/win32/WIN32Util.cpp | 17 ++++++++ xbmc/platform/win32/WIN32Util.h | 6 +++ xbmc/platform/win32/WinRtUtil.cpp | 7 ++-- xbmc/rendering/dx/DeviceResources.cpp | 12 +++--- xbmc/rendering/dx/DirectXHelper.h | 10 ----- xbmc/windowing/windows/VideoSyncD3D.cpp | 4 +- 10 files changed, 85 insertions(+), 58 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.cpp index 5bd64499fb595..8a19603009d2a 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DXVA.cpp @@ -29,6 +29,8 @@ #include "utils/SystemInfo.h" #include "utils/log.h" +#include "platform/win32/WIN32Util.h" + #include <algorithm> #include <mutex> @@ -342,7 +344,7 @@ bool CContext::CreateContext() if (FAILED(pD3DDevice.As(&m_d3d11Debug))) { CLog::LogF(LOGDEBUG, "unable to create debug interface. Error {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); } #endif } @@ -350,7 +352,7 @@ bool CContext::CreateContext() { CLog::LogF(LOGWARNING, "unable to create device for decoding, fallback to using app device. Error {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); m_sharingAllowed = false; } } @@ -601,7 +603,7 @@ bool CContext::CreateSurfaces(const D3D11_VIDEO_DECODER_DESC& format, uint32_t c if (FAILED(hr = pD3DDevice->CreateTexture2D(&texDesc, NULL, texture.GetAddressOf()))) { CLog::LogF(LOGERROR, "failed creating decoder texture array. Error {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return false; } @@ -632,7 +634,7 @@ bool CContext::CreateSurfaces(const D3D11_VIDEO_DECODER_DESC& format, uint32_t c hr = m_pD3D11Device->CreateVideoDecoderOutputView(texture.Get(), &vdovDesc, &surfaces[i]); if (FAILED(hr)) { - CLog::LogF(LOGERROR, "failed creating surfaces. Error {}", DX::GetErrorDescription(hr)); + CLog::LogF(LOGERROR, "failed creating surfaces. Error {}", CWIN32Util::FormatHRESULT(hr)); break; } if (pD3DDeviceContext1) @@ -799,7 +801,7 @@ HRESULT CVideoBufferShared::GetResource(ID3D11Resource** ppResource) if (FAILED(hr = pD3DDevice->OpenSharedResource(handle, __uuidof(ID3D11Resource), &m_sharedRes))) { CLog::LogF(LOGDEBUG, "unable to open the shared resource, error description: {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return hr; } @@ -811,18 +813,18 @@ HRESULT CVideoBufferShared::GetResource(ID3D11Resource** ppResource) if (FAILED(hr = context1.As(&m_appContext4))) { CLog::LogF(LOGDEBUG, "ID3D11DeviceContext4 is not available, error description: {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); } else if (FAILED(hr = pD3DDevice.As(&device5))) { CLog::LogF(LOGDEBUG, "ID3D11Device5 is not available, error description: {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); m_appContext4 = nullptr; } else if (FAILED(hr = device5->OpenSharedFence(m_handleFence, IID_PPV_ARGS(&m_appFence)))) { CLog::LogF(LOGDEBUG, "unable to open the shared fence, error description: {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); m_appContext4 = nullptr; } } @@ -834,7 +836,7 @@ HRESULT CVideoBufferShared::GetResource(ID3D11Resource** ppResource) if (FAILED(hr = m_appContext4->Wait(m_appFence.Get(), m_fenceValue))) { CLog::LogF(LOGDEBUG, "error waiting for the fence value, error description: {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); } } @@ -875,28 +877,28 @@ void CVideoBufferShared::InitializeFence(CDecoder* decoder) if (FAILED(hr = immediateContext.As(&m_deviceContext4))) { CLog::LogF(LOGDEBUG, "ID3D11DeviceContext4 is not available, error description: {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); goto error; } if (FAILED(hr = device.As(&d3ddev5))) { CLog::LogF(LOGDEBUG, "ID3D11Device5 is not available, error description: {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); goto error; } if (FAILED(hr = d3ddev5->CreateFence(0, D3D11_FENCE_FLAG_SHARED, IID_PPV_ARGS(&m_fence)))) { CLog::LogF(LOGDEBUG, "unable to create ID3D11Fence, error description: {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); goto error; } if (FAILED(hr = m_fence->CreateSharedHandle(NULL, GENERIC_ALL, NULL, &m_handleFence))) { CLog::LogF(LOGDEBUG, "unable to create the shared handle of the fence, error description: {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); goto error; } @@ -946,13 +948,15 @@ void CVideoBufferCopy::Initialize(CDecoder* decoder) if (FAILED(hr = CVideoBuffer::GetResource(&pResource))) { - CLog::LogF(LOGDEBUG, "unable to get decoder resource. Error {}", DX::GetErrorDescription(hr)); + CLog::LogF(LOGDEBUG, "unable to get decoder resource. Error {}", + CWIN32Util::FormatHRESULT(hr)); return; } if (FAILED(hr = pResource.As(&pDecoderTexture))) { - CLog::LogF(LOGDEBUG, "unable to get decoder texture. Error {}", DX::GetErrorDescription(hr)); + CLog::LogF(LOGDEBUG, "unable to get decoder texture. Error {}", + CWIN32Util::FormatHRESULT(hr)); return; } @@ -964,20 +968,21 @@ void CVideoBufferCopy::Initialize(CDecoder* decoder) if (FAILED(hr = pDevice->CreateTexture2D(&desc, nullptr, &pCopyTexture))) { - CLog::LogF(LOGDEBUG, "unable to create copy texture. Error {}", DX::GetErrorDescription(hr)); + CLog::LogF(LOGDEBUG, "unable to create copy texture. Error {}", + CWIN32Util::FormatHRESULT(hr)); return; } if (FAILED(hr = pCopyTexture.As(&pDXGIResource))) { CLog::LogF(LOGDEBUG, "unable to get DXGI resource for copy texture. Error {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return; } HANDLE shared_handle; if (FAILED(hr = pDXGIResource->GetSharedHandle(&shared_handle))) { - CLog::LogF(LOGDEBUG, "unable to get shared handle. Error {}", DX::GetErrorDescription(hr)); + CLog::LogF(LOGDEBUG, "unable to get shared handle. Error {}", CWIN32Util::FormatHRESULT(hr)); return; } diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAEnumeratorHD.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAEnumeratorHD.cpp index be7f6b8267db8..e4e262ee64254 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAEnumeratorHD.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAEnumeratorHD.cpp @@ -11,6 +11,8 @@ #include "rendering/dx/RenderContext.h" #include "utils/log.h" +#include "platform/win32/WIN32Util.h" + #include <mutex> #include <Windows.h> @@ -73,7 +75,7 @@ bool CEnumeratorHD::OpenEnumerator() if (FAILED(hr = pD3DDevice.As(&m_pVideoDevice))) { CLog::LogF(LOGWARNING, "video device initialization is failed. Error {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return false; } @@ -91,14 +93,14 @@ bool CEnumeratorHD::OpenEnumerator() &contentDesc, m_pEnumerator.ReleaseAndGetAddressOf()))) { CLog::LogF(LOGWARNING, "failed to init video enumerator with params: {}x{}. Error {}", m_width, - m_height, DX::GetErrorDescription(hr)); + m_height, CWIN32Util::FormatHRESULT(hr)); return false; } if (FAILED(hr = m_pEnumerator.As(&m_pEnumerator1))) { CLog::LogF(LOGDEBUG, "ID3D11VideoProcessorEnumerator1 not available on this system. Message {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); } return true; @@ -135,7 +137,7 @@ ProcessorCapabilities CEnumeratorHD::ProbeProcessorCaps() if (FAILED(hr = m_pEnumerator->GetVideoProcessorCaps(&result.m_vcaps))) { - CLog::LogF(LOGWARNING, "failed to get processor caps. Error {}", DX::GetErrorDescription(hr)); + CLog::LogF(LOGWARNING, "failed to get processor caps. Error {}", CWIN32Util::FormatHRESULT(hr)); return {}; } @@ -181,7 +183,7 @@ ProcessorCapabilities CEnumeratorHD::ProbeProcessorCaps() if (FAILED(hr = m_pEnumerator->GetVideoProcessorRateConversionCaps(i, &convCaps))) { CLog::LogF(LOGWARNING, "unable to retrieve processor rate conversion caps {}. Error {}", i, - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); continue; } @@ -210,7 +212,7 @@ ProcessorCapabilities CEnumeratorHD::ProbeProcessorCaps() CLog::LogF(LOGWARNING, "unable to retrieve processor rate conversion caps {}, the deinterlacing " "capabilities are unknown. Error {}", - result.m_procIndex, DX::GetErrorDescription(hr)); + result.m_procIndex, CWIN32Util::FormatHRESULT(hr)); } CLog::LogF( @@ -269,7 +271,7 @@ ProcessorFormats CEnumeratorHD::GetProcessorFormats(bool inputFormats, bool outp { CLog::LogF(LOGWARNING, "Unable to retrieve support of the dxva processor for format {}, error {}", - DX::DXGIFormatToString(dxgiFormat), DX::GetErrorDescription(hr)); + DX::DXGIFormatToString(dxgiFormat), CWIN32Util::FormatHRESULT(hr)); return formats; } } @@ -329,7 +331,7 @@ bool CEnumeratorHD::CheckConversionInternal(DXGI_FORMAT inputFormat, else { CLog::LogF(LOGERROR, "unable to validate the format conversion, error {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return false; } } @@ -379,7 +381,7 @@ void CEnumeratorHD::LogSupportedConversions(const DXGI_FORMAT inputFormat, { CLog::LogFC(LOGDEBUG, LOGVIDEO, "unable to retrieve processor support of input format {}. Error {}", - DX::DXGIFormatToString(inputFormat), DX::GetErrorDescription(hr)); + DX::DXGIFormatToString(inputFormat), CWIN32Util::FormatHRESULT(hr)); return; } else if (!(uiFlags & D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_INPUT)) @@ -498,7 +500,7 @@ ComPtr<ID3D11VideoProcessor> CEnumeratorHD::CreateVideoProcessor(UINT RateConver if (FAILED(hr)) { CLog::LogF(LOGDEBUG, "failed creating video processor with error {}.", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return {}; } @@ -521,7 +523,7 @@ ComPtr<ID3D11VideoProcessorInputView> CEnumeratorHD::CreateVideoProcessorInputVi if (S_OK != hr) CLog::LogF(FAILED(hr) ? LOGERROR : LOGWARNING, "CreateVideoProcessorInputView returned {}.", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return inputView; } @@ -541,7 +543,7 @@ ComPtr<ID3D11VideoProcessorOutputView> CEnumeratorHD::CreateVideoProcessorOutput outputView.GetAddressOf()); if (S_OK != hr) CLog::LogF(FAILED(hr) ? LOGERROR : LOGWARNING, "CreateVideoProcessorOutputView returned {}.", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return outputView; } diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.cpp index 8147222f65bf8..7c746cc6fb643 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DXVAHD.cpp @@ -21,6 +21,8 @@ #include "utils/StringUtils.h" #include "utils/log.h" +#include "platform/win32/WIN32Util.h" + #include <mutex> #include <Windows.h> @@ -97,13 +99,13 @@ bool CProcessorHD::InitProcessor() if (FAILED(hr = pD3DDeviceContext.As(&m_pVideoContext))) { CLog::LogF(LOGWARNING, "video context initialization is failed. Error {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return false; } if (FAILED(hr = pD3DDevice.As(&m_pVideoDevice))) { CLog::LogF(LOGWARNING, "video device initialization is failed. Error {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return false; } @@ -515,7 +517,7 @@ bool CProcessorHD::Render(CRect src, CRect dst, ID3D11Resource* target, CRenderB { CLog::LogF(FAILED(hr) ? LOGERROR : LOGWARNING, "VideoProcessorBlt returned {} while VideoProcessorBlt execution.", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); } } @@ -580,7 +582,7 @@ void CProcessorHD::EnableIntelVideoSuperResolution() if (FAILED(hr)) { CLog::LogF(LOGWARNING, "Failed to set the Intel VPE version with error {}.", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return; } @@ -592,7 +594,7 @@ void CProcessorHD::EnableIntelVideoSuperResolution() if (FAILED(hr)) { CLog::LogF(LOGWARNING, "Failed to set the Intel VPE mode with error {}.", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return; } @@ -604,7 +606,7 @@ void CProcessorHD::EnableIntelVideoSuperResolution() if (FAILED(hr)) { CLog::LogF(LOGWARNING, "Failed to set the Intel VPE scaling type with error {}.", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return; } @@ -628,7 +630,7 @@ void CProcessorHD::EnableNvidiaRTXVideoSuperResolution() if (FAILED(hr)) { CLog::LogF(LOGWARNING, "Failed to set the NVIDIA video process stream extension with error {}.", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return; } diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureDX.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureDX.cpp index 81d2260f1a5ed..2c701d9f90b1a 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureDX.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderCaptureDX.cpp @@ -13,6 +13,8 @@ #include "rendering/dx/RenderContext.h" #include "utils/log.h" +#include "platform/win32/WIN32Util.h" + extern "C" { #include <libavutil/mem.h> @@ -92,7 +94,7 @@ void CRenderCaptureDX::BeginRender() result = pDevice->CreateQuery(&queryDesc, m_query.ReleaseAndGetAddressOf()); if (FAILED(result)) { - CLog::LogF(LOGERROR, "CreateQuery failed {}", DX::GetErrorDescription(result)); + CLog::LogF(LOGERROR, "CreateQuery failed {}", CWIN32Util::FormatHRESULT(result)); m_asyncSupported = false; m_query = nullptr; } diff --git a/xbmc/platform/win32/WIN32Util.cpp b/xbmc/platform/win32/WIN32Util.cpp index b5c42a1d82d7c..2571bf6c9dd04 100644 --- a/xbmc/platform/win32/WIN32Util.cpp +++ b/xbmc/platform/win32/WIN32Util.cpp @@ -32,6 +32,7 @@ #include <cassert> #endif #include <array> +#include <format> #include <locale.h> #include <sstream> @@ -1861,3 +1862,19 @@ bool CWIN32Util::IsDriverVersionAtLeast(const std::string& version1, const std:: } return true; } + +std::string CWIN32Util::FormatHRESULT(HRESULT hr) +{ + using namespace KODI::PLATFORM::WINDOWS; + + const std::string code = FromW(DXGetErrorStringW(hr)); + WCHAR buff[2048]; + DXGetErrorDescriptionW(hr, buff, 2048); + + // Remove trailing \r\n of system messages + size_t buffLength{wcslen(buff)}; + if ((buffLength) > 2 && buff[buffLength - 2] == L'\r' && buff[buffLength - 1] == L'\n') + buff[buffLength - 2] = 0; + + return std::format("0x{:X} {} ({})", static_cast<uint32_t>(hr), code, FromW(buff)); +} diff --git a/xbmc/platform/win32/WIN32Util.h b/xbmc/platform/win32/WIN32Util.h index 5e948a39aff34..750ce317c21c9 100644 --- a/xbmc/platform/win32/WIN32Util.h +++ b/xbmc/platform/win32/WIN32Util.h @@ -104,6 +104,12 @@ class CWIN32Util * Undefined results when the strings are not formatted properly. */ static bool IsDriverVersionAtLeast(const std::string& version1, const std::string& version2); + /*! + * \brief Format a Windows HRESULT value into a string for logging + * \param hr The error code + * \return Formatted string + */ + static std::string FormatHRESULT(HRESULT hr); private: static HDR_STATUS GetWindowsHDRStatusWin32(); diff --git a/xbmc/platform/win32/WinRtUtil.cpp b/xbmc/platform/win32/WinRtUtil.cpp index ce239a722ecab..f7ce85d908a4f 100644 --- a/xbmc/platform/win32/WinRtUtil.cpp +++ b/xbmc/platform/win32/WinRtUtil.cpp @@ -8,10 +8,11 @@ #include "WinRtUtil.h" -#include "rendering/dx/DirectXHelper.h" #include "utils/SystemInfo.h" #include "utils/log.h" +#include "platform/win32/WIN32Util.h" + #ifdef TARGET_WINDOWS_DESKTOP #include <Windows.Graphics.Display.Interop.h> @@ -92,7 +93,7 @@ HDR_STATUS CWinRtUtil::GetWindowsHDRStatus() if (FALSE == GetWindowRect(g_hWnd, &rect)) { CLog::LogF(LOGERROR, "unable to retrieve window rect, error {}", - DX::GetErrorDescription(GetLastError())); + CWIN32Util::FormatHRESULT(GetLastError())); return HDR_STATUS::HDR_UNKNOWN; } @@ -122,7 +123,7 @@ HDR_STATUS CWinRtUtil::GetWindowsHDRStatus() if (FAILED(hr)) { CLog::LogF(LOGERROR, "unable to retrieve DisplayInformation for window, error {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); return HDR_STATUS::HDR_UNKNOWN; } #endif diff --git a/xbmc/rendering/dx/DeviceResources.cpp b/xbmc/rendering/dx/DeviceResources.cpp index 8fe7d5e412d7c..4ba3948fe389e 100644 --- a/xbmc/rendering/dx/DeviceResources.cpp +++ b/xbmc/rendering/dx/DeviceResources.cpp @@ -14,7 +14,6 @@ #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" #include "messaging/ApplicationMessenger.h" -#include "rendering/dx/DirectXHelper.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/SystemInfo.h" @@ -53,7 +52,7 @@ namespace winrt #endif #define LOG_HR(hr) \ CLog::LogF(LOGERROR, "function call at line {} ends with error: {}", __LINE__, \ - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); #define CHECK_ERR() if (FAILED(hr)) { LOG_HR(hr); breakOnDebug; return; } #define RETURN_ERR(ret) if (FAILED(hr)) { LOG_HR(hr); breakOnDebug; return (##ret); } @@ -398,7 +397,7 @@ void DX::DeviceResources::CreateDeviceResources() if (FAILED(hr)) { CLog::LogF(LOGERROR, "unable to create hardware device with video support, error {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); CLog::LogF(LOGERROR, "trying to create hardware device without video support."); creationFlags &= ~D3D11_CREATE_DEVICE_VIDEO_SUPPORT; @@ -410,7 +409,7 @@ void DX::DeviceResources::CreateDeviceResources() if (FAILED(hr)) { CLog::LogF(LOGERROR, "unable to create hardware device, error {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); CLog::LogF(LOGERROR, "trying to create WARP device."); hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, creationFlags, @@ -420,7 +419,7 @@ void DX::DeviceResources::CreateDeviceResources() if (FAILED(hr)) { CLog::LogF(LOGFATAL, "unable to create WARP device. Rendering is not possible. Error {}", - DX::GetErrorDescription(hr)); + CWIN32Util::FormatHRESULT(hr)); CHECK_ERR(); } } @@ -1478,7 +1477,8 @@ std::vector<DXGI_COLOR_SPACE_TYPE> DX::DeviceResources::GetSwapChainColorSpaces( } else { - CLog::LogF(LOGDEBUG, "IDXGISwapChain3 is not available. Error {}", DX::GetErrorDescription(hr)); + CLog::LogF(LOGDEBUG, "IDXGISwapChain3 is not available. Error {}", + CWIN32Util::FormatHRESULT(hr)); } return result; } diff --git a/xbmc/rendering/dx/DirectXHelper.h b/xbmc/rendering/dx/DirectXHelper.h index 368bfcbad9e21..41dd581e08769 100644 --- a/xbmc/rendering/dx/DirectXHelper.h +++ b/xbmc/rendering/dx/DirectXHelper.h @@ -84,16 +84,6 @@ namespace DX *den = 1000 + i; } - inline std::string GetErrorDescription(HRESULT hr) - { - using namespace KODI::PLATFORM::WINDOWS; - - WCHAR buff[2048]; - DXGetErrorDescriptionW(hr, buff, 2048); - - return FromW(StringUtils::Format(L"{:X} - {} ({})", hr, DXGetErrorStringW(hr), buff)); - } - inline std::string GetFeatureLevelDescription(D3D_FEATURE_LEVEL featureLevel) { uint32_t fl_major = (featureLevel & 0xF000u) >> 12; diff --git a/xbmc/windowing/windows/VideoSyncD3D.cpp b/xbmc/windowing/windows/VideoSyncD3D.cpp index 084c34e0e0383..d98ea7cc7cee3 100644 --- a/xbmc/windowing/windows/VideoSyncD3D.cpp +++ b/xbmc/windowing/windows/VideoSyncD3D.cpp @@ -18,6 +18,8 @@ #include "utils/log.h" #include "windowing/GraphicContext.h" +#include "platform/win32/WIN32Util.h" + #include <mutex> #ifdef TARGET_WINDOWS_STORE @@ -97,7 +99,7 @@ void CVideoSyncD3D::Run(CEvent& stopEvent) CLog::LogF(LOGWARNING, "failed to detect vblank - screen asleep?"); if (!SUCCEEDED(hr)) - CLog::LogF(LOGERROR, "error waiting for vblank, {}", DX::GetErrorDescription(hr)); + CLog::LogF(LOGERROR, "error waiting for vblank, {}", CWIN32Util::FormatHRESULT(hr)); validVBlank = false; From 873c0a086a867ef5fd07ebe0798b9e7af173af8e Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Sat, 19 Oct 2024 14:48:07 -0400 Subject: [PATCH 613/651] [Windows] Remove redundant DirectSound error codes --- .../AudioEngine/Sinks/AESinkDirectSound.cpp | 65 +++++-------------- 1 file changed, 17 insertions(+), 48 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp index 43216796878e5..37661310cb31a 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkDirectSound.cpp @@ -19,6 +19,7 @@ #include "utils/log.h" #include "platform/win32/CharsetConverter.h" +#include "platform/win32/WIN32Util.h" #include <algorithm> #include <list> @@ -42,11 +43,10 @@ extern HWND g_hWnd; DEFINE_GUID( _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, WAVE_FORMAT_IEEE_FLOAT, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); DEFINE_GUID( _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF, WAVE_FORMAT_DOLBY_AC3_SPDIF, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); -extern const char *WASAPIErrToStr(HRESULT err); #define EXIT_ON_FAILURE(hr, reason) \ if (FAILED(hr)) \ { \ - CLog::LogF(LOGERROR, reason " - HRESULT = {} ErrorMessage = {}", hr, WASAPIErrToStr(hr)); \ + CLog::LogF(LOGERROR, reason " - error {}", hr, CWIN32Util::FormatHRESULT(hr)); \ goto failed; \ } @@ -175,13 +175,13 @@ bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device) CLog::LogF( LOGERROR, "Failed to create the DirectSound device {} with error {}, trying the default device.", - deviceFriendlyName, dserr2str(hr)); + deviceFriendlyName, CWIN32Util::FormatHRESULT(hr)); hr = DirectSoundCreate(nullptr, m_pDSound.ReleaseAndGetAddressOf(), nullptr); if (FAILED(hr)) { CLog::LogF(LOGERROR, "Failed to create the default DirectSound device with error {}.", - dserr2str(hr)); + CWIN32Util::FormatHRESULT(hr)); return false; } } @@ -194,8 +194,8 @@ bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device) if (FAILED(hr)) { - CLog::LogF(LOGERROR, "Failed to create the DirectSound device cooperative level."); - CLog::LogF(LOGERROR, "DSErr: {}", dserr2str(hr)); + CLog::LogF(LOGERROR, "Failed to create the DirectSound device cooperative level with error {}.", + CWIN32Util::FormatHRESULT(hr)); m_pDSound = nullptr; return false; } @@ -259,7 +259,7 @@ bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device) if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE) { CLog::LogF(LOGDEBUG, "Couldn't create secondary buffer ({}). Trying without LOCHARDWARE.", - dserr2str(res)); + CWIN32Util::FormatHRESULT(res)); // Try without DSBCAPS_LOCHARDWARE dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE; res = m_pDSound->CreateSoundBuffer(&dsbdesc, m_pBuffer.ReleaseAndGetAddressOf(), nullptr); @@ -267,7 +267,7 @@ bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device) if (res != DS_OK) { m_pBuffer = nullptr; - CLog::LogF(LOGERROR, "cannot create secondary buffer ({})", dserr2str(res)); + CLog::LogF(LOGERROR, "cannot create secondary buffer ({})", CWIN32Util::FormatHRESULT(res)); return false; } } @@ -554,7 +554,7 @@ void CAESinkDirectSound::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bo } else { - CLog::LogF(LOGERROR, "Getting DeviceFormat failed ({})", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "Getting DeviceFormat failed ({})", CWIN32Util::FormatHRESULT(hr)); } deviceInfo.m_deviceName = strDevName; @@ -581,7 +581,8 @@ void CAESinkDirectSound::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bo failed: if (FAILED(hr)) - CLog::LogF(LOGERROR, "Failed to enumerate WASAPI endpoint devices ({}).", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "Failed to enumerate WASAPI endpoint devices ({}).", + CWIN32Util::FormatHRESULT(hr)); } /////////////////////////////////////////////////////////////////////////////// @@ -595,12 +596,14 @@ void CAESinkDirectSound::CheckPlayStatus() return; } - if (!(status & DSBSTATUS_PLAYING) && m_CacheLen != 0) // If we have some data, see if we can start playback + if (!(status & DSBSTATUS_PLAYING) && + m_CacheLen != 0) // If we have some data, see if we can start playback { HRESULT hr = m_pBuffer->Play(0, 0, DSBPLAY_LOOPING); CLog::LogF(LOGDEBUG, "Resuming Playback"); if (FAILED(hr)) - CLog::LogF(LOGERROR, "Failed to play the DirectSound buffer: {}", dserr2str(hr)); + CLog::LogF(LOGERROR, "Failed to play the DirectSound buffer: {}", + CWIN32Util::FormatHRESULT(hr)); } } @@ -612,9 +615,8 @@ bool CAESinkDirectSound::UpdateCacheStatus() HRESULT res = m_pBuffer->GetCurrentPosition(&playCursor, &writeCursor); // Get the current playback and safe write positions if (DS_OK != res) { - CLog::LogF(LOGERROR, - "GetCurrentPosition failed. Unable to determine buffer status. HRESULT = {:#08x}", - res); + CLog::LogF(LOGERROR, "GetCurrentPosition failed. Unable to determine buffer status ({})", + CWIN32Util::FormatHRESULT(res)); m_isDirtyDS = true; return false; } @@ -706,39 +708,6 @@ DWORD CAESinkDirectSound::SpeakerMaskFromAEChannels(const CAEChannelInfo &channe return mask; } -const char *CAESinkDirectSound::dserr2str(int err) -{ - switch (err) - { - case DS_OK: return "DS_OK"; - case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION"; - case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION"; - case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL"; - case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM"; - case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL"; - case DSERR_GENERIC: return "DSERR_GENERIC"; - case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED"; - case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY"; - case DSERR_BADFORMAT: return "DSERR_BADFORMAT"; - case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED"; - case DSERR_NODRIVER: return "DSERR_NODRIVER"; - case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED"; - case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION"; - case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST"; - case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO"; - case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED"; - case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE"; - case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED"; - case DSERR_BUFFERTOOSMALL: return "DSERR_BUFFERTOOSMALL"; - case DSERR_DS8_REQUIRED: return "DSERR_DS8_REQUIRED"; - case DSERR_SENDLOOP: return "DSERR_SENDLOOP"; - case DSERR_BADSENDBUFFERGUID: return "DSERR_BADSENDBUFFERGUID"; - case DSERR_OBJECTNOTFOUND: return "DSERR_OBJECTNOTFOUND"; - case DSERR_FXUNAVAILABLE: return "DSERR_FXUNAVAILABLE"; - default: return "unknown"; - } -} - std::string CAESinkDirectSound::GetDefaultDevice() { HRESULT hr; From 62f51aa6fc4178aa3548a3d670cffce3093b562b Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Sat, 19 Oct 2024 15:03:31 -0400 Subject: [PATCH 614/651] [Windows] Fix XAudio2 error reporting The XAudio2 error codes are defined in dxerr.cpp --- xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp index 792c8ffc6a501..30b7a931dbdda 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp @@ -17,6 +17,7 @@ #include "utils/log.h" #include "platform/win32/CharsetConverter.h" +#include "platform/win32/WIN32Util.h" #include <algorithm> #include <stdint.h> @@ -71,8 +72,6 @@ HRESULT KXAudio2Create(IXAudio2** ppXAudio2, } // namespace -extern const char* WASAPIErrToStr(HRESULT err); - template <class TVoice> inline void SafeDestroyVoice(TVoice **ppVoice) { @@ -252,14 +251,14 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi hr = m_sourceVoice->SubmitSourceBuffer(&xbuffer); if (FAILED(hr)) { - CLog::LogF(LOGERROR, "voice submit buffer failed due to {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "voice submit buffer failed due to {}", CWIN32Util::FormatHRESULT(hr)); delete xbuffer.pContext; return 0; } hr = m_sourceVoice->Start(0, XAUDIO2_COMMIT_NOW); if (FAILED(hr)) { - CLog::LogF(LOGERROR, "voice start failed due to {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "voice start failed due to {}", CWIN32Util::FormatHRESULT(hr)); m_isDirty = true; //flag new device or re-init needed delete xbuffer.pContext; return INT_MAX; @@ -303,7 +302,7 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi hr = m_sourceVoice->SubmitSourceBuffer(&xbuffer); if (FAILED(hr)) { - CLog::LogF(LOGERROR, "submiting buffer failed due to {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "submiting buffer failed due to {}", CWIN32Util::FormatHRESULT(hr)); delete xbuffer.pContext; return INT_MAX; } @@ -335,7 +334,7 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo if (FAILED(hr)) { CLog::LogF(LOGERROR, "failed to activate XAudio for capability testing ({})", - WASAPIErrToStr(hr)); + CWIN32Util::FormatHRESULT(hr)); return; } @@ -534,7 +533,7 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form if (FAILED(hr) || !pMasterVoice) { CLog::LogF(LOGINFO, "Could not retrieve the default XAudio audio endpoint ({}).", - WASAPIErrToStr(hr)); + CWIN32Util::FormatHRESULT(hr)); return false; } } @@ -558,7 +557,7 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form if (CServiceBroker::GetLogging().CanLogComponent(LOGAUDIO)) CLog::LogFC(LOGDEBUG, LOGAUDIO, "CreateSourceVoice failed ({}) - trying to find a compatible format", - WASAPIErrToStr(hr)); + CWIN32Util::FormatHRESULT(hr)); requestedChannels = wfxex.Format.nChannels; @@ -621,7 +620,7 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form } if (FAILED(hr)) - CLog::LogF(LOGERROR, "creating voices failed ({})", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "creating voices failed ({})", CWIN32Util::FormatHRESULT(hr)); } if (closestMatch >= 0) @@ -683,7 +682,7 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form hr = m_sourceVoice->Start(0, XAUDIO2_COMMIT_NOW); if (FAILED(hr)) { - CLog::LogF(LOGERROR, "Voice start failed : {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "Voice start failed : {}", CWIN32Util::FormatHRESULT(hr)); CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec); CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat)); CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample); @@ -702,7 +701,7 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form m_xAudio2->GetPerformanceData(&perfData); if (!perfData.TotalSourceVoiceCount) { - CLog::LogF(LOGERROR, "GetPerformanceData Failed : {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "GetPerformanceData Failed : {}", CWIN32Util::FormatHRESULT(hr)); return false; } From 0bca3726ef882b7c431bed34c6d6f5f0ddecf857 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Sat, 19 Oct 2024 15:45:43 -0400 Subject: [PATCH 615/651] [Windows] Merge WASAPI error reporting into generic code Also add missing codes and some descriptions based on MS documentation --- xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp | 34 +++---- .../Sinks/windows/AESinkFactoryWin.cpp | 41 --------- .../Sinks/windows/AESinkFactoryWin32.cpp | 4 +- xbmc/platform/win32/dxerr.cpp | 92 +++++++++++++++++++ 4 files changed, 112 insertions(+), 59 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp index 02357c123d5e4..cb9f8d3bb0c1e 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp @@ -17,6 +17,8 @@ #include "utils/XTimeUtils.h" #include "utils/log.h" +#include "platform/win32/WIN32Util.h" + #include <algorithm> #include <stdint.h> @@ -32,11 +34,10 @@ const IID IID_IAudioClock = __uuidof(IAudioClock); DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); DEFINE_PROPERTYKEY(PKEY_Device_EnumeratorName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 24); -extern const char *WASAPIErrToStr(HRESULT err); #define EXIT_ON_FAILURE(hr, reason) \ if (FAILED(hr)) \ { \ - CLog::LogF(LOGERROR, reason " - HRESULT = {} ErrorMessage = {}", hr, WASAPIErrToStr(hr)); \ + CLog::LogF(LOGERROR, reason " - error {}", hr, CWIN32Util::FormatHRESULT(hr)); \ goto failed; \ } @@ -279,14 +280,14 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t **data, unsigned int frames, unsi hr = m_pAudioClient->Reset(); if (FAILED(hr)) { - CLog::LogF(LOGERROR, " AudioClient reset failed due to {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, " AudioClient reset failed due to {}", CWIN32Util::FormatHRESULT(hr)); return 0; } hr = m_pRenderClient->GetBuffer(NumFramesRequested, &buf); if (FAILED(hr)) { #ifdef _DEBUG - CLog::LogF(LOGERROR, "GetBuffer failed due to {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "GetBuffer failed due to {}", CWIN32Util::FormatHRESULT(hr)); #endif m_isDirty = true; //flag new device or re-init needed return INT_MAX; @@ -297,7 +298,7 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t **data, unsigned int frames, unsi if (FAILED(hr)) { #ifdef _DEBUG - CLog::LogF(LOGDEBUG, "ReleaseBuffer failed due to {}.", WASAPIErrToStr(hr)); + CLog::LogF(LOGDEBUG, "ReleaseBuffer failed due to {}.", CWIN32Util::FormatHRESULT(hr)); #endif m_isDirty = true; //flag new device or re-init needed return INT_MAX; @@ -347,7 +348,7 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t **data, unsigned int frames, unsi if (FAILED(hr)) { #ifdef _DEBUG - CLog::LogF(LOGERROR, "GetBuffer failed due to {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "GetBuffer failed due to {}", CWIN32Util::FormatHRESULT(hr)); #endif return INT_MAX; } @@ -361,7 +362,7 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t **data, unsigned int frames, unsi if (FAILED(hr)) { #ifdef _DEBUG - CLog::LogF(LOGDEBUG, "ReleaseBuffer failed due to {}.", WASAPIErrToStr(hr)); + CLog::LogF(LOGDEBUG, "ReleaseBuffer failed due to {}.", CWIN32Util::FormatHRESULT(hr)); #endif return INT_MAX; } @@ -673,7 +674,8 @@ void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo failed: if (FAILED(hr)) - CLog::LogF(LOGERROR, "Failed to enumerate WASAPI endpoint devices ({}).", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "Failed to enumerate WASAPI endpoint devices ({}).", + CWIN32Util::FormatHRESULT(hr)); } //Private utility functions//////////////////////////////////////////////////// @@ -755,7 +757,7 @@ bool CAESinkWASAPI::InitializeExclusive(AEAudioFormat &format) } else if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT) //It failed for a reason unrelated to an unsupported format. { - CLog::LogF(LOGERROR, "IsFormatSupported failed ({})", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "IsFormatSupported failed ({})", CWIN32Util::FormatHRESULT(hr)); return false; } else if (format.m_dataFormat == AE_FMT_RAW) //No sense in trying other formats for passthrough. @@ -848,7 +850,7 @@ bool CAESinkWASAPI::InitializeExclusive(AEAudioFormat &format) closestMatch = i; } else if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT) - CLog::LogF(LOGERROR, "IsFormatSupported failed ({})", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "IsFormatSupported failed ({})", CWIN32Util::FormatHRESULT(hr)); } if (closestMatch >= 0) @@ -913,7 +915,7 @@ bool CAESinkWASAPI::InitializeExclusive(AEAudioFormat &format) : AudioCategory_ForegroundOnlyMedia; if (FAILED(hr = audioClient2->SetClientProperties(&props))) - CLog::LogF(LOGERROR, "unable to set audio category, {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "unable to set audio category, {}", CWIN32Util::FormatHRESULT(hr)); } REFERENCE_TIME audioSinkBufferDurationMsec, hnsLatency; @@ -939,7 +941,7 @@ bool CAESinkWASAPI::InitializeExclusive(AEAudioFormat &format) hr = m_pAudioClient->GetBufferSize(&m_uiBufferLen); if (FAILED(hr)) { - CLog::LogF(LOGERROR, "GetBufferSize Failed : {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "GetBufferSize Failed : {}", CWIN32Util::FormatHRESULT(hr)); return false; } @@ -950,7 +952,7 @@ bool CAESinkWASAPI::InitializeExclusive(AEAudioFormat &format) hr = m_pDevice->Activate(m_pAudioClient.ReleaseAndGetAddressOf()); if (FAILED(hr)) { - CLog::LogF(LOGERROR, "Device Activation Failed : {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "Device Activation Failed : {}", CWIN32Util::FormatHRESULT(hr)); return false; } @@ -960,8 +962,8 @@ bool CAESinkWASAPI::InitializeExclusive(AEAudioFormat &format) } if (FAILED(hr)) { - CLog::LogF(LOGERROR, "Failed to initialize WASAPI in exclusive mode {} - ({}).", HRESULT(hr), - WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "Failed to initialize WASAPI in exclusive mode - {}.", + CWIN32Util::FormatHRESULT(hr)); CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec); CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat)); CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample); @@ -987,7 +989,7 @@ bool CAESinkWASAPI::InitializeExclusive(AEAudioFormat &format) hr = m_pAudioClient->GetStreamLatency(&hnsLatency); if (FAILED(hr)) { - CLog::LogF(LOGERROR, "GetStreamLatency Failed : {}", WASAPIErrToStr(hr)); + CLog::LogF(LOGERROR, "GetStreamLatency Failed : {}", CWIN32Util::FormatHRESULT(hr)); return false; } diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.cpp index 7f6e4f9162c42..f7f514589127a 100644 --- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.cpp +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.cpp @@ -7,47 +7,6 @@ */ #include "AESinkFactoryWin.h" -#include "utils/log.h" - -#define ERRTOSTR(err) case err: return #err - -const char *WASAPIErrToStr(HRESULT err) -{ - switch (err) - { - ERRTOSTR(AUDCLNT_E_NOT_INITIALIZED); - ERRTOSTR(AUDCLNT_E_ALREADY_INITIALIZED); - ERRTOSTR(AUDCLNT_E_WRONG_ENDPOINT_TYPE); - ERRTOSTR(AUDCLNT_E_DEVICE_INVALIDATED); - ERRTOSTR(AUDCLNT_E_NOT_STOPPED); - ERRTOSTR(AUDCLNT_E_BUFFER_TOO_LARGE); - ERRTOSTR(AUDCLNT_E_OUT_OF_ORDER); - ERRTOSTR(AUDCLNT_E_UNSUPPORTED_FORMAT); - ERRTOSTR(AUDCLNT_E_INVALID_SIZE); - ERRTOSTR(AUDCLNT_E_DEVICE_IN_USE); - ERRTOSTR(AUDCLNT_E_BUFFER_OPERATION_PENDING); - ERRTOSTR(AUDCLNT_E_THREAD_NOT_REGISTERED); - ERRTOSTR(AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED); - ERRTOSTR(AUDCLNT_E_ENDPOINT_CREATE_FAILED); - ERRTOSTR(AUDCLNT_E_SERVICE_NOT_RUNNING); - ERRTOSTR(AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED); - ERRTOSTR(AUDCLNT_E_EXCLUSIVE_MODE_ONLY); - ERRTOSTR(AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL); - ERRTOSTR(AUDCLNT_E_EVENTHANDLE_NOT_SET); - ERRTOSTR(AUDCLNT_E_INCORRECT_BUFFER_SIZE); - ERRTOSTR(AUDCLNT_E_BUFFER_SIZE_ERROR); - ERRTOSTR(AUDCLNT_E_CPUUSAGE_EXCEEDED); - ERRTOSTR(AUDCLNT_E_BUFFER_ERROR); - ERRTOSTR(AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED); - ERRTOSTR(AUDCLNT_E_INVALID_DEVICE_PERIOD); - ERRTOSTR(E_POINTER); - ERRTOSTR(E_INVALIDARG); - ERRTOSTR(E_OUTOFMEMORY); - default: break; - } - return "Undefined"; -} - void CAESinkFactoryWin::AEChannelsFromSpeakerMask(CAEChannelInfo& channelLayout, DWORD speakers) { channelLayout.Reset(); diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp index d5869c5e2e312..9c195dc0b2eb6 100644 --- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp @@ -10,6 +10,7 @@ #include "utils/log.h" #include "platform/win32/CharsetConverter.h" +#include "platform/win32/WIN32Util.h" #include <algorithm> @@ -23,11 +24,10 @@ const IID IID_IAudioClient = __uuidof(IAudioClient); DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); DEFINE_PROPERTYKEY(PKEY_Device_EnumeratorName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 24); -extern const char *WASAPIErrToStr(HRESULT err); #define EXIT_ON_FAILURE(hr, reason) \ if (FAILED(hr)) \ { \ - CLog::LogF(LOGERROR, reason " - HRESULT = {} ErrorMessage = {}", hr, WASAPIErrToStr(hr)); \ + CLog::LogF(LOGERROR, reason " - {}", hr, CWIN32Util::FormatHRESULT(hr)); \ goto failed; \ } diff --git a/xbmc/platform/win32/dxerr.cpp b/xbmc/platform/win32/dxerr.cpp index f3c74406e8038..a3e80859c5396 100644 --- a/xbmc/platform/win32/dxerr.cpp +++ b/xbmc/platform/win32/dxerr.cpp @@ -40,6 +40,8 @@ #include <dwrite.h> #endif +#include <Audioclient.h> + #define XAUDIO2_E_INVALID_CALL 0x88960001 #define XAUDIO2_E_XMA_DECODER_ERROR 0x88960002 #define XAUDIO2_E_XAPO_CREATION_FAILED 0x88960003 @@ -3329,6 +3331,51 @@ const WCHAR* WINAPI DXGetErrorStringW( _In_ HRESULT hr ) // ------------------------------------------------------------- CHK_ERRA(XAPO_E_FORMAT_UNSUPPORTED) +// ------------------------------------------------------------- +// Audioclient.h error codes - WASAPI +// ------------------------------------------------------------- + CHK_ERRA(AUDCLNT_E_NOT_INITIALIZED) + CHK_ERRA(AUDCLNT_E_ALREADY_INITIALIZED) + CHK_ERRA(AUDCLNT_E_WRONG_ENDPOINT_TYPE) + CHK_ERRA(AUDCLNT_E_DEVICE_INVALIDATED) + CHK_ERRA(AUDCLNT_E_NOT_STOPPED) + CHK_ERRA(AUDCLNT_E_BUFFER_TOO_LARGE) + CHK_ERRA(AUDCLNT_E_OUT_OF_ORDER) + CHK_ERRA(AUDCLNT_E_UNSUPPORTED_FORMAT) + CHK_ERRA(AUDCLNT_E_INVALID_SIZE) + CHK_ERRA(AUDCLNT_E_DEVICE_IN_USE) + CHK_ERRA(AUDCLNT_E_BUFFER_OPERATION_PENDING) + CHK_ERRA(AUDCLNT_E_THREAD_NOT_REGISTERED) + CHK_ERRA(AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED) + CHK_ERRA(AUDCLNT_E_ENDPOINT_CREATE_FAILED) + CHK_ERRA(AUDCLNT_E_SERVICE_NOT_RUNNING) + CHK_ERRA(AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED) + CHK_ERRA(AUDCLNT_E_EXCLUSIVE_MODE_ONLY) + CHK_ERRA(AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL) + CHK_ERRA(AUDCLNT_E_EVENTHANDLE_NOT_SET) + CHK_ERRA(AUDCLNT_E_INCORRECT_BUFFER_SIZE) + CHK_ERRA(AUDCLNT_E_BUFFER_SIZE_ERROR) + CHK_ERRA(AUDCLNT_E_CPUUSAGE_EXCEEDED) + CHK_ERRA(AUDCLNT_E_BUFFER_ERROR) + CHK_ERRA(AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) + CHK_ERRA(AUDCLNT_E_INVALID_DEVICE_PERIOD) + CHK_ERRA(AUDCLNT_E_INVALID_STREAM_FLAG) + CHK_ERRA(AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE) + CHK_ERRA(AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES) + CHK_ERRA(AUDCLNT_E_OFFLOAD_MODE_ONLY) + CHK_ERRA(AUDCLNT_E_NONOFFLOAD_MODE_ONLY) + CHK_ERRA(AUDCLNT_E_RESOURCES_INVALIDATED) + CHK_ERRA(AUDCLNT_E_RAW_MODE_UNSUPPORTED) + CHK_ERRA(AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) + CHK_ERRA(AUDCLNT_E_ENGINE_FORMAT_LOCKED) + CHK_ERRA(AUDCLNT_E_HEADTRACKING_ENABLED) + CHK_ERRA(AUDCLNT_E_HEADTRACKING_UNSUPPORTED) + CHK_ERRA(AUDCLNT_E_EFFECT_NOT_AVAILABLE) + CHK_ERRA(AUDCLNT_E_EFFECT_STATE_READ_ONLY) + CHK_ERRA(AUDCLNT_S_BUFFER_EMPTY) + CHK_ERRA(AUDCLNT_S_THREAD_ALREADY_REGISTERED) + CHK_ERRA(AUDCLNT_S_POSITION_STALLED) + default: return L""; } } @@ -3694,6 +3741,51 @@ void WINAPI DXGetErrorDescriptionW( _In_ HRESULT hr, _Out_cap_(count) WCHAR* des // xapo.h error codes // ------------------------------------------------------------- CHK_ERR(XAPO_E_FORMAT_UNSUPPORTED, "Requested audio format unsupported.") + +// ------------------------------------------------------------- +// Audioclient.h error codes - WASAPI +// ------------------------------------------------------------- + CHK_ERR(AUDCLNT_E_NOT_INITIALIZED, "The audio stream has not been successfully initialized.") + CHK_ERR(AUDCLNT_E_ALREADY_INITIALIZED, "The IAudioClient object is already initialized.") + CHK_ERR(AUDCLNT_E_WRONG_ENDPOINT_TYPE, "The caller tried to access an IAudioCaptureClient interface on a rendering endpoint, or an IAudioRenderClient interface on a capture endpoint.") + CHK_ERR(AUDCLNT_E_DEVICE_INVALIDATED, "The audio endpoint device was invalidated.") + CHK_ERR(AUDCLNT_E_NOT_STOPPED, "The audio stream was not stopped at the time of the Start call.") + CHK_ERR(AUDCLNT_E_BUFFER_TOO_LARGE, "The NumFramesRequested value exceeds the available buffer space.") + CHK_ERR(AUDCLNT_E_OUT_OF_ORDER, "A previous IAudioRenderClient::GetBuffer call is still in effect or a call to IAudioRenderClient::GetBuffer is missing.") + CHK_ERR(AUDCLNT_E_UNSUPPORTED_FORMAT, "The audio engine (shared mode) or audio endpoint device (exclusive mode) does not support the specified format.") + CHK_ERR(AUDCLNT_E_INVALID_SIZE, "The NumFramesWritten value exceeds the NumFramesRequested value specified in the previous IAudioRenderClient::GetBuffer call.") + CHK_ERR(AUDCLNT_E_DEVICE_IN_USE, "The endpoint device is already in use.") + CHK_ERR(AUDCLNT_E_BUFFER_OPERATION_PENDING, "Buffer cannot be accessed because a stream reset is in progress.") +// CHK_ERRA(AUDCLNT_E_THREAD_NOT_REGISTERED) + CHK_ERR(AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED, "The user disabled exclusive-mode use of the device.") + CHK_ERR(AUDCLNT_E_ENDPOINT_CREATE_FAILED, "The method failed to create the audio endpoint for the render or the capture device.") + CHK_ERR(AUDCLNT_E_SERVICE_NOT_RUNNING, "The Windows audio service is not running.") + CHK_ERR(AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED, "The audio stream was not initialized for event-driven buffering.") +// CHK_ERRA(AUDCLNT_E_EXCLUSIVE_MODE_ONLY) + CHK_ERR(AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL, "The parameters hnsBufferDuration and hnsPeriodicity are not equal.") + CHK_ERR(AUDCLNT_E_EVENTHANDLE_NOT_SET, "The event handle was not set for an audio stream in event-driven buffering mode.") +// CHK_ERRA(AUDCLNT_E_INCORRECT_BUFFER_SIZE) + CHK_ERR(AUDCLNT_E_BUFFER_SIZE_ERROR, "The stream is exclusive mode and uses event-driven buffering, but the client attempted to get a packet that was not the size of the buffer.") + CHK_ERR(AUDCLNT_E_CPUUSAGE_EXCEEDED, "The process-pass duration exceeded the maximum allowed CPU usage.") + CHK_ERR(AUDCLNT_E_BUFFER_ERROR, "GetBuffer failed to retrieve a data buffer.") + CHK_ERR(AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED, "The requested buffer size is not aligned.") + CHK_ERR(AUDCLNT_E_INVALID_DEVICE_PERIOD, "The requested device period is not valid.") +// CHK_ERRA(AUDCLNT_E_INVALID_STREAM_FLAG) + CHK_ERR(AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE, "The endpoint does not support offload.") + CHK_ERR(AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES, "The endpoint is out of offload resources.") + CHK_ERR(AUDCLNT_E_OFFLOAD_MODE_ONLY, "The operation is only supported in offload mode.") + CHK_ERR(AUDCLNT_E_NONOFFLOAD_MODE_ONLY, "The operation is only supported in non-offload mode.") + CHK_ERR(AUDCLNT_E_RESOURCES_INVALIDATED, "A resource associated with the audio stream is no longer valid.") + CHK_ERR(AUDCLNT_E_RAW_MODE_UNSUPPORTED, "The endpoint does not support raw mode.") + CHK_ERR(AUDCLNT_E_ENGINE_PERIODICITY_LOCKED, "The periodicity of the audio engine has been locked by another client.") + CHK_ERR(AUDCLNT_E_ENGINE_FORMAT_LOCKED, "The format of the audio engine has been locked by another client.") +// CHK_ERRA(AUDCLNT_E_HEADTRACKING_ENABLED) +// CHK_ERRA(AUDCLNT_E_HEADTRACKING_UNSUPPORTED) + CHK_ERR(AUDCLNT_E_EFFECT_NOT_AVAILABLE, "The specified effect is not available.") + CHK_ERR(AUDCLNT_E_EFFECT_STATE_READ_ONLY, "The specified effect has a state that is read-only.") + CHK_ERR(AUDCLNT_S_BUFFER_EMPTY, "No capture data is available to be read.") +// CHK_ERRA(AUDCLNT_S_THREAD_ALREADY_REGISTERED) + CHK_ERR(AUDCLNT_S_POSITION_STALLED, "The IAudioClient::Start method has not been called for this stream.") } } From 4e9e4c77e139fb9e90df6e59334f5be40ff5c0cf Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Mon, 14 Oct 2024 19:49:28 -0400 Subject: [PATCH 616/651] [Windows] Additional logging in Windows Sink Factory --- .../Sinks/windows/AESinkFactoryWin32.cpp | 85 ++++++++----------- 1 file changed, 34 insertions(+), 51 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp index 9c195dc0b2eb6..b4d30af2d1db7 100644 --- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp @@ -44,20 +44,30 @@ std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails() HRESULT hr; UINT uiCount = 0; - hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf())); + hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, + reinterpret_cast<void**>(pEnumerator.GetAddressOf())); EXIT_ON_FAILURE(hr, "Could not allocate WASAPI device enumerator.") - // get the default audio endpoint - if (pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, pDefaultDevice.GetAddressOf()) == S_OK) + if (S_OK == + (hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, pDefaultDevice.GetAddressOf()))) { - if (pDefaultDevice->GetId(&pwszID) == S_OK) + if (S_OK == (hr = pDefaultDevice->GetId(&pwszID))) { wstrDDID = pwszID; CoTaskMemFree(pwszID); } + else + { + CLog::LogF(LOGERROR, "Unable to retrieve default endpoint identifier ({})", + CWIN32Util::FormatHRESULT(hr)); + } pDefaultDevice.Reset(); } + else + { + CLog::LogF(LOGERROR, "Unable to retrieve default endpoint ({})", CWIN32Util::FormatHRESULT(hr)); + } // enumerate over all audio endpoints hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, pEnumDevices.GetAddressOf()); @@ -75,56 +85,33 @@ std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails() PropVariantInit(&varName); hr = pEnumDevices->Item(i, pDevice.GetAddressOf()); - if (FAILED(hr)) - { - CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint failed."); - goto failed; - } + EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint failed.") hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.ReleaseAndGetAddressOf()); - if (FAILED(hr)) - { - CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint properties failed."); - goto failed; - } + EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint properties failed.") hr = pProperty->GetValue(PKEY_Device_FriendlyName, &varName); - if (FAILED(hr)) - { - CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint device name failed."); - goto failed; - } + EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint device name failed.") details.strDescription = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal); PropVariantClear(&varName); hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName); - if (FAILED(hr)) - { - CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint GUID failed."); - goto failed; - } + EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint GUID failed.") details.strDeviceId = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal); PropVariantClear(&varName); hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName); - if (FAILED(hr)) - { - CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint form factor failed."); - goto failed; - } + EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint form factor failed.") + details.strWinDevType = winEndpoints[(EndpointFormFactor)varName.uiVal].winEndpointType; details.eDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType; PropVariantClear(&varName); hr = pProperty->GetValue(PKEY_AudioEndpoint_PhysicalSpeakers, &varName); - if (FAILED(hr)) - { - CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint speaker layout failed."); - goto failed; - } + EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint speaker layout failed.") details.uiChannelMask = std::max(varName.uintVal, (unsigned int)KSAUDIO_SPEAKER_STEREO); PropVariantClear(&varName); @@ -142,13 +129,17 @@ std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails() } PropVariantClear(&varName); - if (pDevice->GetId(&pwszID) == S_OK) + if (S_OK == (hr = pDevice->GetId(&pwszID))) { if (wstrDDID.compare(pwszID) == 0) details.bDefault = true; CoTaskMemFree(pwszID); } + else + { + CLog::LogF(LOGERROR, "Unable to retrieve device id ({})", CWIN32Util::FormatHRESULT(hr)); + } list.push_back(details); } @@ -181,6 +172,10 @@ struct AEWASAPIDeviceWin32 : public IAEWASAPIDevice *ppAudioClient = pClient.Detach(); return hr; } + else + { + CLog::LogF(LOGERROR, "unable to activate IAudioClient ({})", CWIN32Util::FormatHRESULT(hr)); + } } catch (...) {} return hr; @@ -239,21 +234,13 @@ std::string CAESinkFactoryWin::GetDefaultDeviceId() PropVariantInit(&varName); hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf()); - if (FAILED(hr)) - { - CLog::LogF(LOGERROR, "Retrieval of WASAPI endpoint properties failed."); - goto failed; - } + EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint properties failed.") hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName); - if (FAILED(hr)) - { - CLog::LogF(LOGERROR, "Retrieval of WASAPI endpoint GUID failed."); - goto failed; - } + EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint GUID failed.") + strDeviceId = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal); PropVariantClear(&varName); - } failed: @@ -293,11 +280,7 @@ HRESULT CAESinkFactoryWin::ActivateWASAPIDevice(std::string &device, IAEWASAPIDe EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint properties failed.") hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName); - if (FAILED(hr)) - { - CLog::LogF(LOGERROR, "Retrieval of WASAPI endpoint GUID failed."); - goto failed; - } + EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint GUID failed.") std::string strDevName = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal); From 94e68183e55d7dfc29286dcbbb4ef55d7953bbb7 Mon Sep 17 00:00:00 2001 From: fuzzard <fuzzard@kodi.tv> Date: Wed, 30 Oct 2024 14:22:35 +1000 Subject: [PATCH 617/651] [cmake] FindFreeType add defacto target alias --- cmake/modules/FindFreeType.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/modules/FindFreeType.cmake b/cmake/modules/FindFreeType.cmake index 1131bd2a9dc3a..bfd7a87def7dc 100644 --- a/cmake/modules/FindFreeType.cmake +++ b/cmake/modules/FindFreeType.cmake @@ -36,6 +36,10 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES IMPORTED_LOCATION "${FREETYPE_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${FREETYPE_INCLUDE_DIR}") + + if(NOT TARGET Freetype::Freetype) + add_library(Freetype::Freetype ALIAS ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + endif() else() if(Freetype_FIND_REQUIRED) message(FATAL_ERROR "Freetype libraries were not found.") From f9f1992caa3a8db10b65f89613db3f4c0e32ac6c Mon Sep 17 00:00:00 2001 From: fuzzard <fuzzard@kodi.tv> Date: Wed, 30 Oct 2024 14:23:17 +1000 Subject: [PATCH 618/651] [cmake] FindFriBidi add defacto target alias --- cmake/modules/FindFriBidi.cmake | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cmake/modules/FindFriBidi.cmake b/cmake/modules/FindFriBidi.cmake index 6a265e1c2a3b8..52bf29b749747 100644 --- a/cmake/modules/FindFriBidi.cmake +++ b/cmake/modules/FindFriBidi.cmake @@ -32,11 +32,17 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) if(FRIBIDI_FOUND) if(TARGET PkgConfig::FRIBIDI) add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS PkgConfig::FRIBIDI) + if(NOT TARGET FriBidi::FriBidi) + add_library(FriBidi::FriBidi ALIAS PkgConfig::FRIBIDI) + endif() else() - add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) - set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES - IMPORTED_LOCATION "${FRIBIDI_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${FRIBIDI_INCLUDE_DIR}") + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION "${FRIBIDI_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FRIBIDI_INCLUDE_DIR}") + if(NOT TARGET FriBidi::FriBidi) + add_library(FriBidi::FriBidi ALIAS ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + endif() endif() else() if(FriBidi_FIND_REQUIRED) From 7d1f9a5323c9497b5e5f674addf276c029149fbe Mon Sep 17 00:00:00 2001 From: fuzzard <fuzzard@kodi.tv> Date: Wed, 30 Oct 2024 14:23:45 +1000 Subject: [PATCH 619/651] [cmake] FindHarfBuzz add defacto target alias --- cmake/modules/FindHarfBuzz.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/modules/FindHarfBuzz.cmake b/cmake/modules/FindHarfBuzz.cmake index 834012e727f3f..14ed92561b459 100644 --- a/cmake/modules/FindHarfBuzz.cmake +++ b/cmake/modules/FindHarfBuzz.cmake @@ -34,6 +34,10 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES IMPORTED_LOCATION "${HARFBUZZ_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${HARFBUZZ_INCLUDE_DIR}") + + if(NOT TARGET harfbuzz::harfbuzz) + add_library(harfbuzz::harfbuzz ALIAS ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) + endif() else() if(HarfBuzz_FIND_REQUIRED) message(FATAL_ERROR "Harfbuzz libraries were not found.") From d7c26407f43d635405e687ef9d1101a2add72ff7 Mon Sep 17 00:00:00 2001 From: fuzzard <fuzzard@kodi.tv> Date: Mon, 28 Oct 2024 18:25:28 +1000 Subject: [PATCH 620/651] [tools/depends][target] libass cleanup and win patches introduce cmake build system patches for primary windows use. However it has been tested with Darwin/Android --- tools/depends/target/Makefile | 2 +- .../target/libass/01-win-CMakeLists.patch | 410 ++++++++++ .../libass/02-win-dwrite-fontload.patch | 731 ++++++++++++++++++ ...win-dwrite-prevent-ass_set_fonts_dir.patch | 20 + .../libass/04-win-nullcheck-shared_hdc.patch | 11 + tools/depends/target/libass/LIBASS-VERSION | 4 +- tools/depends/target/libass/Makefile | 27 +- 7 files changed, 1200 insertions(+), 5 deletions(-) create mode 100644 tools/depends/target/libass/01-win-CMakeLists.patch create mode 100644 tools/depends/target/libass/02-win-dwrite-fontload.patch create mode 100644 tools/depends/target/libass/03-win-dwrite-prevent-ass_set_fonts_dir.patch create mode 100644 tools/depends/target/libass/04-win-nullcheck-shared_hdc.patch diff --git a/tools/depends/target/Makefile b/tools/depends/target/Makefile index 35140b4a58515..2ef7812a0fc73 100644 --- a/tools/depends/target/Makefile +++ b/tools/depends/target/Makefile @@ -160,7 +160,7 @@ freetype2: bzip2 harfbuzz $(ZLIB) gettext: $(ICONV) gnutls: nettle $(ZLIB) harfbuzz: freetype2-noharfbuzz $(ICONV) -libass: fontconfig fribidi harfbuzz libpng freetype2 expat $(ICONV) +libass: fontconfig fribidi harfbuzz freetype2 $(ICONV) libbluray: fontconfig freetype2 $(ICONV) udfread libxml2 libcdio-gplv3: $(ICONV) libcdio: $(ICONV) diff --git a/tools/depends/target/libass/01-win-CMakeLists.patch b/tools/depends/target/libass/01-win-CMakeLists.patch new file mode 100644 index 0000000000000..8091bbf019042 --- /dev/null +++ b/tools/depends/target/libass/01-win-CMakeLists.patch @@ -0,0 +1,410 @@ +--- /dev/null ++++ b/cmake/libass-config.cmake +@@ -0,0 +1 @@ ++include(${CMAKE_CURRENT_LIST_DIR}/libass.cmake) +--- /dev/null ++++ b/CMakeLists.txt +@@ -0,0 +1,235 @@ ++cmake_minimum_required(VERSION 3.15) ++ ++project(libass VERSION 0.17.3 LANGUAGES C) ++ ++set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) ++ ++# All platforms require ++find_package(Iconv REQUIRED) ++find_package(Freetype 2.6 REQUIRED) ++find_package(FriBidi REQUIRED) ++find_package(HarfBuzz REQUIRED) ++ ++# Font providers ++if(CMAKE_SYSTEM_NAME MATCHES Windows) ++ set(CONFIG_DIRECTWRITE 1) ++elseif(CMAKE_SYSTEM_NAME STREQUAL Darwin) ++ set(CONFIG_CORETEXT 1) ++else() ++ find_package(Fontconfig REQUIRED) ++ set(CONFIG_FONTCONFIG 1) ++endif() ++ ++# All platforms ++if(Freetype_FOUND) ++ set(CONFIG_FREETYPE 1) ++endif() ++ ++if(FriBidi_FOUND) ++ set(CONFIG_FRIBIDI 1) ++endif() ++ ++if(HarfBuzz_FOUND) ++ set(CONFIG_HARFBUZZ 1) ++endif() ++ ++if(Iconv_FOUND) ++ set(CONFIG_ICONV 1) ++endif() ++ ++# ASM - Currently disabled ++if(ARCH STREQUAL x86_64) ++ set(ARCH_X86_64 1) ++ find_program(NASM nasm HINTS "${NATIVEPREFIX}/bin") ++ ++# if(NASM_FOUND) ++# set(CONFIG_ASM 1) ++# endif() ++elseif(ARCH STREQUAL aarch64) ++ set(ARCH_AARCH64 1) ++# set(CONFIG_ASM 1) ++endif() ++ ++set(VCS_TAG "") ++ ++include(CheckFunctionExists) ++include(CheckIncludeFiles) ++ ++CHECK_FUNCTION_EXISTS (strdup HAVE_STRDUP) ++CHECK_FUNCTION_EXISTS (strndup HAVE_STRNDUP) ++ ++CHECK_INCLUDE_FILES(dlfcn.h HAVE_DLFCN_H) ++CHECK_INCLUDE_FILES(iconv.h HAVE_ICONV_H) ++CHECK_INCLUDE_FILES(inttypes.h HAVE_INTTYPES_H) ++CHECK_INCLUDE_FILES(stdint.h HAVE_STDINT_H) ++CHECK_INCLUDE_FILES(stdio.h HAVE_STDIO_H) ++CHECK_INCLUDE_FILES(stdlib.h HAVE_STDLIB_H) ++CHECK_INCLUDE_FILES(strings.h HAVE_STRINGS_H) ++CHECK_INCLUDE_FILES(string.h HAVE_STRING_H) ++CHECK_INCLUDE_FILES(sys/stat.h HAVE_SYS_STAT_H) ++CHECK_INCLUDE_FILES(sys/types.h HAVE_SYS_TYPES_H) ++CHECK_INCLUDE_FILES(unistd.h HAVE_UNISTD_H) ++ ++set(PACKAGE_NAME ${CMAKE_PROJECT_NAME}) ++set(PACKAGE ${CMAKE_PROJECT_NAME}) ++set(PACKAGE_VERSION ${CMAKE_PROJECT_VERSION}) ++set(VERSION ${CMAKE_PROJECT_VERSION}) ++ ++set(CONFIG_SOURCEVERSION \"\") ++ ++configure_file(${CMAKE_SOURCE_DIR}/libass/config.h.in ${CMAKE_SOURCE_DIR}/libass/config.h) ++ ++if(MSVC) ++ set(CMAKE_DEBUG_POSTFIX "d") ++endif() ++ ++add_library(libass ++ libass/ass_bitmap.c ++ libass/ass_bitmap.h ++ libass/ass_bitmap_engine.c ++ libass/ass_bitmap_engine.h ++ libass/ass_blur.c ++ libass/ass_cache.c ++ libass/ass_cache.h ++ libass/ass_cache_template.h ++ libass/ass_compat.h ++ libass/ass_drawing.c ++ libass/ass_drawing.h ++ libass/ass_filesystem.h ++ libass/ass_filesystem.c ++ libass/ass_font.c ++ libass/ass_font.h ++ libass/ass_fontselect.c ++ libass/ass_fontselect.h ++ libass/ass_library.c ++ libass/ass_library.h ++ libass/ass_outline.c ++ libass/ass_outline.h ++ libass/ass_parse.c ++ libass/ass_parse.h ++ libass/ass_rasterizer.c ++ libass/ass_rasterizer.h ++ libass/ass_render.c ++ libass/ass_render.h ++ libass/ass_render_api.c ++ libass/ass_shaper.c ++ libass/ass_shaper.h ++ libass/ass_string.c ++ libass/ass_string.h ++ libass/ass_strtod.c ++ libass/ass_types.h ++ libass/ass_utils.c ++ libass/ass_utils.h ++ libass/ass.c ++ libass/ass.h ++ libass/wyhash.h ++ libass/c/blur_template.h ++ libass/c/c_be_blur.c ++ libass/c/c_blend_bitmaps.c ++ libass/c/c_blur.c ++ libass/c/c_rasterizer.c ++ libass/c/rasterizer_template.h ++ libass/config.h ++) ++ ++target_link_libraries(libass ++ PRIVATE ++ Freetype::Freetype ++ FriBidi::FriBidi ++ Iconv::Iconv ++ harfbuzz::harfbuzz ++) ++ ++target_include_directories(libass ++ PRIVATE ++ ${CMAKE_SOURCE_DIR}/libass ++ INTERFACE ++ $<INSTALL_INTERFACE:include/ass> ++) ++ ++if(WINDOWS_STORE) ++ target_compile_definitions(libass ++ PRIVATE ++ MS_APP ++ ) ++ target_compile_options(libass ++ PRIVATE ++ /sdl- ++ ) ++endif() ++ ++if(CONFIG_DIRECTWRITE) ++ target_sources(libass PRIVATE ++ libass/dwrite_c.h ++ libass/ass_directwrite_info_template.h ++ libass/ass_directwrite.h ++ libass/ass_directwrite.c ++ ) ++ target_link_libraries(libass PRIVATE dwrite.lib) ++endif() ++ ++if(CONFIG_CORETEXT) ++ target_sources(libass PRIVATE ++ libass/ass_coretext.h ++ libass/ass_coretext.c ++ ) ++ target_link_libraries(libass PRIVATE ++ "-framework CoreText" ++ "-framework CoreFoundation") ++endif() ++ ++if(CONFIG_FONTCONFIG) ++ target_sources(libass PRIVATE ++ libass/ass_fontconfig.c ++ libass/ass_fontconfig.h ++ ) ++ target_link_libraries(libass PRIVATE Fontconfig::Fontconfig) ++endif() ++ ++set(libass_VERSION ${PROJECT_VERSION}) ++include(CMakePackageConfigHelpers) ++write_basic_package_version_file( ++ ${CMAKE_CURRENT_BINARY_DIR}/libass-config-version.cmake ++ VERSION ${libass_VERSION} ++ COMPATIBILITY AnyNewerVersion ++) ++ ++install(TARGETS libass EXPORT libass ++ RUNTIME DESTINATION bin ++ ARCHIVE DESTINATION lib ++ LIBRARY DESTINATION lib) ++ ++install(FILES ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_render.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_shaper.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_types.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_utils.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_bitmap.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_bitmap_engine.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_cache.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_cache_template.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_drawing.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_filesystem.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_font.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_fontconfig.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_library.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_parse.h ++ ${CMAKE_CURRENT_SOURCE_DIR}/libass/ass_rasterizer.h ++ DESTINATION include/ass) ++ ++install(EXPORT libass ++ FILE ++ libass.cmake ++ NAMESPACE ++ libass:: ++ DESTINATION ++ lib/cmake/libass ++) ++install( ++ FILES ++ cmake/libass-config.cmake ++ ${CMAKE_CURRENT_BINARY_DIR}/libass-config-version.cmake ++ DESTINATION ++ lib/cmake/libass ++) +--- /dev/null ++++ b/cmake/FindFriBidi.cmake +@@ -0,0 +1,47 @@ ++#.rst: ++# FindFribidi ++# ----------- ++# Finds the GNU FriBidi library ++# ++# This will define the following target: ++# ++# FriBidi::FriBidi - The FriBidi library ++ ++if(NOT TARGET FriBidi::${CMAKE_FIND_PACKAGE_NAME}) ++ find_package(PkgConfig) ++ ++ if(PKG_CONFIG_FOUND AND NOT (WIN32 OR WINDOWS_STORE)) ++ pkg_check_modules(FRIBIDI fribidi IMPORTED_TARGET GLOBAL QUIET) ++ ++ get_target_property(FRIBIDI_LIBRARY PkgConfig::FRIBIDI INTERFACE_LINK_LIBRARIES) ++ get_target_property(FRIBIDI_INCLUDE_DIR PkgConfig::FRIBIDI INTERFACE_INCLUDE_DIRECTORIES) ++ ++ else() ++ find_path(FRIBIDI_INCLUDE_DIR NAMES fribidi.h ++ PATH_SUFFIXES fribidi ++ HINTS ${DEPENDS_PATH}/include) ++ find_library(FRIBIDI_LIBRARY NAMES fribidi libfribidi ++ HINTS ${DEPENDS_PATH}/lib) ++ endif() ++ ++ include(FindPackageHandleStandardArgs) ++ find_package_handle_standard_args(FriBidi ++ REQUIRED_VARS FRIBIDI_LIBRARY FRIBIDI_INCLUDE_DIR ++ VERSION_VAR FRIBIDI_VERSION) ++ ++ if(FRIBIDI_FOUND) ++ if(TARGET PkgConfig::FRIBIDI) ++ add_library(FriBidi::${CMAKE_FIND_PACKAGE_NAME} ALIAS PkgConfig::FRIBIDI) ++ else() ++ add_library(FriBidi::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) ++ set_target_properties(FriBidi::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES ++ IMPORTED_LOCATION "${FRIBIDI_LIBRARY}" ++ INTERFACE_INCLUDE_DIRECTORIES "${FRIBIDI_INCLUDE_DIR}") ++ endif() ++ else() ++ if(FriBidi_FIND_REQUIRED) ++ message(FATAL_ERROR "FriBidi library was not found.") ++ endif() ++ endif() ++endif() ++ +--- /dev/null ++++ b/libass/config.h.in +@@ -0,0 +1,115 @@ ++/* targeting a 64-bit arm host architecture */ ++#cmakedefine ARCH_AARCH64 @ARCH_AARCH64@ ++ ++/* targeting a 64-bit x86 host architecture */ ++#cmakedefine ARCH_X86_64 @ARCH_X86_64@ ++ ++/* targeting a 32- or 64-bit x86 host architecture */ ++#cmakedefine ARCH_X86 @ARCH_X86@ ++ ++/* ASM enabled */ ++#cmakedefine CONFIG_ASM @CONFIG_ASM@ ++ ++/* found CoreText framework */ ++#cmakedefine CONFIG_CORETEXT @CONFIG_CORETEXT@ ++ ++/* found DirectWrite and GDI (Win32) */ ++#cmakedefine CONFIG_DIRECTWRITE @CONFIG_DIRECTWRITE@ ++ ++/* found fontconfig via pkg-config */ ++#cmakedefine CONFIG_FONTCONFIG @CONFIG_FONTCONFIG@ ++ ++/* found freetype2 via pkg-config */ ++#cmakedefine CONFIG_FREETYPE @CONFIG_FREETYPE@ ++ ++/* found fribidi via pkg-config */ ++#cmakedefine CONFIG_FRIBIDI @CONFIG_FRIBIDI@ ++ ++/* found harfbuzz via pkg-config */ ++#cmakedefine CONFIG_HARFBUZZ @CONFIG_HARFBUZZ@ ++ ++/* use iconv */ ++#cmakedefine CONFIG_ICONV @CONFIG_ICONV@ ++ ++/* use small tiles */ ++#cmakedefine01 CONFIG_LARGE_TILES ++ ++/* found libpng via pkg-config */ ++#cmakedefine CONFIG_LIBPNG @CONFIG_LIBPNG@ ++ ++/* string containing info about the used source */ ++#cmakedefine CONFIG_SOURCEVERSION @CONFIG_SOURCEVERSION@ ++ ++/* found libunibreak via pkg-config */ ++#cmakedefine CONFIG_UNIBREAK ++ ++/* Define to 1 if you have the <dlfcn.h> header file. */ ++#cmakedefine HAVE_DLFCN_H @HAVE_DLFCN_H@ ++ ++/* Define to 1 if you have the <iconv.h> header file. */ ++#cmakedefine HAVE_ICONV_H @HAVE_ICONV_H@ ++ ++/* Define to 1 if you have the <inttypes.h> header file. */ ++#cmakedefine HAVE_INTTYPES_H @HAVE_INTTYPES_H@ ++ ++/* Define to 1 if you have the <stdint.h> header file. */ ++#cmakedefine HAVE_STDINT_H @HAVE_STDINT_H@ ++ ++/* Define to 1 if you have the <stdio.h> header file. */ ++#cmakedefine HAVE_STDIO_H @HAVE_STDIO_H@ ++ ++/* Define to 1 if you have the <stdlib.h> header file. */ ++#cmakedefine HAVE_STDLIB_H @HAVE_STDLIB_H@ ++ ++/* Define to 1 if you have the `strdup' function. */ ++#cmakedefine HAVE_STRDUP @HAVE_STRDUP@ ++ ++/* Define to 1 if you have the <strings.h> header file. */ ++#cmakedefine HAVE_STRINGS_H @HAVE_STRINGS_H@ ++ ++/* Define to 1 if you have the <string.h> header file. */ ++#cmakedefine HAVE_STRING_H @HAVE_STRING_H@ ++ ++/* Define to 1 if you have the `strndup' function. */ ++#cmakedefine HAVE_STRNDUP @HAVE_STRNDUP@ ++ ++/* Define to 1 if you have the <sys/stat.h> header file. */ ++#cmakedefine HAVE_SYS_STAT_H @HAVE_SYS_STAT_H@ ++ ++/* Define to 1 if you have the <sys/types.h> header file. */ ++#cmakedefine HAVE_SYS_TYPES_H @HAVE_SYS_TYPES_H@ ++ ++/* Define to 1 if you have the <unistd.h> header file. */ ++#cmakedefine HAVE_UNISTD_H @HAVE_UNISTD_H@ ++ ++/* Define to the sub-directory where libtool stores uninstalled libraries. */ ++#cmakedefine LT_OBJDIR @LT_OBJDIR@ ++ ++/* Name of package */ ++#cmakedefine PACKAGE @PACKAGE@ ++ ++/* Define to the address where bug reports for this package should be sent. */ ++#cmakedefine PACKAGE_BUGREPORT @PACKAGE_BUGREPORT@ ++ ++/* Define to the full name of this package. */ ++#cmakedefine PACKAGE_NAME @PACKAGE_NAME@ ++ ++/* Define to the full name and version of this package. */ ++#cmakedefine PACKAGE_STRING @PACKAGE_STRING@ ++ ++/* Define to the one symbol short name of this package. */ ++#cmakedefine PACKAGE_TARNAME @PACKAGE_TARNAME@ ++ ++/* Define to the home page for this package. */ ++#cmakedefine PACKAGE_URL @PACKAGE_URL@ ++ ++/* Define to the version of this package. */ ++#cmakedefine PACKAGE_VERSION @PACKAGE_VERSION@ ++ ++/* Define to 1 if all of the C90 standard headers exist (not just the ones ++ required in a freestanding environment). This macro is provided for ++ backward compatibility; new code need not use it. */ ++#cmakedefine STDC_HEADERS @STDC_HEADERS@ ++ ++/* Version number of package */ ++#cmakedefine VERSION @VERSION@ diff --git a/tools/depends/target/libass/02-win-dwrite-fontload.patch b/tools/depends/target/libass/02-win-dwrite-fontload.patch new file mode 100644 index 0000000000000..438a1ae1892c1 --- /dev/null +++ b/tools/depends/target/libass/02-win-dwrite-fontload.patch @@ -0,0 +1,731 @@ +--- a/libass/ass_directwrite.c ++++ b/libass/ass_directwrite.c +@@ -26,6 +26,8 @@ + #include "dwrite_c.h" + + #include "ass_directwrite.h" ++#include "ass_library.h" ++#include "ass_types.h" + #include "ass_utils.h" + + #define FALLBACK_DEFAULT_FONT L"Arial" +@@ -73,14 +75,269 @@ + IDWriteFontFileStream *stream; + } FontPrivate; + ++static void fileNameToPath(wchar_t* dirPath, ++ size_t dirPathLength, ++ wchar_t* filePath, ++ size_t filePathLength, ++ wchar_t* filename) ++{ ++ if (dirPath[dirPathLength - 2] == '*') ++ dirPath[dirPathLength - 2] = '\0'; ++ ++ memset(filePath, 0, filePathLength * sizeof(wchar_t)); ++ wcscpy_s(filePath, filePathLength, dirPath); ++ wcscat_s(filePath, filePathLength, filename); ++} ++ ++typedef struct LocalFontEnumerator ++{ ++ IDWriteFontFileEnumerator iface; ++ IDWriteFontFileEnumeratorVtbl vtbl; ++ LONG ref_count; ++ size_t dirPathLength; ++ wchar_t* dirPath; ++ size_t filePathLength; ++ wchar_t* filePath; ++ WIN32_FIND_DATAW ffd; ++ HANDLE hFind; ++ IDWriteFactory* factory; ++} LocalFontEnumerator; ++ ++typedef struct LocalFontLoader ++{ ++ IDWriteFontCollectionLoader iface; ++ IDWriteFontCollectionLoaderVtbl vtbl; ++ LONG ref_count; ++} LocalFontLoader; ++ + typedef struct { + #if ASS_WINAPI_DESKTOP + HMODULE directwrite_lib; + #endif + IDWriteFactory *factory; ++ LocalFontLoader* loader; ++ LocalFontEnumerator* enumerator; + IDWriteGdiInterop *gdi_interop; ++ char* dirPath; + } ProviderPrivate; + ++ ++static HRESULT STDMETHODCALLTYPE LocalFontEnumerator_MoveNext(IDWriteFontFileEnumerator* This, ++ BOOL* hasCurrentFile) ++{ ++ LocalFontEnumerator* this = (LocalFontEnumerator*)This; ++ ++ if (this->hFind == INVALID_HANDLE_VALUE) ++ { ++ this->hFind = FindFirstFileW(this->dirPath, &this->ffd); ++ if (this->hFind == INVALID_HANDLE_VALUE) ++ { ++ *hasCurrentFile = FALSE; ++ // Not finding a path for custom fonts is not an error ++ return S_OK; ++ } ++ ++ while (this->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ++ { ++ BOOL result = FindNextFileW(this->hFind, &this->ffd); ++ if (result == FALSE) ++ { ++ FindClose(this->hFind); ++ this->hFind = INVALID_HANDLE_VALUE; ++ *hasCurrentFile = FALSE; ++ return S_OK; ++ } ++ } ++ ++ fileNameToPath(this->dirPath, this->dirPathLength, this->filePath, this->filePathLength, ++ this->ffd.cFileName); ++ *hasCurrentFile = TRUE; ++ return S_OK; ++ } ++ ++ BOOL result = FindNextFileW(this->hFind, &this->ffd); ++ if (result == FALSE) ++ { ++ FindClose(this->hFind); ++ this->hFind = INVALID_HANDLE_VALUE; ++ *hasCurrentFile = FALSE; ++ return S_OK; ++ } ++ ++ if (!(this->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) ++ fileNameToPath(this->dirPath, this->dirPathLength, this->filePath, this->filePathLength, ++ this->ffd.cFileName); ++ ++ *hasCurrentFile = TRUE; ++ return S_OK; ++} ++ ++HRESULT __stdcall LocalFontEnumerator_GetCurrentFontFile(IDWriteFontFileEnumerator* This, ++ IDWriteFontFile** fontFile) ++{ ++ LocalFontEnumerator* this = (LocalFontEnumerator*)This; ++ return IDWriteFactory_CreateFontFileReference(this->factory, this->filePath, ++ &this->ffd.ftLastWriteTime, fontFile); ++} ++ ++ ++static ULONG STDMETHODCALLTYPE LocalFontEnumerator_AddRef(IDWriteFontFileEnumerator* This) ++{ ++ LocalFontEnumerator* this = (LocalFontEnumerator*)This; ++ return InterlockedIncrement(&this->ref_count); ++} ++ ++static ULONG STDMETHODCALLTYPE LocalFontEnumerator_Release(IDWriteFontFileEnumerator* This) ++{ ++ LocalFontEnumerator* this = (LocalFontEnumerator*)This; ++ unsigned long new_count = InterlockedDecrement(&this->ref_count); ++ if (new_count == 0) ++ { ++ free(this->dirPath); ++ free(this->filePath); ++ IDWriteFactory_Release(this->factory); ++ free(this); ++ return 0; ++ } ++ ++ return new_count; ++} ++ ++static HRESULT STDMETHODCALLTYPE LocalFontEnumerator_QueryInterface(IDWriteFontFileEnumerator* This, ++ REFIID riid, ++ void** ppvObject) ++{ ++ if (IsEqualGUID(riid, &IID_IDWriteFontFileEnumerator) || IsEqualGUID(riid, &IID_IUnknown)) ++ { ++ *ppvObject = This; ++ } ++ else ++ { ++ *ppvObject = NULL; ++ return E_FAIL; ++ } ++ ++ This->lpVtbl->AddRef(This); ++ return S_OK; ++} ++ ++static LocalFontEnumerator* init_LocalFontEnumerator(IDWriteFactory* factory, char* dir) ++{ ++ size_t len = strlen(dir); ++ if (len > 32000) //simple sanity checking, around max path length ++ return NULL; ++ ++ LocalFontEnumerator* instance = (LocalFontEnumerator*)malloc(sizeof(LocalFontEnumerator)); ++ *instance = (LocalFontEnumerator){ ++ .iface = ++ { ++ .lpVtbl = &instance->vtbl, ++ }, ++ .vtbl = ++ { ++ LocalFontEnumerator_QueryInterface, ++ LocalFontEnumerator_AddRef, ++ LocalFontEnumerator_Release, ++ LocalFontEnumerator_MoveNext, ++ LocalFontEnumerator_GetCurrentFontFile, ++ }, ++ .ref_count = 0, ++ .dirPathLength = 0, ++ .dirPath = NULL, ++ .filePathLength = 0, ++ .filePath = NULL, ++ .hFind = INVALID_HANDLE_VALUE, ++ .factory = factory, ++ }; ++ ++ IDWriteFactory_AddRef(factory); ++ LocalFontEnumerator_AddRef((IDWriteFontFileEnumerator*)instance); ++ ++ instance->dirPathLength = len + 2; //add an extra for null and another for a * at the end ++ ++ char* newDir = (char*)malloc(instance->dirPathLength); ++ if (!newDir) ++ return NULL; ++ ++ strcpy_s(newDir, instance->dirPathLength, dir); ++ strcat_s(newDir, instance->dirPathLength, "*"); ++ ++ instance->dirPath = to_utf16(newDir); ++ free(newDir); ++ instance->filePathLength = 32000; ++ instance->filePath = (wchar_t*)malloc(instance->filePathLength * sizeof(wchar_t)); ++ ++ return instance; ++} ++ ++static HRESULT STDMETHODCALLTYPE ++LocalFontLoader_CreateEnumeratorFromKey(IDWriteFontCollectionLoader* This, ++ IDWriteFactory* factory, ++ void const* collectionKey, ++ UINT32 collectionKeySize, ++ IDWriteFontFileEnumerator** fontFileEnumerator) ++{ ++ char* dirPath = (char*)collectionKey; ++ *fontFileEnumerator = (IDWriteFontFileEnumerator*)init_LocalFontEnumerator(factory, dirPath); ++ return S_OK; ++} ++ ++// IUnknown methods ++ ++static ULONG STDMETHODCALLTYPE LocalFontLoader_AddRef(IDWriteFontCollectionLoader* This) ++{ ++ LocalFontLoader* this = (LocalFontLoader*)This; ++ return InterlockedIncrement(&this->ref_count); ++} ++ ++static ULONG STDMETHODCALLTYPE LocalFontLoader_Release(IDWriteFontCollectionLoader* This) ++{ ++ LocalFontLoader* this = (LocalFontLoader*)This; ++ unsigned long new_count = InterlockedDecrement(&this->ref_count); ++ if (new_count == 0) ++ { ++ free(this); ++ return 0; ++ } ++ ++ return new_count; ++} ++ ++static HRESULT STDMETHODCALLTYPE LocalFontLoader_QueryInterface(IDWriteFontCollectionLoader* This, ++ REFIID riid, ++ void** ppvObject) ++{ ++ if (IsEqualGUID(riid, &IID_IDWriteFontCollectionLoader) || IsEqualGUID(riid, &IID_IUnknown)) ++ { ++ *ppvObject = This; ++ } ++ else ++ { ++ *ppvObject = NULL; ++ return E_FAIL; ++ } ++ ++ This->lpVtbl->AddRef(This); ++ return S_OK; ++} ++ ++static LocalFontLoader* init_LocalFontLoader() ++{ ++ LocalFontLoader* instance = (LocalFontLoader*)malloc(sizeof(LocalFontLoader)); ++ if (!instance) ++ return NULL; ++ ++ *instance = (LocalFontLoader){.iface = ++ { ++ .lpVtbl = &instance->vtbl, ++ }, ++ .vtbl = {LocalFontLoader_QueryInterface, LocalFontLoader_AddRef, ++ LocalFontLoader_Release, ++ LocalFontLoader_CreateEnumeratorFromKey}}; ++ ++ return instance; ++} ++ + /** + * Custom text renderer class for logging the fonts used. It does not + * actually render anything or do anything apart from that. +@@ -393,6 +650,29 @@ + return IDWriteFontFace_GetIndex(priv->face); + } + ++static HRESULT get_font_name(IDWriteLocalizedStrings* strings, UINT32 index, char** name) ++{ ++ *name = NULL; ++ UINT32 length; ++ HRESULT hr = IDWriteLocalizedStrings_GetStringLength(strings, index, &length); ++ if (FAILED(hr)) ++ { ++ return hr; ++ } ++ length++; ++ wchar_t* temp_name = (wchar_t*)malloc(length * sizeof(wchar_t)); ++ hr = IDWriteLocalizedStrings_GetString(strings, 0, temp_name, length); ++ if (FAILED(hr)) ++ { ++ free(temp_name); ++ return hr; ++ } ++ ++ *name = to_utf8(temp_name); ++ free(temp_name); ++ return hr; ++} ++ + /* + * Check if the passed font has a specific unicode character. + */ +@@ -518,9 +798,18 @@ + + init_FallbackLogTextRenderer(&renderer, dw_factory); + +- hr = IDWriteFactory_CreateTextFormat(dw_factory, FALLBACK_DEFAULT_FONT, NULL, +- DWRITE_FONT_WEIGHT_MEDIUM, DWRITE_FONT_STYLE_NORMAL, +- DWRITE_FONT_STRETCH_NORMAL, 1.0f, L"", &text_format); ++ wchar_t* requested_font = to_utf16(base); ++ if (!requested_font) ++ { ++ requested_font = FALLBACK_DEFAULT_FONT; ++ ass_msg(lib, MSGL_WARN, "Failed to get the fallback font name, will be used Arial."); ++ } ++ hr = IDWriteFactory_CreateTextFormat(dw_factory, requested_font, NULL, ++ DWRITE_FONT_WEIGHT_MEDIUM, DWRITE_FONT_STYLE_NORMAL, ++ DWRITE_FONT_STRETCH_NORMAL, 1.0f, L"", &text_format); ++ if (requested_font) ++ free(requested_font); ++ + if (FAILED(hr)) { + return NULL; + } +@@ -543,6 +832,7 @@ + IDWriteFont *font = NULL; + hr = IDWriteTextLayout_Draw(text_layout, &font, &renderer.iface, 0.0f, 0.0f); + if (FAILED(hr) || font == NULL) { ++ FallbackLogTextRenderer_Release((IDWriteTextRenderer*)&renderer); + IDWriteTextLayout_Release(text_layout); + IDWriteTextFormat_Release(text_format); + return NULL; +@@ -588,6 +878,50 @@ + return family; + } + ++static char* get_font_path(IDWriteFont* font) ++{ ++ IDWriteFontFace* fontFace; ++ HRESULT hr = IDWriteFont_CreateFontFace(font, &fontFace); ++ if (FAILED(hr)) ++ return NULL; ++ ++ IDWriteFontFile* fontFiles[1]; ++ UINT32 files = 1; ++ hr = IDWriteFontFace_GetFiles(fontFace, &files, fontFiles); ++ if (FAILED(hr)) ++ { ++ IDWriteFontFace_Release(fontFace); ++ return NULL; ++ } ++ ++ const wchar_t* refKey = NULL; ++ hr = IDWriteFontFile_GetReferenceKey(fontFiles[0], &refKey, &files); ++ if (FAILED(hr)) ++ { ++ IDWriteFontFace_Release(fontFace); ++ for (int i = 0; i < files; ++i) ++ { ++ IDWriteFontFile_Release(fontFiles[i]); ++ } ++ return NULL; ++ } ++ ++ // This must be before we release the reference because the key is ++ // only guaranteed to be valid until release ++ char* path = NULL; ++ wchar_t* start = wcschr(refKey, L':'); ++ if (start) ++ { ++ ptrdiff_t diff = start - refKey - 1; ++ path = to_utf8(start - 1); ++ } ++ ++ IDWriteFontFace_Release(fontFace); ++ IDWriteFontFile_Release(fontFiles[0]); ++ ++ return path; ++} ++ + #define FONT_TYPE IDWriteFontFace3 + #include "ass_directwrite_info_template.h" + #undef FONT_TYPE +@@ -815,6 +1149,8 @@ + } + } + ++#endif ++ + static void add_font(IDWriteFont *font, IDWriteFontFamily *fontFamily, + ASS_FontProvider *provider) + { +@@ -838,8 +1174,48 @@ + IDWriteFont_Release(font); + } + +-#endif ++static void scan_fonts(IDWriteFactory* factory, ++ IDWriteFontCollection* fontCollection, ++ ASS_FontProvider* provider) ++{ ++ HRESULT hr = S_OK; ++ IDWriteFont* font = NULL; ++ IDWriteFontFamily* fontFamily = NULL; ++ ++ UINT32 familyCount = IDWriteFontCollection_GetFontFamilyCount(fontCollection); + ++ for (UINT32 i = 0; i < familyCount; ++i) ++ { ++ IDWriteFontFamily* fontFamily = NULL; ++ ++ hr = IDWriteFontCollection_GetFontFamily(fontCollection, i, &fontFamily); ++ if (FAILED(hr)) ++ continue; ++ ++ UINT32 fontCount = IDWriteFontFamily_GetFontCount(fontFamily); ++ for (UINT32 j = 0; j < fontCount; ++j) ++ { ++ hr = IDWriteFontFamily_GetFont(fontFamily, j, &font); ++ if (FAILED(hr)) ++ continue; ++ ++ // Simulations for bold or oblique are sometimes synthesized by ++ // DirectWrite. We are only interested in physical fonts. ++ if (IDWriteFont_GetSimulations(font) != 0) ++ { ++ IDWriteFont_Release(font); ++ continue; ++ } ++ ++ add_font(font, fontFamily, provider); ++ } ++ ++ IDWriteFontFamily_Release(fontFamily); ++ } ++ ++ IDWriteFontCollection_Release(fontCollection); ++} ++ + /* + * When a new font name is requested, called to load that font from Windows + */ +@@ -1097,18 +1473,45 @@ + #if ASS_WINAPI_DESKTOP + priv->directwrite_lib = directwrite_lib; + #endif ++ priv->dirPath = lib->fonts_dir; + priv->factory = dwFactory; ++ priv->loader = init_LocalFontLoader(); ++ hr = IDWriteFactory_RegisterFontCollectionLoader(dwFactory, ++ (IDWriteFontCollectionLoader*)priv->loader); ++ if (FAILED(hr)) ++ goto cleanup; ++ ++ IDWriteFontCollection* collection; ++ int dirPathLen = strlen(priv->dirPath) + 1; ++ hr = IDWriteFactory_CreateCustomFontCollection( ++ dwFactory, (IDWriteFontCollectionLoader*)priv->loader, (const void*)priv->dirPath, ++ dirPathLen, &collection); ++ if (FAILED(hr)) ++ goto cleanup; + priv->gdi_interop = dwGdiInterop; + + provider = ass_font_provider_new(selector, &directwrite_callbacks, priv); + if (!provider) + goto cleanup; + ++ IDWriteFontCollection* systemCollection; ++ hr = IDWriteFactory_GetSystemFontCollection(dwFactory, &systemCollection, FALSE); ++ if (FAILED(hr)) ++ goto cleanup; ++ ++ scan_fonts(dwFactory, systemCollection, provider); ++ scan_fonts(dwFactory, collection, provider); + return provider; + + cleanup: +- +- free(priv); ++ if (priv) ++ { ++ if (priv->loader) ++ LocalFontLoader_Release((IDWriteFontCollectionLoader*)priv->loader); ++ if (priv->enumerator) ++ LocalFontEnumerator_Release((IDWriteFontFileEnumerator*)priv->enumerator); ++ free(priv); ++ } + if (dwGdiInterop) + dwGdiInterop->lpVtbl->Release(dwGdiInterop); + if (dwFactory) +--- a/libass/ass_utils.c ++++ b/libass/ass_utils.c +@@ -29,6 +29,9 @@ + #include "ass.h" + #include "ass_utils.h" + #include "ass_string.h" ++#if defined(WIN32) || defined(_MSC_VER) ++#include <windows.h> ++#endif + + // Fallbacks + #ifndef HAVE_STRDUP +@@ -270,4 +273,41 @@ + "[%p]: Warning: no style named '%s' found, using '%s'", + track, name, track->styles[i].Name); + return i; ++} ++ ++#if defined(WIN32) || defined(_MSC_VER) ++wchar_t* to_utf16(const char* str) ++{ ++ int utf16len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); ++ if (utf16len == 0) ++ return NULL; ++ ++ wchar_t* dirPath = malloc(sizeof(wchar_t) * utf16len); ++ int result = MultiByteToWideChar(CP_UTF8, 0, str, -1, dirPath, utf16len); ++ ++ if (result == 0) ++ { ++ free(dirPath); ++ return NULL; ++ } ++ ++ return dirPath; + } ++ ++char* to_utf8(const wchar_t* str) ++{ ++ int utf8len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); ++ if (utf8len == 0) ++ return NULL; ++ ++ char* newStr = malloc(utf8len); ++ int result = WideCharToMultiByte(CP_UTF8, 0, str, -1, newStr, utf8len, NULL, NULL); ++ if (result == 0) ++ { ++ free(newStr); ++ return NULL; ++ } ++ ++ return newStr; ++} ++#endif +--- a/libass/ass_utils.h ++++ b/libass/ass_utils.h +@@ -56,6 +56,11 @@ + size_t len; + } ASS_StringView; + ++#if defined(WIN32) || defined(_MSC_VER) ++wchar_t* to_utf16(const char* str); ++char* to_utf8(const wchar_t* str); ++#endif ++ + static inline char *ass_copy_string(ASS_StringView src) + { + char *buf = malloc(src.len + 1); +--- a/libass/dwrite_c.h ++++ b/libass/dwrite_c.h +@@ -33,6 +33,8 @@ typedef struct IDWritePixelSnapping IDWritePixelSnapping; + typedef struct IDWriteTextFormat IDWriteTextFormat; + typedef struct IDWriteTextLayout IDWriteTextLayout; + typedef struct IDWriteTextRenderer IDWriteTextRenderer; ++typedef struct IDWriteFontFileEnumerator IDWriteFontFileEnumerator; ++typedef struct IDWriteFontCollectionLoader IDWriteFontCollectionLoader; + typedef struct IDWriteGdiInterop IDWriteGdiInterop; + + #include <dcommon.h> +@@ -185,6 +187,58 @@ typedef struct DWRITE_UNDERLINE DWRITE_UNDERLINE; + #endif + #endif + ++#undef INTERFACE ++#define INTERFACE IDWriteFontFileEnumerator ++DECLARE_INTERFACE_(IDWriteFontFileEnumerator,IUnknown) ++{ ++ BEGIN_INTERFACE ++ ++#ifndef __cplusplus ++ /* IUnknown methods */ ++ STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; ++ STDMETHOD_(ULONG, AddRef)(THIS) PURE; ++ STDMETHOD_(ULONG, Release)(THIS) PURE; ++#endif ++ STDMETHOD(MoveNext)(THIS, BOOL* hasCurrentFile) PURE; ++ ++ STDMETHOD(GetCurrentFontFile)(THIS, IDWriteFontFile** fontFile) PURE; ++ ++ END_INTERFACE ++}; ++#ifdef COBJMACROS ++#define IDWriteFontFileEnumerator_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) ++#define IDwriteFontFileEnumerator_AddRef(This) (This)->lpVtbl->AddRef(This) ++#define IDWriteFontFileEnumerator_Release(This) (This)->lpVtbl->Release(This) ++#define IDWriteFontFileEnumerator_MoveNext(This) (This)->lpVtbl->MoveNext(This, hasCurrentFile) ++#define IDWriteFontFileEnumerator_GetCurrentFontFile(This) (This)->lpVtbl->GetCurrentFontFile(This, fontFile) ++#endif /*COBJMACROS*/ ++ ++#undef INTERFACE ++#define INTERFACE IDWriteFontCollectionLoader ++DECLARE_INTERFACE_(IDWriteFontCollectionLoader, IUnknown) ++{ ++ BEGIN_INTERFACE ++#ifndef __cplusplus ++ /* IUnknown methods */ ++ STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppvObject) PURE; ++ STDMETHOD_(ULONG, AddRef)(THIS) PURE; ++ STDMETHOD_(ULONG, Release)(THIS) PURE; ++#endif ++ STDMETHOD(CreateEnumeratorFromKey)(THIS_ ++ IDWriteFactory* factory, ++ void const* collectionKey, ++ UINT32 collectionKeySize, ++ IDWriteFontFileEnumerator** fontFileEnumerator) PURE; ++ ++ END_INTERFACE ++}; ++#ifdef COBJMACROS ++#define IDWriteFontCollectionLoader_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) ++#define IDWriteFontCollectionLoader_AddRef(This) (This)->lpVtbl->AddRef(This) ++#define IDWriteFontCollectionLoader_Release(This) (This)->lpVtbl->Release(This) ++#define IDWriteFontCollectionLoader_CreateEnumeratorFromKey(This) (This)->lpVtbl->CreateEnumeratorFromKey(This, factor, collectionKey, collectionKeySize, fontFileEnumerator) ++#endif /*COBJMACROS*/ ++ + #undef INTERFACE + #define INTERFACE IDWriteFactory + DECLARE_INTERFACE_(IDWriteFactory,IUnknown) +@@ -203,10 +257,23 @@ DECLARE_INTERFACE_(IDWriteFactory,IUnknown) + IDWriteFontCollection **fontCollection, + BOOL checkForUpdates __MINGW_DEF_ARG_VAL(FALSE)) PURE; + +- STDMETHOD(dummy1)(THIS); +- STDMETHOD(dummy2)(THIS); +- STDMETHOD(dummy3)(THIS); +- STDMETHOD(dummy4)(THIS); ++ STDMETHOD(CreateCustomFontCollection)(THIS_ ++ IDWriteFontCollectionLoader* collectionLoader, ++ void const* collectionKey, ++ UINT32 collectionKeySize, ++ IDWriteFontCollection** fontCollection) PURE; ++ ++ STDMETHOD(RegisterFontCollectionLoader)(THIS_ ++ IDWriteFontCollectionLoader *fontCollectionLoader) PURE; ++ ++ STDMETHOD(UnregisterFontCollectionLoader)(THIS_ ++ IDWriteFontCollectionLoader *fontCollectionLoader) PURE; ++ ++ STDMETHOD(CreateFontFileReference)(THIS_ ++ _In_z_ WCHAR const* filePath, ++ _In_opt_ FILETIME const* lastWriteTime, ++ _COM_Outptr_ IDWriteFontFile** fontFile) PURE; ++ + STDMETHOD(dummy5)(THIS); + STDMETHOD(dummy6)(THIS); + STDMETHOD(dummy7)(THIS); +@@ -246,8 +313,12 @@ DECLARE_INTERFACE_(IDWriteFactory,IUnknown) + #define IDWriteFactory_AddRef(This) (This)->lpVtbl->AddRef(This) + #define IDWriteFactory_Release(This) (This)->lpVtbl->Release(This) + #define IDWriteFactory_GetSystemFontCollection(This,fontCollection,checkForUpdates) (This)->lpVtbl->GetSystemFontCollection(This,fontCollection,checkForUpdates) ++#define IDWriteFactory_CreateCustomFontCollection(This, collectionLoader,collectionKey,collectionKeySize,fontCollection) (This)->lpVtbl->CreateCustomFontCollection(This,collectionLoader,collectionKey,collectionKeySize,fontCollection) ++#define IDWriteFactory_RegisterFontCollectionLoader(This,fontCollectionLoader) (This)->lpVtbl->RegisterFontCollectionLoader(This,fontCollectionLoader) ++#define IDWriteFactory_UnregisterFontCollectionLoader(This,fontCollectionLoader) (This)->lpVtbl->UnregisterFontCollectionLoader(This,fontCollectionLoader) + #define IDWriteFactory_CreateTextFormat(This,fontFamilyName,fontCollection,fontWeight,fontStyle,fontStretch,fontSize,localeName,textFormat) (This)->lpVtbl->CreateTextFormat(This,fontFamilyName,fontCollection,fontWeight,fontStyle,fontStretch,fontSize,localeName,textFormat) + #define IDWriteFactory_CreateTextLayout(This,string,stringLength,textFormat,maxWidth,maxHeight,textLayout) (This)->lpVtbl->CreateTextLayout(This,string,stringLength,textFormat,maxWidth,maxHeight,textLayout) ++#define IDWriteFactory_CreateFontFileReference(This,filePath,lastWriteTime,fontFile) (This)->lpVtbl->CreateFontFileReference(This,filePath,lastWriteTime,fontFile) + #define IDWriteFactory_GetGdiInterop(This,gdiInterop) (This)->lpVtbl->GetGdiInterop(This,gdiInterop) + #endif /*COBJMACROS*/ + +@@ -828,9 +899,19 @@ DECLARE_INTERFACE_(IDWriteLocalizedStrings,IUnknown) + /* IDWriteLocalizedStrings methods */ + STDMETHOD_(UINT32, GetCount)(THIS) PURE; + +- STDMETHOD(dummy1)(THIS); +- STDMETHOD(dummy2)(THIS); +- STDMETHOD(dummy3)(THIS); ++ STDMETHOD(FindLocaleName)(THIS_ ++ _In_z_ WCHAR const* localeName, ++ _Out_ UINT32* index, ++ _Out_ BOOL* exists) PURE; ++ ++ STDMETHOD(GetLocaleNameLength)(THIS_ ++ UINT32 index, ++ _Out_ UINT32* length) PURE; ++ ++ STDMETHOD(GetLocaleName)(THIS_ ++ UINT32 index, ++ _Out_writes_z_(size) WCHAR* localeName, ++ UINT32 size) PURE; + + STDMETHOD(GetStringLength)(THIS_ + UINT32 index, +@@ -847,6 +928,7 @@ DECLARE_INTERFACE_(IDWriteLocalizedStrings,IUnknown) + #define IDWriteLocalizedStrings_GetCount(This) (This)->lpVtbl->GetCount(This) + #define IDWriteLocalizedStrings_GetStringLength(This,index,length) (This)->lpVtbl->GetStringLength(This,index,length) + #define IDWriteLocalizedStrings_GetString(This,index,stringBuffer,size) (This)->lpVtbl->GetString(This,index,stringBuffer,size) ++#define IDWriteLocalizedStrings_GetStringLength(This,index,size) (This)->lpVtbl->GetStringLength(This,index,size) + #endif /*COBJMACROS*/ + + #undef INTERFACE +@@ -1011,6 +1093,12 @@ DECLARE_INTERFACE_(IDWriteTextRenderer,IDWritePixelSnapping) + END_INTERFACE + }; + ++EXTERN_C HRESULT __declspec(dllimport) __stdcall DWriteCreateFactory( ++ _In_ DWRITE_FACTORY_TYPE factoryType, ++ _In_ REFIID iid, ++ _COM_Outptr_ IUnknown** factory ++); ++ + #undef INTERFACE + #define INTERFACE IDWriteGdiInterop + DECLARE_INTERFACE_(IDWriteGdiInterop,IUnknown) +@@ -1050,5 +1138,8 @@ DEFINE_GUID(IID_IDWriteFactory3, 0x9a1b41c3,0xd3bb,0x466a,0x87,0xfc,0xfe,0x67,0x + DEFINE_GUID(IID_IDWriteFontFace3, 0xd37d7598,0x09be,0x4222,0xa2,0x36,0x20,0x81,0x34,0x1c,0xc1,0xf2); + DEFINE_GUID(IID_IDWritePixelSnapping, 0xeaf3a2da,0xecf4,0x4d24,0xb6,0x44,0xb3,0x4f,0x68,0x42,0x02,0x4b); + DEFINE_GUID(IID_IDWriteTextRenderer, 0xef8a8135,0x5cc6,0x45fe,0x88,0x25,0xc5,0xa0,0x72,0x4e,0xb8,0x19); ++DEFINE_GUID(IID_IDWriteFontFileEnumerator, 0x72755049, 0x5ff7, 0x435d, 0x83, 0x48, 0x4b, 0xe9, 0x7c, 0xfa, 0x6c, 0x7c); ++DEFINE_GUID(IID_IDWriteFontCollectionLoader, 0xcca920e4, 0x52f0, 0x492b, 0xbf, 0xa8, 0x29, 0xc7, 0x2e, 0xe0, 0xa4, 0x68); ++DEFINE_GUID(IID_IDWriteGdiInterop, 0x1edd9491,0x9853,0x4299,0x89,0x8f,0x64,0x32,0x98,0x3b,0x6f,0x3a); + + #endif /* __INC_DWRITE__ */ diff --git a/tools/depends/target/libass/03-win-dwrite-prevent-ass_set_fonts_dir.patch b/tools/depends/target/libass/03-win-dwrite-prevent-ass_set_fonts_dir.patch new file mode 100644 index 0000000000000..dec016121739e --- /dev/null +++ b/tools/depends/target/libass/03-win-dwrite-prevent-ass_set_fonts_dir.patch @@ -0,0 +1,20 @@ +--- a/libass/ass_fontselect.c ++++ b/libass/ass_fontselect.c +@@ -1021,11 +1021,15 @@ + return NULL; + + ASS_Library *lib = selector->library; +- ++#if !defined(CONFIG_DIRECTWRITE) ++ // If a font provider has implemented the feature to load the fonts from a custom folder, ++ // then we do not have to load fonts from fonts_dir path also here or they will be loaded twice. ++ // WARNING: load_fonts_from_dir method will load all fonts in RAM, ++ // too many fonts could cause slow down and system crashes. + if (lib->fonts_dir && lib->fonts_dir[0]) { + load_fonts_from_dir(lib, lib->fonts_dir); + } +- ++#endif + for (size_t i = 0; i < lib->num_fontdata; i++) + process_fontdata(priv, i); + *num_emfonts = lib->num_fontdata; diff --git a/tools/depends/target/libass/04-win-nullcheck-shared_hdc.patch b/tools/depends/target/libass/04-win-nullcheck-shared_hdc.patch new file mode 100644 index 0000000000000..126a108186119 --- /dev/null +++ b/tools/depends/target/libass/04-win-nullcheck-shared_hdc.patch @@ -0,0 +1,11 @@ +--- a/libass/ass_directwrite.c ++++ b/libass/ass_directwrite.c +@@ -55,7 +55,7 @@ + + static void hdc_release(ASS_SharedHDC *shared_hdc) + { +- if (!--shared_hdc->ref_count) { ++ if (shared_hdc && !--shared_hdc->ref_count) { + DeleteDC(shared_hdc->hdc); + free(shared_hdc); + } diff --git a/tools/depends/target/libass/LIBASS-VERSION b/tools/depends/target/libass/LIBASS-VERSION index 336d709ab4034..4f8ee758b3cc0 100644 --- a/tools/depends/target/libass/LIBASS-VERSION +++ b/tools/depends/target/libass/LIBASS-VERSION @@ -1,5 +1,5 @@ LIBNAME=libass -VERSION=0.17.1 +VERSION=0.17.3 ARCHIVE=$(LIBNAME)-$(VERSION).tar.xz -SHA512=437b4b60db2626e48f438b5138ab4dbf2c2ab4c8f0b587a6e554510c6719544ef2235c601ff1e482d020410f9bab95ad1a9b176d19a3bd54880499a576b41f23 +SHA512=c8da55d38159d45838b359547ac1f83aa40665448b71e404ec8bdce68e0b3f46e80ab88c0194697bff0275d88ca86d58a0cf8a8cbc6dfc3ee1dc40d33d532dfc BYPRODUCT=libass.a diff --git a/tools/depends/target/libass/Makefile b/tools/depends/target/libass/Makefile index 94bfcac3fa40b..e8b8b39f692c3 100644 --- a/tools/depends/target/libass/Makefile +++ b/tools/depends/target/libass/Makefile @@ -1,5 +1,12 @@ include ../../Makefile.include LIBASS-VERSION ../../download-files.include -DEPS = ../../Makefile.include Makefile LIBASS-VERSION ../../download-files.include +DEPS = ../../Makefile.include Makefile LIBASS-VERSION ../../download-files.include \ + 01-win-CMakeLists.patch \ + 02-win-dwrite-fontload.patch \ + 03-win-dwrite-prevent-ass_set_fonts_dir.patch \ + 04-win-nullcheck-shared_hdc.patch + +# set ASS_BUILD_SYSTEM to cmake to use cmake build system patches +ASS_BUILD_SYSTEM=make # configuration settings CONFIGURE=cp -f $(CONFIG_SUB) $(CONFIG_GUESS) .; \ @@ -11,16 +18,32 @@ LIBDYLIB=$(PLATFORM)/$(LIBNAME)/.libs/$(LIBNAME).a all: .installed-$(PLATFORM) $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) - rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM) + rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM)/build cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) + cd $(PLATFORM); patch -p1 -i ../01-win-CMakeLists.patch + cd $(PLATFORM); patch -p1 -i ../02-win-dwrite-fontload.patch + cd $(PLATFORM); patch -p1 -i ../03-win-dwrite-prevent-ass_set_fonts_dir.patch + cd $(PLATFORM); patch -p1 -i ../04-win-nullcheck-shared_hdc.patch +ifeq ($(ASS_BUILD_SYSTEM), cmake) + cd $(PLATFORM)/build; $(CMAKE) -DARCH=$(MESON_CPU) -DNATIVEPREFIX=$(NATIVEPREFIX) -DCMAKE_BUILD_TYPE=Release .. +else cd $(PLATFORM); $(AUTORECONF) -vif cd $(PLATFORM); $(CONFIGURE) +endif $(LIBDYLIB): $(PLATFORM) +ifeq ($(ASS_BUILD_SYSTEM), cmake) + $(MAKE) -C $(PLATFORM)/build +else $(MAKE) -C $(PLATFORM) +endif .installed-$(PLATFORM): $(LIBDYLIB) +ifeq ($(ASS_BUILD_SYSTEM), cmake) + $(MAKE) -C $(PLATFORM)/build install +else $(MAKE) -C $(PLATFORM) install +endif touch $@ clean: From db85a5edc341a03bf1362a7ec6808aa78c878aac Mon Sep 17 00:00:00 2001 From: fuzzard <fuzzard@kodi.tv> Date: Mon, 28 Oct 2024 19:01:06 +1000 Subject: [PATCH 621/651] [cmake] Enable building libass internally for all platforms --- CMakeLists.txt | 1 + cmake/modules/FindASS.cmake | 222 +++++++++++++++--- cmake/modules/FindFriBidi.cmake | 4 +- .../scripts/0_package.target-win10-arm.list | 1 - .../scripts/0_package.target-win10-win32.list | 1 - .../scripts/0_package.target-win10-x64.list | 1 - .../scripts/0_package.target-win32.list | 1 - .../scripts/0_package.target-x64.list | 1 - tools/depends/target/Makefile | 6 +- tools/depends/target/libass/LIBASS-VERSION | 2 + 10 files changed, 194 insertions(+), 46 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ef3151592bf1..a850bfb7f1ac3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ option(ENABLE_INTERNAL_CROSSGUID "Enable internal crossguid?" ON) option(ENABLE_INTERNAL_FFMPEG "Enable internal ffmpeg?" OFF) # These are built for all platforms not using system libs or disabled by user +dependent_option(ENABLE_INTERNAL_ASS "Enable internal libass?") dependent_option(ENABLE_INTERNAL_CEC "Enable internal libcec?") dependent_option(ENABLE_INTERNAL_CURL "Enable internal libcurl?") dependent_option(ENABLE_INTERNAL_EXIV2 "Enable internal exiv2?") diff --git a/cmake/modules/FindASS.cmake b/cmake/modules/FindASS.cmake index 9420dd8f79591..b2f2cdf22aa77 100644 --- a/cmake/modules/FindASS.cmake +++ b/cmake/modules/FindASS.cmake @@ -10,59 +10,207 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) - find_package(PkgConfig) - # Do not use pkgconfig on windows - if(PKG_CONFIG_FOUND AND NOT (WIN32 OR WINDOWS_STORE)) - pkg_check_modules(PC_ASS libass QUIET IMPORTED_TARGET) + include(cmake/scripts/common/ModuleHelpers.cmake) - # INTERFACE_LINK_OPTIONS is incorrectly populated when cmake generation is executed - # when an existing build generation is already done. Just set this to blank - set_target_properties(PkgConfig::PC_ASS PROPERTIES INTERFACE_LINK_OPTIONS "") - - # First item is the full path of the library file found - # pkg_check_modules does not populate a variable of the found library explicitly - list(GET PC_ASS_LINK_LIBRARIES 0 ASS_LIBRARY) - set(ASS_INCLUDE_DIR ${PC_ASS_INCLUDEDIR}) - set(ASS_VERSION ${PC_ASS_VERSION}) - elseif(WIN32 OR WINDOWS_STORE) - find_package(libass CONFIG QUIET REQUIRED - HINTS ${DEPENDS_PATH}/lib/cmake - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) + macro(buildlibASS) + + find_package(FreeType REQUIRED QUIET) + find_package(HarfBuzz REQUIRED QUIET) + find_package(FriBidi REQUIRED QUIET) + find_package(Iconv REQUIRED QUIET) + + # Posix platforms (except Apple) use fontconfig + if(NOT (WIN32 OR WINDOWS_STORE) AND + NOT (CMAKE_SYSTEM_NAME STREQUAL Darwin)) + find_package(Fontconfig REQUIRED QUIET) + endif() + + set(ASS_VERSION ${${MODULE}_VER}) + + if(WIN32 OR WINDOWS_STORE) + set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/libass/01-win-CMakeLists.patch" + "${CMAKE_SOURCE_DIR}/tools/depends/target/libass/02-win-dwrite-fontload.patch" + "${CMAKE_SOURCE_DIR}/tools/depends/target/libass/03-win-dwrite-prevent-ass_set_fonts_dir.patch" + "${CMAKE_SOURCE_DIR}/tools/depends/target/libass/04-win-nullcheck-shared_hdc.patch") + + generate_patchcommand("${patches}") + + set(LIBASS_DEBUG_POSTFIX d) + endif() - # we only do this because we use find_package_handle_standard_args for config time output - # and it isnt capable of handling TARGETS, so we have to extract the info - get_target_property(_ASS_CONFIGURATIONS libass::libass IMPORTED_CONFIGURATIONS) - foreach(_ass_config IN LISTS _ASS_CONFIGURATIONS) - # Some non standard config (eg None on Debian) - # Just set to RELEASE var so select_library_configurations can continue to work its magic - string(TOUPPER ${_ass_config} _ass_config_UPPER) - if((NOT ${_ass_config_UPPER} STREQUAL "RELEASE") AND - (NOT ${_ass_config_UPPER} STREQUAL "DEBUG")) - get_target_property(ASS_LIBRARY_RELEASE libass::libass IMPORTED_LOCATION_${_ass_config_UPPER}) - else() - get_target_property(ASS_LIBRARY_${_ass_config_UPPER} libass::libass IMPORTED_LOCATION_${_ass_config_UPPER}) + if(WIN32 OR WINDOWS_STORE) + + set(CMAKE_ARGS -DNATIVEPREFIX=${NATIVEPREFIX} + -DARCH=${ARCH} + ${LIBASS_ADDITIONAL_ARGS}) + else() + find_program(AUTORECONF autoreconf REQUIRED) + if (CMAKE_HOST_SYSTEM_NAME MATCHES "(Free|Net|Open)BSD") + find_program(MAKE_EXECUTABLE gmake) + endif() + find_program(MAKE_EXECUTABLE make REQUIRED) + + if(ARCH STREQUAL x86_64) + find_program(NASM nasm HINTS "${NATIVEPREFIX}/bin") + if(NOT NASM_FOUND) + set(DISABLE_ASM "--disable-asm") + endif() + elseif(NOT ARCH STREQUAL aarch64) + set(DISABLE_ASM "--disable-asm") endif() - endforeach() - get_target_property(ASS_INCLUDE_DIR libass::libass INTERFACE_INCLUDE_DIRECTORIES) - set(ASS_VERSION ${libass_VERSION}) + set(CONFIGURE_COMMAND ${AUTORECONF} -vif + COMMAND ./configure + --prefix=${DEPENDS_PATH} + --disable-shared + --disable-libunibreak + ${DISABLE_ASM}) + + set(BUILD_COMMAND ${MAKE_EXECUTABLE}) + set(INSTALL_COMMAND ${MAKE_EXECUTABLE} install) + set(BUILD_IN_SOURCE 1) + endif() + + BUILD_DEP_TARGET() + + # Link libraries for target interface + set(ASS_LINK_LIBRARIES ${APP_NAME_LC}::FriBidi ${APP_NAME_LC}::Iconv ${APP_NAME_LC}::HarfBuzz ${APP_NAME_LC}::FreeType) + + if(WIN32 OR WINDOWS_STORE) + # Directwrite dependency + list(APPEND ASS_LINK_LIBRARIES dwrite.lib) + elseif(CMAKE_SYSTEM_NAME STREQUAL Darwin) + # Coretext dependencies + list(APPEND ASS_LINK_LIBRARIES "-framework CoreText" + "-framework CoreFoundation") + else() + list(APPEND ASS_LINK_LIBRARIES Fontconfig::Fontconfig) + add_dependencies(${MODULE_LC} Fontconfig::Fontconfig) + endif() + + set(ASS_INCLUDE_DIR ${LIBASS_INCLUDE_DIR}) + set(ASS_LIBRARY_RELEASE ${LIBASS_LIBRARY_RELEASE}) + if(LIBASS_LIBRARY_DEBUG) + set(ASS_LIBRARY_DEBUG ${LIBASS_LIBRARY_DEBUG}) + endif() - include(SelectLibraryConfigurations) - select_library_configurations(ASS) - unset(ASS_LIBRARIES) + # Add dependencies to build target + add_dependencies(${MODULE_LC} ${APP_NAME_LC}::FriBidi) + add_dependencies(${MODULE_LC} ${APP_NAME_LC}::Iconv) + add_dependencies(${MODULE_LC} ${APP_NAME_LC}::HarfBuzz) + add_dependencies(${MODULE_LC} ${APP_NAME_LC}::FreeType) + endmacro() + + set(MODULE_LC libass) + + SETUP_BUILD_VARS() + + if(WIN32 OR WINDOWS_STORE) + find_package(libass CONFIG QUIET + HINTS ${DEPENDS_PATH}/lib/cmake + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) + + if(libass_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_ASS) + # build internal module + buildlibASS() + else() + + # we only do this because we use find_package_handle_standard_args for config time output + # and it isnt capable of handling TARGETS, so we have to extract the info + get_target_property(_ASS_CONFIGURATIONS libass::libass IMPORTED_CONFIGURATIONS) + foreach(_ass_config IN LISTS _ASS_CONFIGURATIONS) + # Some non standard config (eg None on Debian) + # Just set to RELEASE var so select_library_configurations can continue to work its magic + string(TOUPPER ${_ass_config} _ass_config_UPPER) + if((NOT ${_ass_config_UPPER} STREQUAL "RELEASE") AND + (NOT ${_ass_config_UPPER} STREQUAL "DEBUG")) + get_target_property(ASS_LIBRARY_RELEASE libass::libass IMPORTED_LOCATION_${_ass_config_UPPER}) + else() + get_target_property(ASS_LIBRARY_${_ass_config_UPPER} libass::libass IMPORTED_LOCATION_${_ass_config_UPPER}) + endif() + endforeach() + + get_target_property(ASS_INCLUDE_DIR libass::libass INTERFACE_INCLUDE_DIRECTORIES) + set(ASS_VERSION ${libass_VERSION}) + + endif() + else() + find_package(PkgConfig REQUIRED) + pkg_check_modules(PC_ASS libass QUIET IMPORTED_TARGET) + + if((PC_ASS_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_ASS) OR + ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_ASS)) + # build internal module + buildlibASS() + else() + # INTERFACE_LINK_OPTIONS is incorrectly populated when cmake generation is executed + # when an existing build generation is already done. Just set this to blank + set_target_properties(PkgConfig::PC_ASS PROPERTIES INTERFACE_LINK_OPTIONS "") + + # First item is the full path of the library file found + # pkg_check_modules does not populate a variable of the found library explicitly + list(GET PC_ASS_LINK_LIBRARIES 0 ASS_LIBRARY_RELEASE) + set(ASS_INCLUDE_DIR ${PC_ASS_INCLUDEDIR}) + set(ASS_LINK_LIBRARIES ${PC_ASS_LINK_LIBRARIES}) + set(ASS_VERSION ${PC_ASS_VERSION}) + endif() endif() + include(SelectLibraryConfigurations) + select_library_configurations(ASS) + unset(ASS_LIBRARIES) + include(FindPackageHandleStandardArgs) find_package_handle_standard_args(ASS REQUIRED_VARS ASS_LIBRARY ASS_INCLUDE_DIR VERSION_VAR ASS_VERSION) if(ASS_FOUND) - if(TARGET PkgConfig::PC_ASS) + if(TARGET PkgConfig::PC_ASS AND NOT TARGET libass) add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS PkgConfig::PC_ASS) - elseif(TARGET libass::libass) + elseif(TARGET libass::libass AND NOT TARGET libass) # Kodi custom libass target used for windows platforms add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} ALIAS libass::libass) + else() + add_library(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} UNKNOWN IMPORTED) + if(ASS_LIBRARY_RELEASE) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${ASS_LIBRARY_RELEASE}") + endif() + if(ASS_LIBRARY_DEBUG) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + IMPORTED_LOCATION_DEBUG "${ASS_LIBRARY_DEBUG}") + set_property(TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + endif() + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${ASS_INCLUDE_DIR}") + + if(ASS_LINK_LIBRARIES) + set_target_properties(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} PROPERTIES + INTERFACE_LINK_LIBRARIES "${ASS_LINK_LIBRARIES}") + endif() + endif() + + if(TARGET libass) + add_dependencies(${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME} libass) + endif() + + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + if(NOT TARGET libass) + buildlibASS() + set_target_properties(libass PROPERTIES EXCLUDE_FROM_ALL TRUE) + endif() + add_dependencies(build_internal_depends libass) endif() else() if(ASS_FIND_REQUIRED) diff --git a/cmake/modules/FindFriBidi.cmake b/cmake/modules/FindFriBidi.cmake index 52bf29b749747..8467dd9686d74 100644 --- a/cmake/modules/FindFriBidi.cmake +++ b/cmake/modules/FindFriBidi.cmake @@ -1,11 +1,11 @@ #.rst: -# FindFribidi +# FindFriBidi # ----------- # Finds the GNU FriBidi library # # This will define the following target: # -# ${APP_NAME_LC}::Fribidi - The FriBidi library +# ${APP_NAME_LC}::FriBidi - The FriBidi library if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME}) find_package(PkgConfig) diff --git a/project/BuildDependencies/scripts/0_package.target-win10-arm.list b/project/BuildDependencies/scripts/0_package.target-win10-arm.list index 264f68847db38..87935c0f6ee04 100644 --- a/project/BuildDependencies/scripts/0_package.target-win10-arm.list +++ b/project/BuildDependencies/scripts/0_package.target-win10-arm.list @@ -15,7 +15,6 @@ GoogleTest-1.10.0-win10-arm-v141-20200410.7z harfbuzz-2.8.0-win10-arm-v142-20210602.7z lcms2-2.9-win10-arm-v141-20200105.7z libaacs-0.9.0-win10-arm-v141-20200203.7z -libass-0.17.1-win10-arm-v142-20230619.7z libbdplus-0.1.2-win10-arm-v141-20200105.7z libbluray-1.3.4-win10-arm-v142-20230728.7z libcdio-2.1.0-win10-arm-v141-20200112.7z diff --git a/project/BuildDependencies/scripts/0_package.target-win10-win32.list b/project/BuildDependencies/scripts/0_package.target-win10-win32.list index b2ad19e4ade26..a7bdf28b20303 100644 --- a/project/BuildDependencies/scripts/0_package.target-win10-win32.list +++ b/project/BuildDependencies/scripts/0_package.target-win10-win32.list @@ -15,7 +15,6 @@ GoogleTest-1.10.0-win10-win32-v141-20200410.7z harfbuzz-2.8.0-win10-win32-v142-20210602.7z lcms2-2.9-win10-win32-v141-20200105.7z libaacs-0.9.0-win10-win32-v141-20200203.7z -libass-0.17.1-win10-win32-v142-20230619.7z libbdplus-0.1.2-win10-win32-v141-20200105.7z libbluray-1.3.4-win10-win32-v142-20230728.7z libcdio-2.1.0-win10-win32-v141-20200112.7z diff --git a/project/BuildDependencies/scripts/0_package.target-win10-x64.list b/project/BuildDependencies/scripts/0_package.target-win10-x64.list index 6a44f9f4d8e50..3c289e4c791c5 100644 --- a/project/BuildDependencies/scripts/0_package.target-win10-x64.list +++ b/project/BuildDependencies/scripts/0_package.target-win10-x64.list @@ -15,7 +15,6 @@ GoogleTest-1.10.0-win10-x64-v141-20200410.7z harfbuzz-2.8.0-win10-x64-v142-20210602.7z lcms2-2.9-win10-x64-v141-20200105.7z libaacs-0.9.0-win10-x64-v141-20200203.7z -libass-0.17.1-win10-x64-v142-20230619.7z libbdplus-0.1.2-win10-x64-v141-20200105.7z libbluray-1.3.4-win10-x64-v142-20230728.7z libcdio-2.1.0-win10-x64-v141-20200112.7z diff --git a/project/BuildDependencies/scripts/0_package.target-win32.list b/project/BuildDependencies/scripts/0_package.target-win32.list index f87dbf5d67e78..2bc44b92de2d6 100644 --- a/project/BuildDependencies/scripts/0_package.target-win32.list +++ b/project/BuildDependencies/scripts/0_package.target-win32.list @@ -18,7 +18,6 @@ GoogleTest-1.10.0-win32-v141-20200410.7z harfbuzz-2.8.0-win32-v142-20210602.7z lcms2-2.9-win32-v141-20200105.7z libaacs-0.9.0-win32-v141-20200203.7z -libass-0.17.1-win32-v142-20230619.7z libbdplus-0.1.2-win32-v141-20200105.7z libbluray-1.3.4-win32-v142-20230728.7z libcdio-2.1.0-win32-v141-20200112.7z diff --git a/project/BuildDependencies/scripts/0_package.target-x64.list b/project/BuildDependencies/scripts/0_package.target-x64.list index b2705edd96d12..2474d09ca5da1 100644 --- a/project/BuildDependencies/scripts/0_package.target-x64.list +++ b/project/BuildDependencies/scripts/0_package.target-x64.list @@ -17,7 +17,6 @@ GoogleTest-1.10.0-x64-v141-20200410.7z harfbuzz-2.8.0-x64-v142-20210602.7z lcms2-2.9-x64-v141-20200105.7z libaacs-0.9.0-x64-v141-20200203.7z -libass-0.17.1-x64-v142-20230619.7z libbdplus-0.1.2-x64-v141-20200105.7z libbluray-1.3.4-x64-v142-20230728.7z libcdio-2.1.0-x64-v141-20200112.7z diff --git a/tools/depends/target/Makefile b/tools/depends/target/Makefile index 2ef7812a0fc73..17a4cd3b223ec 100644 --- a/tools/depends/target/Makefile +++ b/tools/depends/target/Makefile @@ -22,7 +22,6 @@ DEPENDS = \ gnutls \ gtest \ harfbuzz \ - libass \ libbluray \ libffi \ libgcrypt \ @@ -55,8 +54,10 @@ else DEPENDS+=samba libcdio endif +FONTCONFIG=fontconfig ifeq ($(OS),darwin_embedded) EXCLUDED_DEPENDS = libusb gtest + FONTCONFIG= ifeq ($(TARGET_PLATFORM),appletvos) EXCLUDED_DEPENDS += libshairplay libplist endif @@ -64,6 +65,7 @@ ifeq ($(OS),darwin_embedded) endif ifeq ($(OS),osx) + FONTCONFIG= EXCLUDED_DEPENDS = libusb DEPENDS += smctemp endif @@ -160,7 +162,7 @@ freetype2: bzip2 harfbuzz $(ZLIB) gettext: $(ICONV) gnutls: nettle $(ZLIB) harfbuzz: freetype2-noharfbuzz $(ICONV) -libass: fontconfig fribidi harfbuzz freetype2 $(ICONV) +libass: $(FONTCONFIG) fribidi harfbuzz freetype2 $(ICONV) libbluray: fontconfig freetype2 $(ICONV) udfread libxml2 libcdio-gplv3: $(ICONV) libcdio: $(ICONV) diff --git a/tools/depends/target/libass/LIBASS-VERSION b/tools/depends/target/libass/LIBASS-VERSION index 4f8ee758b3cc0..90ad4e2d80bd1 100644 --- a/tools/depends/target/libass/LIBASS-VERSION +++ b/tools/depends/target/libass/LIBASS-VERSION @@ -3,3 +3,5 @@ VERSION=0.17.3 ARCHIVE=$(LIBNAME)-$(VERSION).tar.xz SHA512=c8da55d38159d45838b359547ac1f83aa40665448b71e404ec8bdce68e0b3f46e80ab88c0194697bff0275d88ca86d58a0cf8a8cbc6dfc3ee1dc40d33d532dfc BYPRODUCT=libass.a +BYPRODUCT_WIN=libass.lib + From a1e4d33a29f9eb67b463db521405b5cc114ca186 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:54:21 +0100 Subject: [PATCH 622/651] [video] CVideoDatabase::SetStreamDetailsForFileId: Fix SQL syntax error. --- xbmc/video/VideoDatabase.cpp | 40 +++++++++++++++++------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 2068480e24db1..39c4fd3629ba9 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -3270,34 +3270,32 @@ void CVideoDatabase::SetStreamDetailsForFileId(const CStreamDetails& details, in for (int i=1; i<=details.GetVideoStreamCount(); i++) { - m_pDS->exec(PrepareSQL("INSERT INTO streamdetails " - "(idFile, iStreamType, strVideoCodec, fVideoAspect, iVideoWidth, " - "iVideoHeight, iVideoDuration, strStereoMode, strVideoLanguage, " - "strHdrType)" - "VALUES (%i,%i,'%s',%f,%i,%i,%i,'%s','%s','%s')", - idFile, (int)CStreamDetail::VIDEO, details.GetVideoCodec(i).c_str(), - static_cast<double>(details.GetVideoAspect(i)), - details.GetVideoWidth(i), details.GetVideoHeight(i), - details.GetVideoDuration(i), details.GetStereoMode(i).c_str(), - details.GetVideoLanguage(i).c_str(), - details.GetVideoHdrType(i).c_str())); + m_pDS->exec(PrepareSQL( + "INSERT INTO streamdetails " + "(idFile, iStreamType, strVideoCodec, fVideoAspect, iVideoWidth, iVideoHeight, " + "iVideoDuration, strStereoMode, strVideoLanguage, strHdrType) " + "VALUES (%i,%i,'%s',%f,%i,%i,%i,'%s','%s','%s')", + idFile, static_cast<int>(CStreamDetail::VIDEO), details.GetVideoCodec(i).c_str(), + static_cast<double>(details.GetVideoAspect(i)), details.GetVideoWidth(i), + details.GetVideoHeight(i), details.GetVideoDuration(i), details.GetStereoMode(i).c_str(), + details.GetVideoLanguage(i).c_str(), details.GetVideoHdrType(i).c_str())); } for (int i=1; i<=details.GetAudioStreamCount(); i++) { - m_pDS->exec(PrepareSQL("INSERT INTO streamdetails " - "(idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage) " - "VALUES (%i,%i,'%s',%i,'%s')", - idFile, (int)CStreamDetail::AUDIO, - details.GetAudioCodec(i).c_str(), details.GetAudioChannels(i), - details.GetAudioLanguage(i).c_str())); + m_pDS->exec(PrepareSQL( + "INSERT INTO streamdetails " + "(idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage) " + "VALUES (%i,%i,'%s',%i,'%s')", + idFile, static_cast<int>(CStreamDetail::AUDIO), details.GetAudioCodec(i).c_str(), + details.GetAudioChannels(i), details.GetAudioLanguage(i).c_str())); } for (int i=1; i<=details.GetSubtitleStreamCount(); i++) { m_pDS->exec(PrepareSQL("INSERT INTO streamdetails " - "(idFile, iStreamType, strSubtitleLanguage) " - "VALUES (%i,%i,'%s')", - idFile, (int)CStreamDetail::SUBTITLE, - details.GetSubtitleLanguage(i).c_str())); + "(idFile, iStreamType, strSubtitleLanguage) " + "VALUES (%i,%i,'%s')", + idFile, static_cast<int>(CStreamDetail::SUBTITLE), + details.GetSubtitleLanguage(i).c_str())); } // update the runtime information, if empty From 83c92fb2818ea75fe1709bc152c7e53f6ea3f4f2 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 30 Oct 2024 11:09:40 +0000 Subject: [PATCH 623/651] [Utils] Move MillisecondsToTimeString to StringUtils --- xbmc/cores/VideoPlayer/Edl.cpp | 73 ++++++++++++-------------- xbmc/cores/VideoPlayer/Edl.h | 2 - xbmc/cores/VideoPlayer/VideoPlayer.cpp | 10 ++-- xbmc/utils/StringUtils.cpp | 8 +++ xbmc/utils/StringUtils.h | 12 ++++- 5 files changed, 58 insertions(+), 47 deletions(-) diff --git a/xbmc/cores/VideoPlayer/Edl.cpp b/xbmc/cores/VideoPlayer/Edl.cpp index 68cd40bf631c1..cf0659f2a2060 100644 --- a/xbmc/cores/VideoPlayer/Edl.cpp +++ b/xbmc/cores/VideoPlayer/Edl.cpp @@ -589,15 +589,17 @@ bool CEdl::ReadPvr(const CFileItem &fileItem) if (AddEdit(edit)) { CLog::Log(LOGDEBUG, "{} - Added break [{} - {}] found in PVR item for: {}.", __FUNCTION__, - MillisecondsToTimeString(edit.start), MillisecondsToTimeString(edit.end), + StringUtils::MillisecondsToTimeString(edit.start), + StringUtils::MillisecondsToTimeString(edit.end), CURL::GetRedacted(fileItem.GetDynPath())); } else { CLog::Log(LOGERROR, "{} - Invalid break [{} - {}] found in PVR item for: {}. Continuing anyway.", - __FUNCTION__, MillisecondsToTimeString(edit.start), - MillisecondsToTimeString(edit.end), CURL::GetRedacted(fileItem.GetDynPath())); + __FUNCTION__, StringUtils::MillisecondsToTimeString(edit.start), + StringUtils::MillisecondsToTimeString(edit.end), + CURL::GetRedacted(fileItem.GetDynPath())); } break; @@ -627,32 +629,32 @@ bool CEdl::AddEdit(const Edit& newEdit) { CLog::Log(LOGERROR, "{} - Not an Action::CUT, Action::MUTE, or Action::COMM_BREAK! [{} - {}], {}", - __FUNCTION__, MillisecondsToTimeString(edit.start), - MillisecondsToTimeString(edit.end), static_cast<int>(edit.action)); + __FUNCTION__, StringUtils::MillisecondsToTimeString(edit.start), + StringUtils::MillisecondsToTimeString(edit.end), static_cast<int>(edit.action)); return false; } if (edit.start < 0ms) { CLog::Log(LOGERROR, "{} - Before start! [{} - {}], {}", __FUNCTION__, - MillisecondsToTimeString(edit.start), MillisecondsToTimeString(edit.end), - static_cast<int>(edit.action)); + StringUtils::MillisecondsToTimeString(edit.start), + StringUtils::MillisecondsToTimeString(edit.end), static_cast<int>(edit.action)); return false; } if (edit.start >= edit.end) { CLog::Log(LOGERROR, "{} - Times are around the wrong way or the same! [{} - {}], {}", - __FUNCTION__, MillisecondsToTimeString(edit.start), - MillisecondsToTimeString(edit.end), static_cast<int>(edit.action)); + __FUNCTION__, StringUtils::MillisecondsToTimeString(edit.start), + StringUtils::MillisecondsToTimeString(edit.end), static_cast<int>(edit.action)); return false; } if (InEdit(edit.start) || InEdit(edit.end)) { CLog::Log(LOGERROR, "{} - Start or end is in an existing edit! [{} - {}], {}", __FUNCTION__, - MillisecondsToTimeString(edit.start), MillisecondsToTimeString(edit.end), - static_cast<int>(edit.action)); + StringUtils::MillisecondsToTimeString(edit.start), + StringUtils::MillisecondsToTimeString(edit.end), static_cast<int>(edit.action)); return false; } @@ -661,8 +663,8 @@ bool CEdl::AddEdit(const Edit& newEdit) if (edit.start < m_vecEdits[i].start && edit.end > m_vecEdits[i].end) { CLog::Log(LOGERROR, "{} - Edit surrounds an existing edit! [{} - {}], {}", __FUNCTION__, - MillisecondsToTimeString(edit.start), MillisecondsToTimeString(edit.end), - static_cast<int>(edit.action)); + StringUtils::MillisecondsToTimeString(edit.start), + StringUtils::MillisecondsToTimeString(edit.end), static_cast<int>(edit.action)); return false; } } @@ -703,8 +705,8 @@ bool CEdl::AddEdit(const Edit& newEdit) if (m_vecEdits.empty() || edit.start > m_vecEdits.back().start) { CLog::Log(LOGDEBUG, "{} - Pushing new edit to back [{} - {}], {}", __FUNCTION__, - MillisecondsToTimeString(edit.start), MillisecondsToTimeString(edit.end), - static_cast<int>(edit.action)); + StringUtils::MillisecondsToTimeString(edit.start), + StringUtils::MillisecondsToTimeString(edit.end), static_cast<int>(edit.action)); m_vecEdits.emplace_back(edit); } else @@ -715,8 +717,8 @@ bool CEdl::AddEdit(const Edit& newEdit) if (edit.start < pCurrentEdit->start) { CLog::Log(LOGDEBUG, "{} - Inserting new edit [{} - {}], {}", __FUNCTION__, - MillisecondsToTimeString(edit.start), MillisecondsToTimeString(edit.end), - static_cast<int>(edit.action)); + StringUtils::MillisecondsToTimeString(edit.start), + StringUtils::MillisecondsToTimeString(edit.end), static_cast<int>(edit.action)); m_vecEdits.insert(pCurrentEdit, edit); break; } @@ -736,7 +738,7 @@ bool CEdl::AddSceneMarker(std::chrono::milliseconds iSceneMarker) return false; CLog::Log(LOGDEBUG, "{} - Inserting new scene marker: {}", __FUNCTION__, - MillisecondsToTimeString(iSceneMarker)); + StringUtils::MillisecondsToTimeString(iSceneMarker)); m_vecSceneMarkers.push_back(iSceneMarker); // Unsorted return true; @@ -950,14 +952,6 @@ std::optional<std::chrono::milliseconds> CEdl::GetNextSceneMarker(Direction dire return sceneMarker; } -std::string CEdl::MillisecondsToTimeString(std::chrono::milliseconds milliSeconds) -{ - std::string strTimeString = StringUtils::SecondsToTimeString( - std::chrono::duration_cast<std::chrono::seconds>(milliSeconds).count(), TIME_FORMAT_HH_MM_SS); - strTimeString += StringUtils::Format(".{:03}", milliSeconds.count() % 1000); - return strTimeString; -} - void CEdl::MergeShortCommBreaks() { /* @@ -970,8 +964,8 @@ void CEdl::MergeShortCommBreaks() (m_vecEdits[0].end - m_vecEdits[0].start) < 5s) { CLog::Log(LOGDEBUG, "{} - Removing short commercial break at start [{} - {}]. <5 seconds", - __FUNCTION__, MillisecondsToTimeString(m_vecEdits[0].start), - MillisecondsToTimeString(m_vecEdits[0].end)); + __FUNCTION__, StringUtils::MillisecondsToTimeString(m_vecEdits[0].start), + StringUtils::MillisecondsToTimeString(m_vecEdits[0].end)); m_vecEdits.erase(m_vecEdits.begin()); } @@ -994,13 +988,14 @@ void CEdl::MergeShortCommBreaks() commBreak.start = m_vecEdits[i].start; commBreak.end = m_vecEdits[i + 1].end; - CLog::Log( - LOGDEBUG, "{} - Consolidating commercial break [{} - {}] and [{} - {}] to: [{} - {}]", - __FUNCTION__, MillisecondsToTimeString(m_vecEdits[i].start), - MillisecondsToTimeString(m_vecEdits[i].end), - MillisecondsToTimeString(m_vecEdits[i + 1].start), - MillisecondsToTimeString(m_vecEdits[i + 1].end), - MillisecondsToTimeString(commBreak.start), MillisecondsToTimeString(commBreak.end)); + CLog::Log(LOGDEBUG, + "{} - Consolidating commercial break [{} - {}] and [{} - {}] to: [{} - {}]", + __FUNCTION__, StringUtils::MillisecondsToTimeString(m_vecEdits[i].start), + StringUtils::MillisecondsToTimeString(m_vecEdits[i].end), + StringUtils::MillisecondsToTimeString(m_vecEdits[i + 1].start), + StringUtils::MillisecondsToTimeString(m_vecEdits[i + 1].end), + StringUtils::MillisecondsToTimeString(commBreak.start), + StringUtils::MillisecondsToTimeString(commBreak.end)); /* * Erase old edits and insert the new merged one. @@ -1023,8 +1018,8 @@ void CEdl::MergeShortCommBreaks() std::chrono::seconds(advancedSettings->m_iEdlMaxStartGap))) { CLog::Log(LOGDEBUG, "{} - Expanding first commercial break back to start [{} - {}].", - __FUNCTION__, MillisecondsToTimeString(m_vecEdits[0].start), - MillisecondsToTimeString(m_vecEdits[0].end)); + __FUNCTION__, StringUtils::MillisecondsToTimeString(m_vecEdits[0].start), + StringUtils::MillisecondsToTimeString(m_vecEdits[0].end)); m_vecEdits[0].start = 0ms; } @@ -1040,8 +1035,8 @@ void CEdl::MergeShortCommBreaks() { CLog::Log(LOGDEBUG, "{} - Removing short commercial break [{} - {}]. Minimum length: {} seconds", - __FUNCTION__, MillisecondsToTimeString(m_vecEdits[i].start), - MillisecondsToTimeString(m_vecEdits[i].end), + __FUNCTION__, StringUtils::MillisecondsToTimeString(m_vecEdits[i].start), + StringUtils::MillisecondsToTimeString(m_vecEdits[i].end), advancedSettings->m_iEdlMinCommBreakLength); m_vecEdits.erase(m_vecEdits.begin() + i); diff --git a/xbmc/cores/VideoPlayer/Edl.h b/xbmc/cores/VideoPlayer/Edl.h index eaf5e295d1ece..22e229585ff57 100644 --- a/xbmc/cores/VideoPlayer/Edl.h +++ b/xbmc/cores/VideoPlayer/Edl.h @@ -152,8 +152,6 @@ class CEdl std::optional<std::chrono::milliseconds> GetNextSceneMarker(Direction direction, std::chrono::milliseconds clockTime); - static std::string MillisecondsToTimeString(std::chrono::milliseconds milliSeconds); - private: // total cut time (edl cuts) in ms std::chrono::milliseconds m_totalCutTime; diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index a441b556ea6ed..f8fabbb08075a 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -2433,8 +2433,9 @@ void CVideoPlayer::CheckAutoSceneSkip() (m_playSpeed < 0 && correctClock < (edit->end - 1s))) { CLog::Log(LOGDEBUG, "{} - Clock in EDL cut [{} - {}]: {}. Automatically skipping over.", - __FUNCTION__, CEdl::MillisecondsToTimeString(edit->start), - CEdl::MillisecondsToTimeString(edit->end), CEdl::MillisecondsToTimeString(clock)); + __FUNCTION__, StringUtils::MillisecondsToTimeString(edit->start), + StringUtils::MillisecondsToTimeString(edit->end), + StringUtils::MillisecondsToTimeString(clock)); // Seeking either goes to the start or the end of the cut depending on the play direction. std::chrono::milliseconds seek = m_playSpeed >= 0 ? edit->end : edit->start; @@ -2473,8 +2474,9 @@ void CVideoPlayer::CheckAutoSceneSkip() CLog::Log(LOGDEBUG, "{} - Clock in commercial break [{} - {}]: {}. Automatically skipping to end of " "commercial break", - __FUNCTION__, CEdl::MillisecondsToTimeString(edit->start), - CEdl::MillisecondsToTimeString(edit->end), CEdl::MillisecondsToTimeString(clock)); + __FUNCTION__, StringUtils::MillisecondsToTimeString(edit->start), + StringUtils::MillisecondsToTimeString(edit->end), + StringUtils::MillisecondsToTimeString(clock)); CDVDMsgPlayerSeek::CMode mode; mode.time = edit->end.count(); diff --git a/xbmc/utils/StringUtils.cpp b/xbmc/utils/StringUtils.cpp index 1da187446dc90..9ddbcf1d80d79 100644 --- a/xbmc/utils/StringUtils.cpp +++ b/xbmc/utils/StringUtils.cpp @@ -1509,6 +1509,14 @@ std::string StringUtils::SecondsToTimeString(long lSeconds, TIME_FORMAT format) return strHMS; } +std::string StringUtils::MillisecondsToTimeString(std::chrono::milliseconds milliSeconds) +{ + std::string strTimeString = StringUtils::SecondsToTimeString( + std::chrono::duration_cast<std::chrono::seconds>(milliSeconds).count(), TIME_FORMAT_HH_MM_SS); + strTimeString += StringUtils::Format(".{:03}", milliSeconds.count() % 1000); + return strTimeString; +} + bool StringUtils::IsNaturalNumber(const std::string& str) { size_t i = 0, n = 0; diff --git a/xbmc/utils/StringUtils.h b/xbmc/utils/StringUtils.h index dc30c8cdbda8e..b24110b25dabe 100644 --- a/xbmc/utils/StringUtils.h +++ b/xbmc/utils/StringUtils.h @@ -19,12 +19,13 @@ // //------------------------------------------------------------------------ +#include <chrono> +#include <locale> +#include <sstream> #include <stdarg.h> #include <stdint.h> #include <string> #include <vector> -#include <sstream> -#include <locale> // workaround for broken [[deprecated]] in coverity #if defined(__COVERITY__) @@ -268,6 +269,13 @@ class StringUtils */ static std::string SecondsToTimeString(long seconds, TIME_FORMAT format = TIME_FORMAT_GUESS); + /*! \brief convert a milliseconds value to a time string in the TIME_FORMAT_HH_MM_SS format + \param milliSeconds time in milliseconds + \return the formatted time + \sa TIME_FORMAT + */ + static std::string MillisecondsToTimeString(std::chrono::milliseconds milliSeconds); + /*! \brief check whether a string is a natural number. Matches [ \t]*[0-9]+[ \t]* \param str the string to check From 8cc641479983af3ad67f39891ea37cc8f17ee021 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Wed, 30 Oct 2024 09:24:01 -0400 Subject: [PATCH 624/651] [dbwrappers] Detect incorrect transaction usage - nesting is not supported - a transaction must have been started before a commit or rollback --- xbmc/dbwrappers/mysqldataset.cpp | 9 ++++++++- xbmc/dbwrappers/sqlitedataset.cpp | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/xbmc/dbwrappers/mysqldataset.cpp b/xbmc/dbwrappers/mysqldataset.cpp index 5595760d9aaf1..8888859d91f36 100644 --- a/xbmc/dbwrappers/mysqldataset.cpp +++ b/xbmc/dbwrappers/mysqldataset.cpp @@ -567,9 +567,14 @@ void MysqlDatabase::start_transaction() { if (active) { + assert(!_in_transaction); mysql_autocommit(conn, false); CLog::LogFC(LOGDEBUG, LOGDATABASE, "Mysql Start transaction"); - _in_transaction = true; + + if (_in_transaction) + CLog::LogF(LOGERROR, "error: nested transactions are not supported."); + else + _in_transaction = true; } } @@ -577,6 +582,7 @@ void MysqlDatabase::commit_transaction() { if (active) { + assert(_in_transaction); mysql_commit(conn); mysql_autocommit(conn, true); CLog::LogFC(LOGDEBUG, LOGDATABASE, "Mysql commit transaction"); @@ -588,6 +594,7 @@ void MysqlDatabase::rollback_transaction() { if (active) { + assert(_in_transaction); mysql_rollback(conn); mysql_autocommit(conn, true); CLog::LogFC(LOGDEBUG, LOGDATABASE, "Mysql rollback transaction"); diff --git a/xbmc/dbwrappers/sqlitedataset.cpp b/xbmc/dbwrappers/sqlitedataset.cpp index cee89f4a9c14e..1c3996ff4b517 100644 --- a/xbmc/dbwrappers/sqlitedataset.cpp +++ b/xbmc/dbwrappers/sqlitedataset.cpp @@ -566,9 +566,14 @@ void SqliteDatabase::start_transaction() { if (active) { + assert(!_in_transaction); sqlite3_exec(conn, "begin IMMEDIATE", NULL, NULL, NULL); CLog::LogFC(LOGDEBUG, LOGDATABASE, "Sqlite start transaction"); - _in_transaction = true; + + if (_in_transaction) + CLog::LogF(LOGERROR, "error: nested transactions are not supported."); + else + _in_transaction = true; } } @@ -576,6 +581,7 @@ void SqliteDatabase::commit_transaction() { if (active) { + assert(_in_transaction); sqlite3_exec(conn, "commit", NULL, NULL, NULL); CLog::LogFC(LOGDEBUG, LOGDATABASE, "Sqlite commit transaction"); _in_transaction = false; @@ -586,6 +592,7 @@ void SqliteDatabase::rollback_transaction() { if (active) { + assert(_in_transaction); sqlite3_exec(conn, "rollback", NULL, NULL, NULL); CLog::LogFC(LOGDEBUG, LOGDATABASE, "Sqlite rollback transaction"); _in_transaction = false; From c318e64e1d88620b378ae14d556f30f7a9a00742 Mon Sep 17 00:00:00 2001 From: Lukas Rusak <lorusak@gmail.com> Date: Wed, 30 Oct 2024 13:26:06 -0700 Subject: [PATCH 625/651] PythonBindings: TypeInfo: add initialization of tp_versions_used for PyTypeObject This member was added in upstream commit https://github.com/python/cpython/commit/992446dd5bd3fff92ea0f8064fb19eebfe105cef This change first appeared in Python v3.13.0a4 Signed-off-by: Lukas Rusak <lorusak@gmail.com> --- xbmc/interfaces/python/swig.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xbmc/interfaces/python/swig.cpp b/xbmc/interfaces/python/swig.cpp index 20b97ac0463e8..c1af7011122ba 100644 --- a/xbmc/interfaces/python/swig.cpp +++ b/xbmc/interfaces/python/swig.cpp @@ -74,6 +74,9 @@ namespace PythonBindings #endif #if PY_VERSION_HEX >= 0x030C00A1 0, +#endif +#if PY_VERSION_HEX >= 0x030D00A4 + 0, #endif }; From a46cf98757187ff672fd8d3765680b4be96b3a6e Mon Sep 17 00:00:00 2001 From: fuzzard <fuzzard@kodi.tv> Date: Thu, 31 Oct 2024 09:38:13 +1000 Subject: [PATCH 626/651] [tools/depends][native] python fix multithread dir creation failure At times we run across the following CI failure 07:12:39 install: mkdir /Users/Shared/jenkins/workspace/OSX-64/tools/depends/xbmc-depends/arm-darwin22.6.0-native/lib/python3.12: File exists 07:12:39 make[3]: *** [sharedinstall] Error 71 Make the install just use a single thread. We do this in the target python3 install already for the same reason. --- tools/depends/native/python3/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/depends/native/python3/Makefile b/tools/depends/native/python3/Makefile index bb93cc87e7940..cac77a108d7bb 100644 --- a/tools/depends/native/python3/Makefile +++ b/tools/depends/native/python3/Makefile @@ -31,7 +31,8 @@ $(LIBDYLIB): $(PLATFORM) cd $(PLATFORM); $(MAKE) .installed-$(PLATFORM): $(LIBDYLIB) - cd $(PLATFORM); $(MAKE) install +# We specifically use -j1 as some threading issues can occur with install directory creation + cd $(PLATFORM); $(MAKE) install -j1 touch $@ clean: From f89e5f3fbcfb9057d2d0eb5ecc7f7c5b5d7ab3d2 Mon Sep 17 00:00:00 2001 From: sarbes <sarbes@kodi.tv> Date: Thu, 31 Oct 2024 01:59:15 +0100 Subject: [PATCH 627/651] Texturepacker: identify swizzle of uniform white/opaque texture as RRR1 --- .../native/TexturePacker/src/TexturePacker.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/depends/native/TexturePacker/src/TexturePacker.cpp b/tools/depends/native/TexturePacker/src/TexturePacker.cpp index 9d4f270b23c0e..638279797f368 100644 --- a/tools/depends/native/TexturePacker/src/TexturePacker.cpp +++ b/tools/depends/native/TexturePacker/src/TexturePacker.cpp @@ -344,13 +344,7 @@ void TexturePacker::ReduceChannels(RGBAImage& image) if (uniformRed && uniformGreen && uniformBlue && !isWhite) printf("WARNING: uniform color detected! Consider using diffusecolor!\n"); - if (isIntensity) - { - // this is an intensity (GL_INTENSITY) texture - ConvertToSingleChannel(image, 0); - image.textureSwizzle = KD_TEX_SWIZ_RRRR; - } - else if (uniformAlpha && alpha == 0xff) + if (uniformAlpha && alpha == 0xff) { // we have a opaque texture, L or RGBX if (isGrey) @@ -366,6 +360,12 @@ void TexturePacker::ReduceChannels(RGBAImage& image) ConvertToSingleChannel(image, 3); image.textureSwizzle = KD_TEX_SWIZ_111R; } + else if (isIntensity) + { + // this is an intensity (GL_INTENSITY) texture + ConvertToSingleChannel(image, 0); + image.textureSwizzle = KD_TEX_SWIZ_RRRR; + } else if (isGrey) { // a LA texture From dff03f59457331ca6c1101494caaccf75d3d0023 Mon Sep 17 00:00:00 2001 From: sarbes <sarbes@kodi.tv> Date: Thu, 31 Oct 2024 16:26:17 +0100 Subject: [PATCH 628/651] Estuary: make dialog header opaque --- addons/skin.estuary/colors/brown.xml | 1 + addons/skin.estuary/colors/charcoal.xml | 1 + addons/skin.estuary/colors/chartreuse.xml | 1 + addons/skin.estuary/colors/concrete.xml | 1 + addons/skin.estuary/colors/defaults.xml | 1 + addons/skin.estuary/colors/gold.xml | 1 + addons/skin.estuary/colors/green.xml | 1 + addons/skin.estuary/colors/maroon.xml | 1 + addons/skin.estuary/colors/midnight.xml | 1 + addons/skin.estuary/colors/orange.xml | 1 + addons/skin.estuary/colors/pink.xml | 1 + addons/skin.estuary/colors/rose.xml | 1 + addons/skin.estuary/colors/teal.xml | 1 + addons/skin.estuary/colors/violet.xml | 1 + addons/skin.estuary/xml/Includes.xml | 10 +++++----- 15 files changed, 19 insertions(+), 5 deletions(-) diff --git a/addons/skin.estuary/colors/brown.xml b/addons/skin.estuary/colors/brown.xml index e75f74dafccb1..f5ec8f6bad515 100644 --- a/addons/skin.estuary/colors/brown.xml +++ b/addons/skin.estuary/colors/brown.xml @@ -3,6 +3,7 @@ <color name="primary_background">FF563529</color> <color name="secondary_background">33738627</color> <color name="dialog_tint">FF282524</color> + <color name="dialog_header_tint">FF674336</color> <color name="button_focus">FF83513F</color> <color name="button_alt_focus">8083513F</color> <color name="selected">FFFF4400</color> diff --git a/addons/skin.estuary/colors/charcoal.xml b/addons/skin.estuary/colors/charcoal.xml index 7229160b85e0c..165d85d3f103b 100644 --- a/addons/skin.estuary/colors/charcoal.xml +++ b/addons/skin.estuary/colors/charcoal.xml @@ -3,6 +3,7 @@ <color name="primary_background">FF666666</color> <color name="secondary_background">4D808080</color> <color name="dialog_tint">FF262626</color> + <color name="dialog_header_tint">FF7C7C7C</color> <color name="button_focus">FFA2A2A2</color> <color name="button_alt_focus">80A2A2A2</color> <color name="selected">FFFFB70F</color> diff --git a/addons/skin.estuary/colors/chartreuse.xml b/addons/skin.estuary/colors/chartreuse.xml index 77bf016ae8433..32d0e05ad5aea 100644 --- a/addons/skin.estuary/colors/chartreuse.xml +++ b/addons/skin.estuary/colors/chartreuse.xml @@ -3,6 +3,7 @@ <color name="primary_background">FF3B5E12</color> <color name="secondary_background">3398FF05</color> <color name="dialog_tint">FF202920</color> + <color name="dialog_header_tint">FF408609</color> <color name="button_focus">FF4FAF00</color> <color name="button_alt_focus">804FAF00</color> <color name="selected">FF24C6C9</color> diff --git a/addons/skin.estuary/colors/concrete.xml b/addons/skin.estuary/colors/concrete.xml index 366c7d8ffd4b0..bb2c642c6e825 100644 --- a/addons/skin.estuary/colors/concrete.xml +++ b/addons/skin.estuary/colors/concrete.xml @@ -3,6 +3,7 @@ <color name="primary_background">FF4B5F69</color> <color name="secondary_background">33DA845C</color> <color name="dialog_tint">FF242728</color> + <color name="dialog_header_tint">FF4D636D</color> <color name="button_focus">FF607D8B</color> <color name="button_alt_focus">80607D8B</color> <color name="selected">FFFF8C00</color> diff --git a/addons/skin.estuary/colors/defaults.xml b/addons/skin.estuary/colors/defaults.xml index 4d3b3b6bc7b9b..24648aa860366 100644 --- a/addons/skin.estuary/colors/defaults.xml +++ b/addons/skin.estuary/colors/defaults.xml @@ -3,6 +3,7 @@ <color name="primary_background">FF0E597E</color> <color name="secondary_background">330BAA8E</color> <color name="dialog_tint">FF1A2123</color> + <color name="dialog_header_tint">FF147995</color> <color name="background">FF000000</color> <color name="bg_image">FF909090</color> <color name="bg_overlay">30FFFFFF</color> diff --git a/addons/skin.estuary/colors/gold.xml b/addons/skin.estuary/colors/gold.xml index 7a2399e8edf5e..b15b0002b03e1 100644 --- a/addons/skin.estuary/colors/gold.xml +++ b/addons/skin.estuary/colors/gold.xml @@ -3,6 +3,7 @@ <color name="primary_background">FF966300</color> <color name="secondary_background">33FFFF00</color> <color name="dialog_tint">FF2B2B22</color> + <color name="dialog_header_tint">FF9D810A</color> <color name="button_focus">FFCFA700</color> <color name="button_alt_focus">80CFA700</color> <color name="selected">FFFFF000</color> diff --git a/addons/skin.estuary/colors/green.xml b/addons/skin.estuary/colors/green.xml index 9aa86d5454b4b..d8a65fdc1e5c0 100644 --- a/addons/skin.estuary/colors/green.xml +++ b/addons/skin.estuary/colors/green.xml @@ -3,6 +3,7 @@ <color name="primary_background">FF1C6E45</color> <color name="secondary_background">3300A3CC</color> <color name="dialog_tint">FF1F2722</color> + <color name="dialog_header_tint">FF22995F</color> <color name="button_focus">FF24CA7A</color> <color name="button_alt_focus">8024CA7A</color> <color name="selected">FF14D519</color> diff --git a/addons/skin.estuary/colors/maroon.xml b/addons/skin.estuary/colors/maroon.xml index 46ad34949f4b4..5a5c6a0ee7635 100644 --- a/addons/skin.estuary/colors/maroon.xml +++ b/addons/skin.estuary/colors/maroon.xml @@ -3,6 +3,7 @@ <color name="primary_background">FF6B1414</color> <color name="secondary_background">33DB2C83</color> <color name="dialog_tint">FF262020</color> + <color name="dialog_header_tint">FF940B09</color> <color name="button_focus">FFC40300</color> <color name="button_alt_focus">80C40300</color> <color name="selected">FF24C6C9</color> diff --git a/addons/skin.estuary/colors/midnight.xml b/addons/skin.estuary/colors/midnight.xml index e35d0bf299787..6daf93064fb88 100644 --- a/addons/skin.estuary/colors/midnight.xml +++ b/addons/skin.estuary/colors/midnight.xml @@ -3,6 +3,7 @@ <color name="primary_background">FF11375C</color> <color name="secondary_background">334F4F9E</color> <color name="dialog_tint">FF181B1E</color> + <color name="dialog_header_tint">FF234F7B</color> <color name="button_focus">FF2866A4</color> <color name="button_alt_focus">802866A4</color> <color name="selected">FF5BE5EE</color> diff --git a/addons/skin.estuary/colors/orange.xml b/addons/skin.estuary/colors/orange.xml index f5eadf8d0a140..576cdb0c21e23 100644 --- a/addons/skin.estuary/colors/orange.xml +++ b/addons/skin.estuary/colors/orange.xml @@ -3,6 +3,7 @@ <color name="primary_background">FFA74012</color> <color name="secondary_background">33BDAE12</color> <color name="dialog_tint">FF2B2621</color> + <color name="dialog_header_tint">FFBF7509</color> <color name="button_focus">FFFF9800</color> <color name="button_alt_focus">80FF9800</color> <color name="selected">FFFFF100</color> diff --git a/addons/skin.estuary/colors/pink.xml b/addons/skin.estuary/colors/pink.xml index aa26c3ff30a46..5211c55bc20f6 100644 --- a/addons/skin.estuary/colors/pink.xml +++ b/addons/skin.estuary/colors/pink.xml @@ -3,6 +3,7 @@ <color name="primary_background">FFA61558</color> <color name="secondary_background">3363BCE9</color> <color name="dialog_tint">FF2B2225</color> + <color name="dialog_header_tint">FFB01F50</color> <color name="button_focus">FFE91E63</color> <color name="button_alt_focus">80E91E63</color> <color name="selected">FF94D800</color> diff --git a/addons/skin.estuary/colors/rose.xml b/addons/skin.estuary/colors/rose.xml index f4f2f5d0e9e40..ff67de4ff3f2d 100644 --- a/addons/skin.estuary/colors/rose.xml +++ b/addons/skin.estuary/colors/rose.xml @@ -3,6 +3,7 @@ <color name="primary_background">FFA22A66</color> <color name="secondary_background">33FFB3D7</color> <color name="dialog_tint">FF251F24</color> + <color name="dialog_header_tint">FFBD6C94</color> <color name="button_focus">FFFF8EC4</color> <color name="button_alt_focus">80FF8EC4</color> <color name="selected">FFFF0261</color> diff --git a/addons/skin.estuary/colors/teal.xml b/addons/skin.estuary/colors/teal.xml index 6bca4c1406713..7d71689c40785 100644 --- a/addons/skin.estuary/colors/teal.xml +++ b/addons/skin.estuary/colors/teal.xml @@ -3,6 +3,7 @@ <color name="primary_background">FF00665C</color> <color name="secondary_background">33F07942</color> <color name="dialog_tint">FF222A2A</color> + <color name="dialog_header_tint">FF0A756B</color> <color name="button_focus">FF009688</color> <color name="button_alt_focus">80009688</color> <color name="selected">FFC67F03</color> diff --git a/addons/skin.estuary/colors/violet.xml b/addons/skin.estuary/colors/violet.xml index b6b1dd3f74a7f..67aeb7adb89de 100644 --- a/addons/skin.estuary/colors/violet.xml +++ b/addons/skin.estuary/colors/violet.xml @@ -3,6 +3,7 @@ <color name="primary_background">FF5B1C80</color> <color name="secondary_background">33FFB3D7</color> <color name="dialog_tint">FF27222A</color> + <color name="dialog_header_tint">FF9242BF</color> <color name="button_focus">FFC050FF</color> <color name="button_alt_focus">80C050FF</color> <color name="selected">FFFF0054</color> diff --git a/addons/skin.estuary/xml/Includes.xml b/addons/skin.estuary/xml/Includes.xml index b649528a36b84..0151498531097 100644 --- a/addons/skin.estuary/xml/Includes.xml +++ b/addons/skin.estuary/xml/Includes.xml @@ -930,15 +930,15 @@ <left>0</left> <top>0</top> <right>0</right> - <bottom>0</bottom> - <texture colordiffuse="dialog_tint">colors/white.png</texture> + <height>70</height> + <texture colordiffuse="dialog_header_tint">colors/white.png</texture> </control> <control type="image"> <left>0</left> - <top>0</top> + <top>70</top> <right>0</right> - <height>70</height> - <texture colordiffuse="button_focus" border="2">colors/white70.png</texture> + <bottom>0</bottom> + <texture colordiffuse="dialog_tint">colors/white.png</texture> </control> <control type="label" id="$PARAM[header_id]"> <left>40</left> From 3657db153f8754f23daf3d09caaaf9aa344a9662 Mon Sep 17 00:00:00 2001 From: sarbes <sarbes@kodi.tv> Date: Thu, 31 Oct 2024 19:28:48 +0100 Subject: [PATCH 629/651] Estuary: improve highlighting of focused elements --- .../media/buttons/thumbnail_focused.png | Bin 83 -> 0 bytes addons/skin.estuary/xml/DialogAddonInfo.xml | 2 +- addons/skin.estuary/xml/DialogMusicInfo.xml | 2 +- addons/skin.estuary/xml/DialogVideoInfo.xml | 4 ++-- addons/skin.estuary/xml/Includes_DialogSelect.xml | 6 +++--- addons/skin.estuary/xml/Includes_PVR.xml | 2 +- addons/skin.estuary/xml/VideoOSDBookmarks.xml | 2 +- addons/skin.estuary/xml/View_501_Banner.xml | 2 +- addons/skin.estuary/xml/View_51_Poster.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 addons/skin.estuary/media/buttons/thumbnail_focused.png diff --git a/addons/skin.estuary/media/buttons/thumbnail_focused.png b/addons/skin.estuary/media/buttons/thumbnail_focused.png deleted file mode 100644 index e9d8dfe105b84cb9655c4bca33b81cfb62a91c6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 83 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc0wmQNuC@UwDNh&25RLQ6Km1h^BzU+a3l0Rf g9`=9uz_*`)cWH0`&G=U<fT|cgUHx3vIVCg!0QZ*``v3p{ diff --git a/addons/skin.estuary/xml/DialogAddonInfo.xml b/addons/skin.estuary/xml/DialogAddonInfo.xml index ca306481359cc..19f1b768dbaea 100644 --- a/addons/skin.estuary/xml/DialogAddonInfo.xml +++ b/addons/skin.estuary/xml/DialogAddonInfo.xml @@ -117,7 +117,7 @@ <top>16</top> <width>393</width> <height>228</height> - <texture border="8" colordiffuse="button_focus">buttons/thumbnail_focused.png</texture> + <texture border="4" infill="false" colordiffuse="button_focus">colors/white.png</texture> </control> </focusedlayout> </control> diff --git a/addons/skin.estuary/xml/DialogMusicInfo.xml b/addons/skin.estuary/xml/DialogMusicInfo.xml index 50d07c0996580..81d2792423a5c 100644 --- a/addons/skin.estuary/xml/DialogMusicInfo.xml +++ b/addons/skin.estuary/xml/DialogMusicInfo.xml @@ -342,7 +342,7 @@ <top>6</top> <width>218</width> <height>280</height> - <texture border="8" colordiffuse="button_focus">buttons/thumbnail_focused.png</texture> + <texture border="4" infill="false" colordiffuse="button_focus">colors/white.png</texture> <animation effect="fade" start="100" end="0" time="200">Unfocus</animation> </control> </control> diff --git a/addons/skin.estuary/xml/DialogVideoInfo.xml b/addons/skin.estuary/xml/DialogVideoInfo.xml index 47e9a8e8b8e07..7b574e72d315d 100644 --- a/addons/skin.estuary/xml/DialogVideoInfo.xml +++ b/addons/skin.estuary/xml/DialogVideoInfo.xml @@ -420,7 +420,7 @@ <top>16</top> <width>232</width> <height>285</height> - <texture border="8" colordiffuse="button_focus">buttons/thumbnail_focused.png</texture> + <texture border="4" infill="false" colordiffuse="button_focus">colors/white.png</texture> </control> </control> </focusedlayout> @@ -513,7 +513,7 @@ <top>16</top> <width>194</width> <height>287</height> - <texture border="8" colordiffuse="button_focus">buttons/thumbnail_focused.png</texture> + <texture border="4" infill="false" colordiffuse="button_focus">colors/white.png</texture> </control> </control> </focusedlayout> diff --git a/addons/skin.estuary/xml/Includes_DialogSelect.xml b/addons/skin.estuary/xml/Includes_DialogSelect.xml index 72049e69dd838..1b5ff50f1f6f2 100644 --- a/addons/skin.estuary/xml/Includes_DialogSelect.xml +++ b/addons/skin.estuary/xml/Includes_DialogSelect.xml @@ -1022,7 +1022,7 @@ <control type="image"> <width>444</width> <height>250</height> - <texture border="8" colordiffuse="button_focus">buttons/thumbnail_focused.png</texture> + <texture border="4" infill="false" colordiffuse="button_focus">colors/white.png</texture> <visible>Control.HasFocus(10811)</visible> </control> </control> @@ -1128,7 +1128,7 @@ <control type="image"> <width>444</width> <height>250</height> - <texture border="8" colordiffuse="button_focus">buttons/thumbnail_focused.png</texture> + <texture border="4" infill="false" colordiffuse="button_focus">colors/white.png</texture> <visible>Control.HasFocus(10811)</visible> </control> </control> @@ -1235,7 +1235,7 @@ <control type="image"> <width>444</width> <height>250</height> - <texture border="8" colordiffuse="button_focus">buttons/thumbnail_focused.png</texture> + <texture border="4" infill="false" colordiffuse="button_focus">colors/white.png</texture> </control> </control> </focusedlayout> diff --git a/addons/skin.estuary/xml/Includes_PVR.xml b/addons/skin.estuary/xml/Includes_PVR.xml index 674f0ba835641..163efdd2168a3 100644 --- a/addons/skin.estuary/xml/Includes_PVR.xml +++ b/addons/skin.estuary/xml/Includes_PVR.xml @@ -717,7 +717,7 @@ <control type="image" id="2"> <width>58</width> <height>58</height> - <texture border="8" colordiffuse="button_focus">buttons/thumbnail_focused.png</texture> + <texture border="4" infill="false" colordiffuse="button_focus">colors/white.png</texture> </control> <control type="image" id="2"> <width>58</width> diff --git a/addons/skin.estuary/xml/VideoOSDBookmarks.xml b/addons/skin.estuary/xml/VideoOSDBookmarks.xml index 57996e7ac4f43..87fd04a83529b 100644 --- a/addons/skin.estuary/xml/VideoOSDBookmarks.xml +++ b/addons/skin.estuary/xml/VideoOSDBookmarks.xml @@ -124,7 +124,7 @@ <top>5</top> <width>330</width> <height>250</height> - <texture border="8" colordiffuse="button_focus">buttons/thumbnail_focused.png</texture> + <texture border="4" infill="false" colordiffuse="button_focus">colors/white.png</texture> <visible>Control.HasFocus(11)</visible> </control> </control> diff --git a/addons/skin.estuary/xml/View_501_Banner.xml b/addons/skin.estuary/xml/View_501_Banner.xml index 07aa554495aa5..20d757f2f3c84 100644 --- a/addons/skin.estuary/xml/View_501_Banner.xml +++ b/addons/skin.estuary/xml/View_501_Banner.xml @@ -89,7 +89,7 @@ <top>18</top> <width>824</width> <height>160</height> - <texture border="8" colordiffuse="button_focus">buttons/thumbnail_focused.png</texture> + <texture border="4" infill="false" colordiffuse="button_focus">colors/white.png</texture> </control> <control type="image"> <left>22</left> diff --git a/addons/skin.estuary/xml/View_51_Poster.xml b/addons/skin.estuary/xml/View_51_Poster.xml index 8ee39b6480d9f..bed5f14e61151 100644 --- a/addons/skin.estuary/xml/View_51_Poster.xml +++ b/addons/skin.estuary/xml/View_51_Poster.xml @@ -204,7 +204,7 @@ <height>721</height> <visible>Control.HasFocus(51)</visible> <animation effect="fade" time="100">VisibleChange</animation> - <texture border="8" colordiffuse="button_focus">buttons/thumbnail_focused.png</texture> + <texture border="4" infill="false" colordiffuse="button_focus">colors/white.png</texture> </control> <control type="group"> <left>216</left> From 2699277d0369dcc7832c5c248bcb28c4ad37c6f9 Mon Sep 17 00:00:00 2001 From: sarbes <sarbes@kodi.tv> Date: Thu, 31 Oct 2024 19:57:32 +0100 Subject: [PATCH 630/651] Estuary: improve codec flags --- addons/skin.estuary/media/flags/flag.png | Bin 312 -> 0 bytes addons/skin.estuary/xml/Includes.xml | 11 ++++++----- 2 files changed, 6 insertions(+), 5 deletions(-) delete mode 100644 addons/skin.estuary/media/flags/flag.png diff --git a/addons/skin.estuary/media/flags/flag.png b/addons/skin.estuary/media/flags/flag.png deleted file mode 100644 index 9626cfa68188d918bd86a1ba4c801a526d8de055..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 312 zcmeAS@N?(olHy`uVBq!ia0y~yVB`a`D>zty<jO5q+<?@7PZ!6Kid%1Qo#tdv<Z*CR z{&v48$Kk}q`)2}ePAae4Fp>A#E@cOq4(37yz9Wqm0_=}K90Us}9A`Rhb)C*%{~0nn z!ocz|AEPw)B~`l2f(gKM<2MIrD29T<2AGvF`(b85U4o>bU`yaTkM@)zsFoZ7IprC6 e9e(6sx!O)E)=wWA^DY2`g2B_(&t;ucLK6Tqre}o! diff --git a/addons/skin.estuary/xml/Includes.xml b/addons/skin.estuary/xml/Includes.xml index b649528a36b84..165a2d258e836 100644 --- a/addons/skin.estuary/xml/Includes.xml +++ b/addons/skin.estuary/xml/Includes.xml @@ -340,7 +340,7 @@ <param name="width">115</param> <param name="height">60</param> <param name="flag_visible">true</param> - <param name="texture">flags/flag.png</param> + <param name="texture">colors/white.png</param> <definition> <control type="group"> <width>$PARAM[width]</width> @@ -348,10 +348,11 @@ <visible>$PARAM[flag_visible]</visible> <control type="image"> <fadetime>0</fadetime> - <align>center</align> - <aligny>center</aligny> - <aspectratio>keep</aspectratio> - <texture>$PARAM[texture]</texture> + <top>13</top> + <left>5</left> + <width>105</width> + <height>34</height> + <texture border="2" infill="false" colordiffuse="white">$PARAM[texture]</texture> </control> <control type="label"> <align>center</align> From 3700e6f8c973abddaf169ebe0fdd58aa65f04a4f Mon Sep 17 00:00:00 2001 From: bnk1 <> Date: Fri, 1 Nov 2024 05:12:08 +0200 Subject: [PATCH 631/651] Enable/disable file deletetion dialog box --- .../resource.language.en_gb/resources/strings.po | 12 ++++++++++++ system/settings/settings.xml | 10 ++++++++++ xbmc/music/windows/GUIWindowMusicNav.cpp | 4 +--- xbmc/settings/Settings.h | 1 + xbmc/utils/FileUtils.cpp | 15 +++++++++++++++ xbmc/utils/FileUtils.h | 3 +++ xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 4 +--- xbmc/video/windows/GUIWindowVideoBase.cpp | 4 +--- xbmc/video/windows/GUIWindowVideoNav.cpp | 4 +--- xbmc/windows/GUIMediaWindow.cpp | 8 +------- 10 files changed, 46 insertions(+), 19 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 135f520bf7ca7..161c008f4ee4c 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -24712,3 +24712,15 @@ msgid "50th Anniversary Edition" msgstr "" # 40000 to 40800 are reserved for Video Versions feature + +#. Confirm file deletion +#: system/settings/settings.xml +msgctxt "#40801" +msgid "Confirm file deletion" +msgstr "" + +#. Description of setting with label #40801 "Confirm file deletion" +#: system/settings/settings.xml +msgctxt "#40802" +msgid "If enabled, a confirmation dialog will be displayed when files shall be deleted. If disabled, files will be deleted without user confirmation." +msgstr "" diff --git a/system/settings/settings.xml b/system/settings/settings.xml index db2375bb0ae84..87bc95efb335f 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -1080,6 +1080,16 @@ </dependencies> <control type="toggle" /> </setting> + <setting id="filelists.confirmfiledeletion" type="boolean" label="40801" help="40802"> + <level>2</level> + <default>true</default> + <dependencies> + <dependency type="enable"> + <condition setting="filelists.allowfiledeletion" operator="is">true</condition> + </dependency> + </dependencies> + <control type="toggle" /> + </setting> </group> </category> <category id="video" label="14215" help="38107"> diff --git a/xbmc/music/windows/GUIWindowMusicNav.cpp b/xbmc/music/windows/GUIWindowMusicNav.cpp index 840881515a357..9a1d194aa8bc3 100644 --- a/xbmc/music/windows/GUIWindowMusicNav.cpp +++ b/xbmc/music/windows/GUIWindowMusicNav.cpp @@ -826,9 +826,7 @@ bool CGUIWindowMusicNav::OnContextButton(int itemNumber, CONTEXT_BUTTON button) if (PLAYLIST::IsPlayList(*item) || PLAYLIST::IsSmartPlayList(*item)) { item->m_bIsFolder = false; - CGUIComponent *gui = CServiceBroker::GetGUI(); - if (gui && gui->ConfirmDelete(item->GetPath())) - CFileUtils::DeleteItem(item); + CFileUtils::DeleteItemWithConfirm(item); } else if (!VIDEO::IsVideoDb(*item)) OnDeleteItem(itemNumber); diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h index 203479aaf1df2..52732e2c9d428 100644 --- a/xbmc/settings/Settings.h +++ b/xbmc/settings/Settings.h @@ -56,6 +56,7 @@ class CSettings : public CSettingsBase, public CSettingCreator, public CSettingC static constexpr auto SETTING_FILELISTS_SHOWEXTENSIONS = "filelists.showextensions"; static constexpr auto SETTING_FILELISTS_IGNORETHEWHENSORTING = "filelists.ignorethewhensorting"; static constexpr auto SETTING_FILELISTS_ALLOWFILEDELETION = "filelists.allowfiledeletion"; + static constexpr auto SETTING_FILELISTS_CONFIRMFILEDELETION = "filelists.confirmfiledeletion"; static constexpr auto SETTING_FILELISTS_SHOWADDSOURCEBUTTONS = "filelists.showaddsourcebuttons"; static constexpr auto SETTING_FILELISTS_SHOWHIDDEN = "filelists.showhidden"; static constexpr auto SETTING_SCREENSAVER_MODE = "screensaver.mode"; diff --git a/xbmc/utils/FileUtils.cpp b/xbmc/utils/FileUtils.cpp index fd4cd7596f145..e80193b348db7 100644 --- a/xbmc/utils/FileUtils.cpp +++ b/xbmc/utils/FileUtils.cpp @@ -19,6 +19,7 @@ #include "filesystem/MultiPathDirectory.h" #include "filesystem/SpecialProtocol.h" #include "filesystem/StackDirectory.h" +#include "guilib/GUIComponent.h" #include "guilib/GUIKeyboardFactory.h" #include "guilib/LocalizeStrings.h" #include "imagefiles/ImageFileURL.h" @@ -353,3 +354,17 @@ bool CFileUtils::Exists(const std::string& strFileName, bool bUseCache) { return CFile::Exists(strFileName, bUseCache); } + +bool CFileUtils::DeleteItemWithConfirm(const std::shared_ptr<CFileItem>& item) +{ + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_FILELISTS_CONFIRMFILEDELETION)) + { + CGUIComponent* gui = CServiceBroker::GetGUI(); + + if (!gui || !gui->ConfirmDelete(item->GetPath())) + return false; + } + + return CFileUtils::DeleteItem(item); +} diff --git a/xbmc/utils/FileUtils.h b/xbmc/utils/FileUtils.h index 667d4b3cc898e..1f6854f9e10ad 100644 --- a/xbmc/utils/FileUtils.h +++ b/xbmc/utils/FileUtils.h @@ -21,6 +21,9 @@ class CFileUtils static bool DeleteItem(const std::shared_ptr<CFileItem>& item); static bool DeleteItem(const std::string &strPath); static bool Exists(const std::string& strFileName, bool bUseCache = true); + /** \brief Delete a file with or without gui confirmation unless setting is off. + */ + static bool DeleteItemWithConfirm(const std::shared_ptr<CFileItem>& item); static bool RenameFile(const std::string &strFile); static bool RemoteAccessAllowed(const std::string &strPath); static unsigned int LoadFile(const std::string &filename, void* &outputBuffer); diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index d297a15634af9..dadb84b87b033 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -1482,9 +1482,7 @@ bool CGUIDialogVideoInfo::DeleteVideoItem(const std::shared_ptr<CFileItem>& item if (item->IsStack()) item->m_bIsFolder = true; - CGUIComponent *gui = CServiceBroker::GetGUI(); - if (gui && gui->ConfirmDelete(item->GetPath())) - CFileUtils::DeleteItem(item); + CFileUtils::DeleteItemWithConfirm(item); } } diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index d231a809f765d..ccf27c73ce484 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -1059,9 +1059,7 @@ void CGUIWindowVideoBase::OnDeleteItem(const CFileItemPtr& item) m_vecItems->IsPath("special://videoplaylists/")) && CUtil::SupportsWriteFileOperations(item->GetPath())) { - CGUIComponent *gui = CServiceBroker::GetGUI(); - if (gui && gui->ConfirmDelete(item->GetPath())) - CFileUtils::DeleteItem(item); + CFileUtils::DeleteItemWithConfirm(item); } } diff --git a/xbmc/video/windows/GUIWindowVideoNav.cpp b/xbmc/video/windows/GUIWindowVideoNav.cpp index d85294bbd9a55..edff6b7a4cb26 100644 --- a/xbmc/video/windows/GUIWindowVideoNav.cpp +++ b/xbmc/video/windows/GUIWindowVideoNav.cpp @@ -712,9 +712,7 @@ void CGUIWindowVideoNav::OnDeleteItem(const CFileItemPtr& pItem) m_vecItems->IsPath("special://videoplaylists/")) { pItem->m_bIsFolder = false; - CGUIComponent *gui = CServiceBroker::GetGUI(); - if (gui && gui->ConfirmDelete(pItem->GetPath())) - CFileUtils::DeleteItem(pItem); + CFileUtils::DeleteItemWithConfirm(pItem); } else { diff --git a/xbmc/windows/GUIMediaWindow.cpp b/xbmc/windows/GUIMediaWindow.cpp index 9c1f8e48dfd47..4bc80bc4c50f7 100644 --- a/xbmc/windows/GUIMediaWindow.cpp +++ b/xbmc/windows/GUIMediaWindow.cpp @@ -1652,13 +1652,7 @@ void CGUIMediaWindow::OnDeleteItem(int iItem) return; } - CGUIComponent *gui = CServiceBroker::GetGUI(); - if (gui && gui->ConfirmDelete(item->GetPath())) - { - if (!CFileUtils::DeleteItem(item)) - return; - } - else + if (!CFileUtils::DeleteItemWithConfirm(item)) return; Refresh(true); From eaa5117e3de3aef4514ea17aefaee39ee601a093 Mon Sep 17 00:00:00 2001 From: wsnipex <wsnipex@a1.net> Date: Fri, 1 Nov 2024 07:04:32 +0100 Subject: [PATCH 632/651] [depends] look for android sdk manager under latest subdir --- tools/depends/configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac index 976c1977e3610..ff561f57271e2 100644 --- a/tools/depends/configure.ac +++ b/tools/depends/configure.ac @@ -573,7 +573,7 @@ if test "$platform_os" = "android"; then AC_MSG_ERROR("SDK path is required for android") fi - if [ ! test -f $use_sdk_path/tools/bin/sdkmanager ] && [ ! test -f $use_sdk_path/cmdline-tools/bin/sdkmanager ]; then + if [ ! test -f $use_sdk_path/tools/bin/sdkmanager ] && [ ! test -f $use_sdk_path/cmdline-tools/bin/sdkmanager ] && [ ! test -f $use_sdk_path/cmdline-tools/latest/bin/sdkmanager ]; then AC_MSG_ERROR(verify sdk path) fi From b183de3d30fd5e88fdcd85f7228041c2d03d0ce2 Mon Sep 17 00:00:00 2001 From: Hosted Weblate <hosted@weblate.org> Date: Fri, 1 Nov 2024 09:45:31 +0000 Subject: [PATCH 633/651] Update translation files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Polish (pl_pl)) Currently translated at 100.0% (17 of 17 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Spanish (Spain) (es_es)) Currently translated at 100.0% (172 of 172 strings) Translated using Weblate (German (de_de)) Currently translated at 100.0% (172 of 172 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Polish (pl_pl)) Currently translated at 100.0% (172 of 172 strings) Translated using Weblate (Korean (ko_kr)) Currently translated at 100.0% (172 of 172 strings) Translated using Weblate (Italian (it_it)) Currently translated at 100.0% (172 of 172 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Hungarian (hu_hu)) Currently translated at 100.0% (170 of 170 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Esperanto) Currently translated at 60.0% (3 of 5 strings) Translated using Weblate (Esperanto) Currently translated at 40.0% (2 of 5 strings) Translated using Weblate (Esperanto) Currently translated at 100.0% (5 of 5 strings) Translated using Weblate (Esperanto) Currently translated at 40.0% (2 of 5 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Esperanto) Currently translated at 100.0% (5 of 5 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Romanian (ro_ro)) Currently translated at 97.0% (165 of 170 strings) Translated using Weblate (Hungarian (hu_hu)) Currently translated at 100.0% (170 of 170 strings) Translated using Weblate (Romanian (ro_ro)) Currently translated at 100.0% (5 of 5 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (German (de_de)) Currently translated at 100.0% (170 of 170 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Russian (ru_ru)) Currently translated at 100.0% (170 of 170 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Portuguese (Brazil) (pt_br)) Currently translated at 100.0% (170 of 170 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Romanian (ro_ro)) Currently translated at 100.0% (3 of 3 strings) Translated using Weblate (Romanian (ro_ro)) Currently translated at 97.0% (165 of 170 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Estonian (et_ee)) Currently translated at 100.0% (170 of 170 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Asturian (Spain) (ast_es)) Currently translated at 20.0% (1 of 5 strings) Translated using Weblate (Asturian (Spain) (ast_es)) Currently translated at 20.0% (1 of 5 strings) Translated using Weblate (Asturian (Spain) (ast_es)) Currently translated at 1.1% (2 of 170 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Finnish (fi_fi)) Currently translated at 100.0% (170 of 170 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Korean (ko_kr)) Currently translated at 100.0% (170 of 170 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Spanish (Spain) (es_es)) Currently translated at 100.0% (170 of 170 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Polish (pl_pl)) Currently translated at 100.0% (170 of 170 strings) Translated using Weblate (Italian (it_it)) Currently translated at 100.0% (170 of 170 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Spanish (Spain) (es_es)) Currently translated at 100.0% (5 of 5 strings) Translated using Weblate (Spanish (Spain) (es_es)) Currently translated at 100.0% (5 of 5 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Spanish (Spain) (es_es)) Currently translated at 100.0% (169 of 169 strings) Co-authored-by: Alexey <signfinder@gmail.com> Co-authored-by: Alfonso Cachero <alfonso.cachero@gmail.com> Co-authored-by: Daniel <danny3@tutanota.com> Co-authored-by: Enol P <enolp@softastur.org> Co-authored-by: Frodo19 <bilbohu@gmail.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Jakub Fabijan <animatorzpolski@gmail.com> Co-authored-by: José Antonio Alvarado <jalvarado0.eses@gmail.com> Co-authored-by: Kai Sommerfeld <ksooo@users.noreply.kodi.weblate.cloud> Co-authored-by: Marek Adamski <fevbew@wp.pl> Co-authored-by: Massimo Pissarello <mapi68@gmail.com> Co-authored-by: Minho Park <parkmino@gmail.com> Co-authored-by: Oskari Lavinto <olavinto@protonmail.com> Co-authored-by: icarok99 <icarok00@gmail.com> Co-authored-by: rimasx <riks_12@hot.ee> Co-authored-by: roliverosc <roliverosc@hotmail.com> Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/ast_es/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/de_de/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/es_es/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/et_ee/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/fi_fi/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/hu_hu/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/it_it/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/ko_kr/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/pl_pl/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/pt_br/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/ro_ro/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/ru_ru/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-aac/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-aac/ast_es/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-aac/eo/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-aac/es_es/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-wma/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-wma/ast_es/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-wma/eo/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-wma/es_es/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/kodi-main-android-strings/pl_pl/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/kodi-main-android-strings/ro_ro/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/screensaver-xbmc-builtin-dim/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/screensaver-xbmc-builtin-dim/eo/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/screensaver-xbmc-builtin-dim/ro_ro/ Translation: Kodi add-ons: skins/skin.estuary Translation: Kodi core/audioencoder.kodi.builtin.aac Translation: Kodi core/audioencoder.kodi.builtin.wma Translation: Kodi core/kodi main (Android strings) Translation: Kodi core/screensaver.xbmc.builtin.dim --- .../resource.language.ast_es/strings.po | 8 +-- .../language/resource.language.eo/strings.po | 16 ++--- .../resource.language.es_es/strings.po | 6 +- .../resource.language.ast_es/strings.po | 8 +-- .../language/resource.language.eo/strings.po | 12 ++-- .../resource.language.es_es/strings.po | 8 +-- .../resource.language.ro_ro/strings.po | 17 ++--- .../resource.language.af_za/strings.po | 26 ++++++-- .../resource.language.am_et/strings.po | 18 ++++- .../resource.language.ar_sa/strings.po | 30 +++++++-- .../resource.language.ast_es/strings.po | 30 ++++++--- .../resource.language.az_az/strings.po | 18 ++++- .../resource.language.be_by/strings.po | 30 +++++++-- .../resource.language.bg_bg/strings.po | 24 ++++++- .../resource.language.bs_ba/strings.po | 18 ++++- .../resource.language.ca_es/strings.po | 30 +++++++-- .../resource.language.cs_cz/strings.po | 30 +++++++-- .../resource.language.cy_gb/strings.po | 18 ++++- .../resource.language.da_dk/strings.po | 30 +++++++-- .../resource.language.de_de/strings.po | 36 ++++++++-- .../resource.language.el_gr/strings.po | 24 ++++++- .../resource.language.en_au/strings.po | 18 ++++- .../resource.language.en_nz/strings.po | 24 ++++++- .../resource.language.en_us/strings.po | 30 +++++++-- .../language/resource.language.eo/strings.po | 18 ++++- .../resource.language.es_ar/strings.po | 30 +++++++-- .../resource.language.es_es/strings.po | 38 ++++++++--- .../resource.language.es_mx/strings.po | 30 +++++++-- .../resource.language.et_ee/strings.po | 36 ++++++++-- .../resource.language.eu_es/strings.po | 24 ++++++- .../resource.language.fa_af/strings.po | 18 ++++- .../resource.language.fa_ir/strings.po | 30 +++++++-- .../resource.language.fi_fi/strings.po | 36 ++++++++-- .../language/resource.language.fil/strings.po | 18 ++++- .../resource.language.fo_fo/strings.po | 18 ++++- .../resource.language.fr_ca/strings.po | 30 +++++++-- .../resource.language.fr_fr/strings.po | 30 +++++++-- .../resource.language.gl_es/strings.po | 24 ++++++- .../resource.language.he_il/strings.po | 24 ++++++- .../resource.language.hi_in/strings.po | 18 ++++- .../resource.language.hr_hr/strings.po | 30 +++++++-- .../resource.language.hu_hu/strings.po | 38 ++++++++--- .../resource.language.hy_am/strings.po | 18 ++++- .../resource.language.id_id/strings.po | 30 +++++++-- .../resource.language.is_is/strings.po | 30 +++++++-- .../resource.language.it_it/strings.po | 36 ++++++++-- .../resource.language.ja_jp/strings.po | 30 +++++++-- .../resource.language.kn_in/strings.po | 18 ++++- .../resource.language.ko_kr/strings.po | 36 ++++++++-- .../resource.language.lt_lt/strings.po | 30 +++++++-- .../resource.language.lv_lv/strings.po | 18 ++++- .../language/resource.language.mi/strings.po | 18 ++++- .../resource.language.mk_mk/strings.po | 24 ++++++- .../resource.language.ml_in/strings.po | 18 ++++- .../resource.language.mn_mn/strings.po | 18 ++++- .../resource.language.ms_my/strings.po | 24 ++++++- .../resource.language.mt_mt/strings.po | 18 ++++- .../resource.language.my_mm/strings.po | 18 ++++- .../resource.language.nb_no/strings.po | 24 ++++++- .../resource.language.nl_nl/strings.po | 32 +++++++-- .../resource.language.pl_pl/strings.po | 36 ++++++++-- .../resource.language.pt_br/strings.po | 38 ++++++++--- .../resource.language.pt_pt/strings.po | 24 ++++++- .../resource.language.ro_ro/strings.po | 66 ++++++++++++------- .../resource.language.ru_ru/strings.po | 38 ++++++++--- .../resource.language.si_lk/strings.po | 18 ++++- .../resource.language.sk_sk/strings.po | 32 +++++++-- .../resource.language.sl_si/strings.po | 32 +++++++-- .../resource.language.sq_al/strings.po | 18 ++++- .../resource.language.sr_rs/strings.po | 24 ++++++- .../resource.language.sr_rs@latin/strings.po | 24 ++++++- .../resource.language.sv_se/strings.po | 30 +++++++-- .../language/resource.language.szl/strings.po | 24 ++++++- .../resource.language.ta_in/strings.po | 18 ++++- .../resource.language.te_in/strings.po | 18 ++++- .../resource.language.tg_tj/strings.po | 18 ++++- .../resource.language.th_th/strings.po | 24 ++++++- .../resource.language.tr_tr/strings.po | 30 +++++++-- .../resource.language.uk_ua/strings.po | 30 +++++++-- .../resource.language.uz_uz/strings.po | 18 ++++- .../resource.language.vi_vn/strings.po | 30 +++++++-- .../resource.language.zh_cn/strings.po | 30 +++++++-- .../resource.language.zh_tw/strings.po | 30 +++++++-- .../xbmc/res/values-pl-rpl/strings.xml | 14 ++++ .../xbmc/res/values-ro-rro/strings.xml | 3 +- 85 files changed, 1767 insertions(+), 339 deletions(-) diff --git a/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.ast_es/strings.po b/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.ast_es/strings.po index b72442f17c70c..dbb39c6388a0d 100644 --- a/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.ast_es/strings.po +++ b/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.ast_es/strings.po @@ -7,15 +7,15 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2022-02-26 16:13+0000\n" -"Last-Translator: Christian Gade <gade@kodi.tv>\n" +"PO-Revision-Date: 2024-08-04 09:04+0000\n" +"Last-Translator: \"Enol P.\" <enolp@softastur.org>\n" "Language-Team: Asturian (Spain) <https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-aac/ast_es/>\n" "Language: ast_es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.11\n" +"X-Generator: Weblate 5.6.2\n" msgctxt "Addon Summary" msgid "AAC Audio Encoder" @@ -41,4 +41,4 @@ msgstr "" #: resources/settings.xml msgctxt "#30002" msgid "{0:d} kbps" -msgstr "{0:d} kbps" +msgstr "{0:d} Kb/s" diff --git a/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.eo/strings.po b/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.eo/strings.po index e5b28b03174b6..3443e55144680 100644 --- a/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.eo/strings.po +++ b/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.eo/strings.po @@ -7,38 +7,38 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2021-08-21 08:21+0000\n" -"Last-Translator: Christian Gade <gade@kodi.tv>\n" +"PO-Revision-Date: 2024-09-22 17:12+0000\n" +"Last-Translator: Jakub Fabijan <animatorzpolski@gmail.com>\n" "Language-Team: Esperanto <https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-aac/eo/>\n" "Language: eo\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.7.2\n" +"X-Generator: Weblate 5.7.2\n" msgctxt "Addon Summary" msgid "AAC Audio Encoder" -msgstr "" +msgstr "AAC-sonkodilo" msgctxt "Addon Description" msgid "AAC is a set of codecs designed to provide better compression than MP3s, and are improved versions of MPEG audio." -msgstr "" +msgstr "AAC estas aro de kodiloj projektitaj por provizi pli bonan densigon ol MP3-oj, kiuj estas plibonigitaj versioj de \"MPEG audio\"." #. Bitrate to use on for compression #: resources/settings.xml msgctxt "#30000" msgid "Bitrate" -msgstr "Bit Rate" +msgstr "Bitrapido" #. Description of setting with label #30000 "Bitrate" #: resources/settings.xml msgctxt "#30001" msgid "Select which bitrate to use for the AAC audio encoder for audio compression." -msgstr "" +msgstr "Elektu bitrapidon uzatan por la AAC-sonkodilo por sonan densigon." #. Value format for with bitrate edited field #: resources/settings.xml msgctxt "#30002" msgid "{0:d} kbps" -msgstr "" +msgstr "{0:d} kb/s" diff --git a/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.es_es/strings.po b/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.es_es/strings.po index c427df087f1a7..b657bbeac408e 100644 --- a/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.es_es/strings.po +++ b/addons/audioencoder.kodi.builtin.aac/resources/language/resource.language.es_es/strings.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2024-05-24 05:07+0000\n" +"PO-Revision-Date: 2024-07-30 23:23+0000\n" "Last-Translator: roliverosc <roliverosc@hotmail.com>\n" "Language-Team: Spanish (Spain) <https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-aac/es_es/>\n" "Language: es_es\n" @@ -15,11 +15,11 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.5.4\n" +"X-Generator: Weblate 5.6.2\n" msgctxt "Addon Summary" msgid "AAC Audio Encoder" -msgstr "Codificador de Audio AAC" +msgstr "Codificador de audio AAC" msgctxt "Addon Description" msgid "AAC is a set of codecs designed to provide better compression than MP3s, and are improved versions of MPEG audio." diff --git a/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.ast_es/strings.po b/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.ast_es/strings.po index a1b10f1de068f..19694400c81f2 100644 --- a/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.ast_es/strings.po +++ b/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.ast_es/strings.po @@ -7,15 +7,15 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2022-02-26 16:13+0000\n" -"Last-Translator: Christian Gade <gade@kodi.tv>\n" +"PO-Revision-Date: 2024-08-04 09:04+0000\n" +"Last-Translator: \"Enol P.\" <enolp@softastur.org>\n" "Language-Team: Asturian (Spain) <https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-wma/ast_es/>\n" "Language: ast_es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.11\n" +"X-Generator: Weblate 5.6.2\n" msgctxt "Addon Summary" msgid "WMA Audio Encoder" @@ -41,4 +41,4 @@ msgstr "" #: resources/settings.xml msgctxt "#30002" msgid "{0:d} kbps" -msgstr "{0:d} kbps" +msgstr "{0:d} Kb/s" diff --git a/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.eo/strings.po b/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.eo/strings.po index c5df814563cd3..eab2cce5cea66 100644 --- a/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.eo/strings.po +++ b/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.eo/strings.po @@ -7,19 +7,19 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2021-08-21 08:21+0000\n" -"Last-Translator: Christian Gade <gade@kodi.tv>\n" +"PO-Revision-Date: 2024-09-22 17:29+0000\n" +"Last-Translator: Jakub Fabijan <animatorzpolski@gmail.com>\n" "Language-Team: Esperanto <https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-wma/eo/>\n" "Language: eo\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.7.2\n" +"X-Generator: Weblate 5.7.2\n" msgctxt "Addon Summary" msgid "WMA Audio Encoder" -msgstr "" +msgstr "WMA-sonkodilo" msgctxt "Addon Description" msgid "Windows Media Audio, Microsoft’s lossy audio format." @@ -29,7 +29,7 @@ msgstr "" #: resources/settings.xml msgctxt "#30000" msgid "Bitrate" -msgstr "Bit Rate" +msgstr "Bitrapido" #. Description of setting with label #30000 "Bitrate" #: resources/settings.xml @@ -41,4 +41,4 @@ msgstr "" #: resources/settings.xml msgctxt "#30002" msgid "{0:d} kbps" -msgstr "" +msgstr "{0:d} kb/s" diff --git a/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.es_es/strings.po b/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.es_es/strings.po index e28a8fbb464ac..b69ecf84dfe47 100644 --- a/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.es_es/strings.po +++ b/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.es_es/strings.po @@ -7,19 +7,19 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2021-12-01 20:14+0000\n" -"Last-Translator: Alfonso Cachero <alfonso.cachero@gmail.com>\n" +"PO-Revision-Date: 2024-07-30 23:23+0000\n" +"Last-Translator: roliverosc <roliverosc@hotmail.com>\n" "Language-Team: Spanish (Spain) <https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-wma/es_es/>\n" "Language: es_es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.9.1\n" +"X-Generator: Weblate 5.6.2\n" msgctxt "Addon Summary" msgid "WMA Audio Encoder" -msgstr "Codificador de Audio WMA" +msgstr "Codificador de audio WMA" msgctxt "Addon Description" msgid "Windows Media Audio, Microsoft’s lossy audio format." diff --git a/addons/screensaver.xbmc.builtin.dim/resources/language/resource.language.ro_ro/strings.po b/addons/screensaver.xbmc.builtin.dim/resources/language/resource.language.ro_ro/strings.po index b64fc1624e40c..9b1f203d06e15 100644 --- a/addons/screensaver.xbmc.builtin.dim/resources/language/resource.language.ro_ro/strings.po +++ b/addons/screensaver.xbmc.builtin.dim/resources/language/resource.language.ro_ro/strings.po @@ -5,16 +5,17 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Kodi Translation Team\n" -"Language-Team: Romanian (Romania) (http://www.transifex.com/projects/p/kodi-main/language/ro_RO/)\n" -"Language: ro_RO\n" +"PO-Revision-Date: 2024-08-27 02:47+0000\n" +"Last-Translator: Daniel <danny3@tutanota.com>\n" +"Language-Team: Romanian <https://kodi.weblate.cloud/projects/kodi-core/screensaver-xbmc-builtin-dim/ro_ro/>\n" +"Language: ro_ro\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n" +"Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;\n" +"X-Generator: Weblate 5.6.2\n" msgctxt "Addon Summary" msgid "Screensaver that dims your screen" @@ -27,12 +28,12 @@ msgstr "Dim este un protector de ecran simplu care va întuneca (reduce intensit #. Setting name for edit screen dim in percentage (0% = black, 100% = no dim) msgctxt "#30000" msgid "Brightness level" -msgstr "" +msgstr "Nivelul luminozității" #. Setting help text msgctxt "#30001" msgid "Level in percent of how strongly the screen remains illuminated." -msgstr "" +msgstr "Nivelul în procente de cât de puternic ecranul rămâne iluminat." #. Setting category name msgctxt "#30002" diff --git a/addons/skin.estuary/language/resource.language.af_za/strings.po b/addons/skin.estuary/language/resource.language.af_za/strings.po index bf3642cb00cf9..cb89347043e40 100644 --- a/addons/skin.estuary/language/resource.language.af_za/strings.po +++ b/addons/skin.estuary/language/resource.language.af_za/strings.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2024-02-12 00:31+0000\n" "Last-Translator: Christian Gade <gade@kodi.tv>\n" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Vinnig vorentoe" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Kamera vervaardiger" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "WyeLys" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Tik lêer seksie in" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Besigtig jou persoonlike prente of laai een van die baie beeld byvoegsels af vanaf die offisiële stoorplek." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Wissel oudio stroom" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "Deursoek YouTube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "Stelsel" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Wissel oudio stroom" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Ekstras" diff --git a/addons/skin.estuary/language/resource.language.am_et/strings.po b/addons/skin.estuary/language/resource.language.am_et/strings.po index 9d5c3be73c4b8..7b57c2adc7f10 100644 --- a/addons/skin.estuary/language/resource.language.am_et/strings.po +++ b/addons/skin.estuary/language/resource.language.am_et/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "ወደ ፊት ማሳለፊያ" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "ካሜራ አምራቾች" @@ -394,6 +398,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -404,7 +413,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -416,7 +425,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -738,6 +747,11 @@ msgctxt "#31611" msgid "System" msgstr "ስርአት" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + #~ msgctxt "#31137" #~ msgid "PVR info" #~ msgstr "የ PVR መረጃ" diff --git a/addons/skin.estuary/language/resource.language.ar_sa/strings.po b/addons/skin.estuary/language/resource.language.ar_sa/strings.po index 7b21b6a52c3ef..02e784f505b57 100644 --- a/addons/skin.estuary/language/resource.language.ar_sa/strings.po +++ b/addons/skin.estuary/language/resource.language.ar_sa/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "التقدم السريع" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "قائمة واسعة" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,8 +419,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "اعرض صورك الشخصية أو قم بتنزيل إحدى إضافات الصور العديدة من المستودع الرسمي." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "تبديل تدفق الصوت" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -422,8 +431,8 @@ msgid "Search YouTube" msgstr "ابحث في YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "تبديل الترجمة" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -744,6 +753,19 @@ msgctxt "#31611" msgid "System" msgstr "النظام" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "تبديل تدفق الصوت" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "تبديل الترجمة" + #~ msgctxt "#31059" #~ msgid "Select + X" #~ msgstr "Select + X" diff --git a/addons/skin.estuary/language/resource.language.ast_es/strings.po b/addons/skin.estuary/language/resource.language.ast_es/strings.po index 75e43a9edb0e7..3aa99e0a6d924 100644 --- a/addons/skin.estuary/language/resource.language.ast_es/strings.po +++ b/addons/skin.estuary/language/resource.language.ast_es/strings.po @@ -7,15 +7,15 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2022-02-26 16:13+0000\n" -"Last-Translator: Christian Gade <gade@kodi.tv>\n" +"PO-Revision-Date: 2024-08-04 09:04+0000\n" +"Last-Translator: \"Enol P.\" <enolp@softastur.org>\n" "Language-Team: Asturian (Spain) <https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/ast_es/>\n" "Language: ast_es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.11\n" +"X-Generator: Weblate 5.6.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -367,7 +371,7 @@ msgstr "" #. viewtype name msgctxt "#31100" msgid "Shift" -msgstr "Mayúscules" +msgstr "" #. viewtype name msgctxt "#31101" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -626,7 +635,7 @@ msgstr "" #. Label for the kind of profile identification msgctxt "#31165" msgid "Profile name" -msgstr "Nome del perfil" +msgstr "" #. Label for the kind of profile identification msgctxt "#31166" @@ -737,9 +746,14 @@ msgstr "" #. Label to show the media (metadata) info page msgctxt "#31610" msgid "Media" -msgstr "Media" +msgstr "" #. Label to show the system info page msgctxt "#31611" msgid "System" msgstr "" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.az_az/strings.po b/addons/skin.estuary/language/resource.language.az_az/strings.po index 25b2e8b5971b5..8543511308ef1 100644 --- a/addons/skin.estuary/language/resource.language.az_az/strings.po +++ b/addons/skin.estuary/language/resource.language.az_az/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "Media" msgctxt "#31611" msgid "System" msgstr "Sistem" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.be_by/strings.po b/addons/skin.estuary/language/resource.language.be_by/strings.po index 6bfe56e166d05..b529c393c8277 100644 --- a/addons/skin.estuary/language/resource.language.be_by/strings.po +++ b/addons/skin.estuary/language/resource.language.be_by/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Перамотванне ўперад" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Вытворца камеры" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Шырокі спіс" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Увайсці ў раздзел \"Файлы\"" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Прагляд вашых малюнкаў альбо іх спампоўванне пры дапамозе дапаўненняў з афіцыйнага рэпазіторыя." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Пераключыць аўдыяструмень" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Пошук на YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Пераключыць субцітры" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Сістэма" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Пераключыць аўдыяструмень" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Пераключыць субцітры" + #~ msgctxt "#31059" #~ msgid "Select + X" #~ msgstr "Select + X" diff --git a/addons/skin.estuary/language/resource.language.bg_bg/strings.po b/addons/skin.estuary/language/resource.language.bg_bg/strings.po index 6ba7ad7efbddf..d5d77f4978bc3 100644 --- a/addons/skin.estuary/language/resource.language.bg_bg/strings.po +++ b/addons/skin.estuary/language/resource.language.bg_bg/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Превъртане напред" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Производител" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Широк списък" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Раздел Файлове" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Разгледайте собствените си снимки или свалете някоя от многото добавки за изображения от официалното хранилище." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Превключване на звуковия поток" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "Търси в YouTube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "Системни" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Превключване на звуковия поток" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Екстри" diff --git a/addons/skin.estuary/language/resource.language.bs_ba/strings.po b/addons/skin.estuary/language/resource.language.bs_ba/strings.po index c160ad601544e..5eb8dcb055aab 100644 --- a/addons/skin.estuary/language/resource.language.bs_ba/strings.po +++ b/addons/skin.estuary/language/resource.language.bs_ba/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "Sistem" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.ca_es/strings.po b/addons/skin.estuary/language/resource.language.ca_es/strings.po index 3435acb066527..b69358d591ae1 100644 --- a/addons/skin.estuary/language/resource.language.ca_es/strings.po +++ b/addons/skin.estuary/language/resource.language.ca_es/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Avanç ràpid" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Fabricant de la càmera" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Llista ampla" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Entrada a la secció dels fitxers" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Visualitzeu les vostres fotografies personals o baixeu un dels molts complements d'imatges des del dipòsit oficial." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Commuta la transmissió de l'àudio" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Cerca de YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Canvia el subtítol" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Sistema" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Commuta la transmissió de l'àudio" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Canvia el subtítol" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Extres" diff --git a/addons/skin.estuary/language/resource.language.cs_cz/strings.po b/addons/skin.estuary/language/resource.language.cs_cz/strings.po index c5c99ea0f5ab5..e144c77d2cbe4 100644 --- a/addons/skin.estuary/language/resource.language.cs_cz/strings.po +++ b/addons/skin.estuary/language/resource.language.cs_cz/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Přetočit vpřed" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Výrobce fotoaparátu" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Široký seznam" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Vstoupit do sekce souborů" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Zobrazte vlastní obrázky nebo si stáhněte jeden z mnoha doplňků obrázků z oficiálního repozitáře." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Přepnout zvukovou stopu" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Vyhledat na YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Přepnout titulky" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Systém" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Přepnout zvukovou stopu" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Přepnout titulky" + #~ msgctxt "#31059" #~ msgid "Select + X" #~ msgstr "Select + X" diff --git a/addons/skin.estuary/language/resource.language.cy_gb/strings.po b/addons/skin.estuary/language/resource.language.cy_gb/strings.po index 556a2188489f0..2ce75392e8cd4 100644 --- a/addons/skin.estuary/language/resource.language.cy_gb/strings.po +++ b/addons/skin.estuary/language/resource.language.cy_gb/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -744,6 +753,11 @@ msgctxt "#31611" msgid "System" msgstr "System" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + #~ msgctxt "#31137" #~ msgid "PVR info" #~ msgstr "Manylion y PVR" diff --git a/addons/skin.estuary/language/resource.language.da_dk/strings.po b/addons/skin.estuary/language/resource.language.da_dk/strings.po index c10345aa6a7be..7d64416c4a485 100644 --- a/addons/skin.estuary/language/resource.language.da_dk/strings.po +++ b/addons/skin.estuary/language/resource.language.da_dk/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Spol frem" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Kameraproducent" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Bred liste" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Gå til filsektionen" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Se dine personlige billeder eller hent et af de mange billed-add-ons fra det officielle fjernlager." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Skift lydspor" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Søg på YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Skift undertekst" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "System" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Skift lydspor" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Skift undertekst" + #~ msgctxt "#31612" #~ msgid "Add version" #~ msgstr "Tilføj version" diff --git a/addons/skin.estuary/language/resource.language.de_de/strings.po b/addons/skin.estuary/language/resource.language.de_de/strings.po index 1652c6b178b8b..bffdf2240698f 100644 --- a/addons/skin.estuary/language/resource.language.de_de/strings.po +++ b/addons/skin.estuary/language/resource.language.de_de/strings.po @@ -5,9 +5,9 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-11-24 16:07+0000\n" +"PO-Revision-Date: 2024-10-20 06:47+0000\n" "Last-Translator: Kai Sommerfeld <ksooo@users.noreply.kodi.weblate.cloud>\n" "Language-Team: German <https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/de_de/>\n" "Language: de_de\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.2.1\n" +"X-Generator: Weblate 5.7.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Schneller Vorlauf" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "Widgets bearbeiten" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Kamerahersteller" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "WideList" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "Videostreams" + msgctxt "#31110" msgid "Enter files section" msgstr "Zu „Dateien“ ..." @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Persönliche Fotos anschauen oder eines der vielen Bilder-Addons aus dem offiziellen Repository downloaden." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Tonspur wechseln" +msgid "Audio streams" +msgstr "Audiostreams" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "YouTube durchsuchen" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Untertitel umschalten" +msgid "Subtitles streams" +msgstr "Untertitelstreams" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "System" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "Kanäle" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Tonspur wechseln" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Untertitel umschalten" + #~ msgctxt "#31612" #~ msgid "Add version" #~ msgstr "Version hinzufügen" diff --git a/addons/skin.estuary/language/resource.language.el_gr/strings.po b/addons/skin.estuary/language/resource.language.el_gr/strings.po index 73586ce698cb8..37a3e8207804f 100644 --- a/addons/skin.estuary/language/resource.language.el_gr/strings.po +++ b/addons/skin.estuary/language/resource.language.el_gr/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Γρήγορη Προώθηση" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Κατασκευαστής κάμερας" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Ευρεία λίστα" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Μετάβαση στην ενότητα Αρχεία" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Δείτε τις προσωπικές σας εικόνες ή κατεβάστε ένα από τα πολλά πρόσθετα εικόνας από το επίσημο αποθετήριο." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Εναλλαγή ηχητικής ροής" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "Αναζήτηση στο Youtube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "Σύστημα" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Εναλλαγή ηχητικής ροής" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Extras" diff --git a/addons/skin.estuary/language/resource.language.en_au/strings.po b/addons/skin.estuary/language/resource.language.en_au/strings.po index f1a9518f36e79..dca4e0e35d6e0 100644 --- a/addons/skin.estuary/language/resource.language.en_au/strings.po +++ b/addons/skin.estuary/language/resource.language.en_au/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -399,6 +403,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -409,7 +418,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -421,7 +430,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,6 +752,11 @@ msgctxt "#31611" msgid "System" msgstr "System" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Extras" diff --git a/addons/skin.estuary/language/resource.language.en_nz/strings.po b/addons/skin.estuary/language/resource.language.en_nz/strings.po index 1fa18841395d9..0391db21425d9 100644 --- a/addons/skin.estuary/language/resource.language.en_nz/strings.po +++ b/addons/skin.estuary/language/resource.language.en_nz/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Fast forward" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Camera manufacturer" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "WideList" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Enter files section" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "View your personal pictures or download one of the many image add-ons from the official repository." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Toggle audio stream" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "Search YouTube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "System" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Toggle audio stream" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Extras" diff --git a/addons/skin.estuary/language/resource.language.en_us/strings.po b/addons/skin.estuary/language/resource.language.en_us/strings.po index 928c6dbb3e0a4..c87b56996465b 100644 --- a/addons/skin.estuary/language/resource.language.en_us/strings.po +++ b/addons/skin.estuary/language/resource.language.en_us/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Fast forward" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Camera manufacturer" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "WideList" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Enter files section" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "View your personal pictures or download one of the many image add-ons from the official repository." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Toggle audio stream" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Search YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Toggle subtitle" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "System" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Toggle audio stream" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Toggle subtitle" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Extras" diff --git a/addons/skin.estuary/language/resource.language.eo/strings.po b/addons/skin.estuary/language/resource.language.eo/strings.po index 8cf9e4193ce90..029f0f4448712 100644 --- a/addons/skin.estuary/language/resource.language.eo/strings.po +++ b/addons/skin.estuary/language/resource.language.eo/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "System" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.es_ar/strings.po b/addons/skin.estuary/language/resource.language.es_ar/strings.po index 9105c304745ee..4aa65b477f41e 100644 --- a/addons/skin.estuary/language/resource.language.es_ar/strings.po +++ b/addons/skin.estuary/language/resource.language.es_ar/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Avance rápido" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Fabricante de cámara" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Lista amplia" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Ir a la sección \"Archivos\"" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Vea sus imágenes personales o descargue uno de los muchos add-ons de imágenes desde el repositorio oficial." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Cambiar canal de sonido" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Buscar en YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Cambiar subtítulo" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Sistema" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Cambiar canal de sonido" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Cambiar subtítulo" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Extras" diff --git a/addons/skin.estuary/language/resource.language.es_es/strings.po b/addons/skin.estuary/language/resource.language.es_es/strings.po index c2e4ca90e1e52..721e8e7e10ec0 100644 --- a/addons/skin.estuary/language/resource.language.es_es/strings.po +++ b/addons/skin.estuary/language/resource.language.es_es/strings.po @@ -7,15 +7,15 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2024-06-21 17:23+0000\n" -"Last-Translator: roliverosc <roliverosc@hotmail.com>\n" +"PO-Revision-Date: 2024-10-20 06:47+0000\n" +"Last-Translator: José Antonio Alvarado <jalvarado0.eses@gmail.com>\n" "Language-Team: Spanish (Spain) <https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/es_es/>\n" "Language: es_es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.6\n" +"X-Generator: Weblate 5.7.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Avance rápido" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "Editar widgets" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Fabricante de la cámara" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Lista amplia" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "Transmisiones de vídeo" + msgctxt "#31110" msgid "Enter files section" msgstr "Entrar en la sección archivos" @@ -399,20 +408,20 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Vea sus imágenes personales o descargue uno de los muchos complementos de imágenes del repositorio oficial." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Activar canal de audio" +msgid "Audio streams" +msgstr "Transmisiones de audio" msgctxt "#31113" msgid "Search local library" -msgstr "Buscar en la biblioteca local" +msgstr "Buscar en biblioteca local" msgctxt "#31114" msgid "Search YouTube" msgstr "Buscar en YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Cambiar subtítulos" +msgid "Subtitles streams" +msgstr "Secuencias de subtítulos" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Sistema" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "canales" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Activar canal de audio" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Cambiar subtítulos" + #~ msgctxt "#31612" #~ msgid "Add version" #~ msgstr "Añadir versión" diff --git a/addons/skin.estuary/language/resource.language.es_mx/strings.po b/addons/skin.estuary/language/resource.language.es_mx/strings.po index db737f2845984..f28deead30271 100644 --- a/addons/skin.estuary/language/resource.language.es_mx/strings.po +++ b/addons/skin.estuary/language/resource.language.es_mx/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Avance rápido" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Fabricante de la cámara" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Lista ancha" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Entrar en la sección de archivos" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Mira tus fotos personales o descarga uno de los muchos complementos de imágenes del repositorio oficial." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Alternar secuencia de audio" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Buscar en YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Cambiar subtítulo" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Sistema" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Alternar secuencia de audio" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Cambiar subtítulo" + #~ msgctxt "#31612" #~ msgid "Add version" #~ msgstr "Agregar versión" diff --git a/addons/skin.estuary/language/resource.language.et_ee/strings.po b/addons/skin.estuary/language/resource.language.et_ee/strings.po index b3d8450e14c1b..9fd1f71009ee1 100644 --- a/addons/skin.estuary/language/resource.language.et_ee/strings.po +++ b/addons/skin.estuary/language/resource.language.et_ee/strings.po @@ -7,15 +7,15 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-12-04 09:42+0000\n" -"Last-Translator: Christian Gade <gade@kodi.tv>\n" +"PO-Revision-Date: 2024-08-04 13:44+0000\n" +"Last-Translator: rimasx <riks_12@hot.ee>\n" "Language-Team: Estonian <https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/et_ee/>\n" "Language: et_ee\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.2.1\n" +"X-Generator: Weblate 5.6.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Edasi" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "Kohanda vidinaid" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Kaamera valmistaja" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Lai loend" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Ava jaotis 'Failid'" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Vaata oma pilte või lae ametlikust hoidlast alla piltide lisamoodulid." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Vaheta heliriba" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Otsi YouTube-ist" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Vaheta subtiitrid" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Süsteem" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Vaheta heliriba" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Vaheta subtiitrid" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Lisad" diff --git a/addons/skin.estuary/language/resource.language.eu_es/strings.po b/addons/skin.estuary/language/resource.language.eu_es/strings.po index 98446d8a3ad19..9d087e8411f3a 100644 --- a/addons/skin.estuary/language/resource.language.eu_es/strings.po +++ b/addons/skin.estuary/language/resource.language.eu_es/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Bizkor aurreratu" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Kameraren egilea" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Zerrenda zabala" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Sartu fitxategiak sekziora" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Ikusi zure irudi pertsonalak edo deskargatu biltegi ofizialeko irudi gehigarri ugarietako bat." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Txandakatu audio korrontea" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "Bilatu YouTube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "Sistema" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Txandakatu audio korrontea" + #~ msgctxt "#31137" #~ msgid "PVR info" #~ msgstr "PVR informazioa" diff --git a/addons/skin.estuary/language/resource.language.fa_af/strings.po b/addons/skin.estuary/language/resource.language.fa_af/strings.po index 16095f9f8fff0..8fe3d926edd8e 100644 --- a/addons/skin.estuary/language/resource.language.fa_af/strings.po +++ b/addons/skin.estuary/language/resource.language.fa_af/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.fa_ir/strings.po b/addons/skin.estuary/language/resource.language.fa_ir/strings.po index b8f2bb3474375..4ae47cde0b324 100644 --- a/addons/skin.estuary/language/resource.language.fa_ir/strings.po +++ b/addons/skin.estuary/language/resource.language.fa_ir/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "پیش‌روی سریع" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -399,6 +403,11 @@ msgctxt "#31107" msgid "WideList" msgstr "فهرست پهناور" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -409,8 +418,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "تغییر جریان صدا" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -421,8 +430,8 @@ msgid "Search YouTube" msgstr "جست‌وجوی یوتوب" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "تغییر وضعیت زیرنویس" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -743,6 +752,19 @@ msgctxt "#31611" msgid "System" msgstr "سیستم" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "تغییر جریان صدا" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "تغییر وضعیت زیرنویس" + #~ msgctxt "#31059" #~ msgid "Select + X" #~ msgstr "گزینش + X" diff --git a/addons/skin.estuary/language/resource.language.fi_fi/strings.po b/addons/skin.estuary/language/resource.language.fi_fi/strings.po index 19a7e7d3b68f0..ff731c3db1c72 100644 --- a/addons/skin.estuary/language/resource.language.fi_fi/strings.po +++ b/addons/skin.estuary/language/resource.language.fi_fi/strings.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-11-28 18:10+0000\n" +"PO-Revision-Date: 2024-08-02 17:06+0000\n" "Last-Translator: Oskari Lavinto <olavinto@protonmail.com>\n" "Language-Team: Finnish <https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/fi_fi/>\n" "Language: fi_fi\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.2.1\n" +"X-Generator: Weblate 5.6.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -171,7 +171,7 @@ msgstr "Oma arvio" msgctxt "#31034" msgid "Extended info" -msgstr "ExtendedInfo" +msgstr "Laajat tiedot" msgctxt "#31035" msgid "Pages" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Eteenpäin" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "Muokkaa widgettejä" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Kameran valmistaja" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Leveä lista" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Avaa tiedostonäkymä" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Katsele omia kuviasi tai asenna jokin monista kuvalisäosista virallisesta jakeluvarastosta." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Vaihda ääniraitaa" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Etsi YouTubesta" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Vaihda tekstitystä" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Järjestelmä" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Vaihda ääniraitaa" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Vaihda tekstitystä" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Lisäsisältö" diff --git a/addons/skin.estuary/language/resource.language.fil/strings.po b/addons/skin.estuary/language/resource.language.fil/strings.po index 06f4872286e71..c1d4b48eba9e0 100644 --- a/addons/skin.estuary/language/resource.language.fil/strings.po +++ b/addons/skin.estuary/language/resource.language.fil/strings.po @@ -192,6 +192,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -399,6 +403,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -409,7 +418,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -421,7 +430,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -742,3 +751,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.fo_fo/strings.po b/addons/skin.estuary/language/resource.language.fo_fo/strings.po index 2cd24233e4111..b78e6e669a4cc 100644 --- a/addons/skin.estuary/language/resource.language.fo_fo/strings.po +++ b/addons/skin.estuary/language/resource.language.fo_fo/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "Skipan" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.fr_ca/strings.po b/addons/skin.estuary/language/resource.language.fr_ca/strings.po index 23a0fc0777ed8..15cf706c06a76 100644 --- a/addons/skin.estuary/language/resource.language.fr_ca/strings.po +++ b/addons/skin.estuary/language/resource.language.fr_ca/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Avance rapide" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Fabricant de l’appareil photo" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Liste large" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Se rendre à la section des fichiers" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Visualisez vos images personnelles ou téléchargez l’un des nombreux addiciels du dépôt officiel." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Activer/désactiver le flux audio" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Rechercher sur YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Changer les sous-titres" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Système" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Activer/désactiver le flux audio" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Changer les sous-titres" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Suppléments" diff --git a/addons/skin.estuary/language/resource.language.fr_fr/strings.po b/addons/skin.estuary/language/resource.language.fr_fr/strings.po index fd0020c3e6a8b..a6dc9b5df1eda 100644 --- a/addons/skin.estuary/language/resource.language.fr_fr/strings.po +++ b/addons/skin.estuary/language/resource.language.fr_fr/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Avance rapide" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Fabricant caméra" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Liste large" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Section des fichiers" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Permet de voir les images personnelles ou de télécharger une des nombreuses extension images du dépôt officiel." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Changer de flux audio" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Rechercher sur YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Changer de sous-titres" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Système" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Changer de flux audio" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Changer de sous-titres" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Extras" diff --git a/addons/skin.estuary/language/resource.language.gl_es/strings.po b/addons/skin.estuary/language/resource.language.gl_es/strings.po index 3fa0b7d769818..488e3520c4872 100644 --- a/addons/skin.estuary/language/resource.language.gl_es/strings.po +++ b/addons/skin.estuary/language/resource.language.gl_es/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Axiña adiante" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Fabricante da cámara" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Lista ampla" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Introducir sección de ficheiros" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Mira as túas imaxes persoais ou descarga un dos moitos complementos de imaxes dende o repositorio oficial." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Escoller fluxo de son" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "Buscar no YouTube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "Sistema" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Escoller fluxo de son" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Extras" diff --git a/addons/skin.estuary/language/resource.language.he_il/strings.po b/addons/skin.estuary/language/resource.language.he_il/strings.po index 49b65864ea7b3..3f6c63dcee25f 100644 --- a/addons/skin.estuary/language/resource.language.he_il/strings.po +++ b/addons/skin.estuary/language/resource.language.he_il/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "הרצה קדימה" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "יצרן מצלמה" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "רשימה רחבה" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "הזן מקטע קבצים" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "הצג את התמונות האישיות שלך או הורד אחת מהרחבות התמונה הרבות מהמאגר הרשמי." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "החלף זרם שמע" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "חפש ביו-טיוב" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "מערכת" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "החלף זרם שמע" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "מיוחדים" diff --git a/addons/skin.estuary/language/resource.language.hi_in/strings.po b/addons/skin.estuary/language/resource.language.hi_in/strings.po index 20df134699a7c..4213954616dcc 100644 --- a/addons/skin.estuary/language/resource.language.hi_in/strings.po +++ b/addons/skin.estuary/language/resource.language.hi_in/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "प्रणाली" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.hr_hr/strings.po b/addons/skin.estuary/language/resource.language.hr_hr/strings.po index 7256146e55d3b..e5f953920c241 100644 --- a/addons/skin.estuary/language/resource.language.hr_hr/strings.po +++ b/addons/skin.estuary/language/resource.language.hr_hr/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Premotavanje unaprijed" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Proizvođač kamere" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Širok popis" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Otvori odjeljak datoteka" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Gledajte svoje osobne slike ili preuzmite jedan od mnogih slikovnih dodataka iz službenog repozitorija." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Odaberi zvučni zapis" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Pretraži YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Odaberi podnaslov" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Sustav" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Odaberi zvučni zapis" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Odaberi podnaslov" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Dodatno" diff --git a/addons/skin.estuary/language/resource.language.hu_hu/strings.po b/addons/skin.estuary/language/resource.language.hu_hu/strings.po index 31fe7bace9e90..626ef59bc99c3 100644 --- a/addons/skin.estuary/language/resource.language.hu_hu/strings.po +++ b/addons/skin.estuary/language/resource.language.hu_hu/strings.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-11-28 18:10+0000\n" +"PO-Revision-Date: 2024-10-08 13:49+0000\n" "Last-Translator: Frodo19 <bilbohu@gmail.com>\n" "Language-Team: Hungarian <https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/hu_hu/>\n" "Language: hu_hu\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.2.1\n" +"X-Generator: Weblate 5.7.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -155,7 +155,7 @@ msgstr "Utoljára bejelentkezve" #. Label to show the system memory usage msgctxt "#31030" msgid "System memory usage" -msgstr "Rendszer memóriahasználata" +msgstr "Memória használat" msgctxt "#31031" msgid "Version info" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Előretekerés" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "Widgetek szerkesztése" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Fényképezőkép gyártó" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Széles Lista" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Fájlok megadása szakasz" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Nézd meg a saját fényképeidet vagy tölts le egyet a sok képkiegészítő közül a hivatalos kiegészítő tárhelyről." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Hangfolyam ki- és bekapcsolása" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "YouTube keresés" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Felirat váltás" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -720,7 +729,7 @@ msgstr "A rendszer renderelési sebessége" #. Label to show the system CPU usage msgctxt "#31609" msgid "System CPU usage" -msgstr "Rendszer CPU használat" +msgstr "CPU használat" #. Label to show the media (metadata) info page msgctxt "#31610" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Rendszer" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Hangfolyam ki- és bekapcsolása" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Felirat váltás" + #~ msgctxt "#31612" #~ msgid "Add version" #~ msgstr "Verzió hozzáadása" diff --git a/addons/skin.estuary/language/resource.language.hy_am/strings.po b/addons/skin.estuary/language/resource.language.hy_am/strings.po index 4219eae6bc80c..fdabe80c8ced2 100644 --- a/addons/skin.estuary/language/resource.language.hy_am/strings.po +++ b/addons/skin.estuary/language/resource.language.hy_am/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.id_id/strings.po b/addons/skin.estuary/language/resource.language.id_id/strings.po index 0b6c8dbdea470..ea44f26cbf1d7 100644 --- a/addons/skin.estuary/language/resource.language.id_id/strings.po +++ b/addons/skin.estuary/language/resource.language.id_id/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Mempercepat" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -399,6 +403,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Daftar lebar" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -409,8 +418,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Lihat gambar pribadi Anda atau unduh salah satu dari banyak pengaya gambar dari repositori resmi." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Alihkan aliran audio" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -421,8 +430,8 @@ msgid "Search YouTube" msgstr "Cari YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Alihkan takarir" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -743,6 +752,19 @@ msgctxt "#31611" msgid "System" msgstr "Sistem" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Alihkan aliran audio" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Alihkan takarir" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Ekstra" diff --git a/addons/skin.estuary/language/resource.language.is_is/strings.po b/addons/skin.estuary/language/resource.language.is_is/strings.po index 8b61f14c06390..6fc20fdf979ae 100644 --- a/addons/skin.estuary/language/resource.language.is_is/strings.po +++ b/addons/skin.estuary/language/resource.language.is_is/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Hratt áfram" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Framleiðandi myndavélar" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Breiðlisti" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Fara í skráahlutann" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Skoðaðu þínar eigin myndir eða náðu í einhverja af fjölmörgum ljósmyndaviðbótum úr opinbera hugbúnaðarsafninu." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Víxla hljóðstreymi" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Leita á YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Víxla skjátexta af/á" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Kerfi" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Víxla hljóðstreymi" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Víxla skjátexta af/á" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Aukahlutir" diff --git a/addons/skin.estuary/language/resource.language.it_it/strings.po b/addons/skin.estuary/language/resource.language.it_it/strings.po index c36ca99141dd2..dca63045dc8c2 100644 --- a/addons/skin.estuary/language/resource.language.it_it/strings.po +++ b/addons/skin.estuary/language/resource.language.it_it/strings.po @@ -5,9 +5,9 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-11-26 21:18+0000\n" +"PO-Revision-Date: 2024-10-19 07:10+0000\n" "Last-Translator: Massimo Pissarello <mapi68@gmail.com>\n" "Language-Team: Italian <https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/it_it/>\n" "Language: it_it\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.2.1\n" +"X-Generator: Weblate 5.7.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Avanti veloce" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "Modifica widget" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Produttore fotocamera" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Elenco ampio" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "Tracce video" + msgctxt "#31110" msgid "Enter files section" msgstr "Entra a sfogliare i file" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Vedi le tue immagini personali o scarica uno dei tanti add-on immagini dal repository ufficiale." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Cambia traccia audio" +msgid "Audio streams" +msgstr "Tracce audio" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Cerca su YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Cambia traccia sottotitoli" +msgid "Subtitles streams" +msgstr "Tracce sottotitoli" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Sistema" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "canali" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Cambia traccia audio" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Cambia traccia sottotitoli" + #~ msgctxt "#31612" #~ msgid "Add version" #~ msgstr "Aggiungi versione" diff --git a/addons/skin.estuary/language/resource.language.ja_jp/strings.po b/addons/skin.estuary/language/resource.language.ja_jp/strings.po index ce3bc5637bac7..1e40bb1e50cf0 100644 --- a/addons/skin.estuary/language/resource.language.ja_jp/strings.po +++ b/addons/skin.estuary/language/resource.language.ja_jp/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "早送り" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "カメラメーカー" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "ワイドリスト" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "ファイルセクションに入る" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "個人的な写真を表示またはオフィシャルリポジトリから画像アドオンをダウンロード。" msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "オーディオストリームをトグル" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "YouTube検索" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "字幕を切り替える" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "システム" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "オーディオストリームをトグル" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "字幕を切り替える" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "エクストラ" diff --git a/addons/skin.estuary/language/resource.language.kn_in/strings.po b/addons/skin.estuary/language/resource.language.kn_in/strings.po index 6071c977d1d8e..a78f10bf28b79 100644 --- a/addons/skin.estuary/language/resource.language.kn_in/strings.po +++ b/addons/skin.estuary/language/resource.language.kn_in/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.ko_kr/strings.po b/addons/skin.estuary/language/resource.language.ko_kr/strings.po index 403065ecf843b..472a7fff1ce31 100644 --- a/addons/skin.estuary/language/resource.language.ko_kr/strings.po +++ b/addons/skin.estuary/language/resource.language.ko_kr/strings.po @@ -5,9 +5,9 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-11-23 17:23+0000\n" +"PO-Revision-Date: 2024-10-19 07:10+0000\n" "Last-Translator: Minho Park <parkmino@gmail.com>\n" "Language-Team: Korean <https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/ko_kr/>\n" "Language: ko_kr\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.1\n" +"X-Generator: Weblate 5.7.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "빨리감기" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "위젯 수정" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "카메라 제조사" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "WideList" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "비디오 스트림" + msgctxt "#31110" msgid "Enter files section" msgstr "파일 섹션 가기" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "개인 사진을 보거나 공식 저장소의 수많은 이미지 애드온에서 사진을 다운로드합니다." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "오디오 스트림 전환" +msgid "Audio streams" +msgstr "오디오 스트림" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "YouTube 검색" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "자막 전환" +msgid "Subtitles streams" +msgstr "자막 스트림" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "시스템" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "채널" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "오디오 스트림 전환" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "자막 전환" + #~ msgctxt "#31612" #~ msgid "Add version" #~ msgstr "버전 추가" diff --git a/addons/skin.estuary/language/resource.language.lt_lt/strings.po b/addons/skin.estuary/language/resource.language.lt_lt/strings.po index 83af0e6229a9a..f771be8e959e0 100644 --- a/addons/skin.estuary/language/resource.language.lt_lt/strings.po +++ b/addons/skin.estuary/language/resource.language.lt_lt/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Prasukti pirmyn" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Kameros gamintojas" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Platus sąrašas" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Eiti į failų skyrių" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Peržiūrėkite asmeninius paveikslėlius arba atsisiųskite vieną iš daugelio atvaizdų priedų iš oficialios saugyklos." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Perjungti garso srautą" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Ieškoti YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Perjungti subtitrus" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Sistema" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Perjungti garso srautą" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Perjungti subtitrus" + #~ msgctxt "#31059" #~ msgid "Select + X" #~ msgstr "Select + X" diff --git a/addons/skin.estuary/language/resource.language.lv_lv/strings.po b/addons/skin.estuary/language/resource.language.lv_lv/strings.po index 3701babf247e9..cd7602891fe22 100644 --- a/addons/skin.estuary/language/resource.language.lv_lv/strings.po +++ b/addons/skin.estuary/language/resource.language.lv_lv/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Patīt uz priekšu" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Kameras ražotājs" @@ -392,6 +396,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -402,7 +411,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -414,7 +423,7 @@ msgid "Search YouTube" msgstr "Meklēt YouTube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -736,6 +745,11 @@ msgctxt "#31611" msgid "System" msgstr "Sistēma" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + #~ msgctxt "#31137" #~ msgid "PVR info" #~ msgstr "PVR info" diff --git a/addons/skin.estuary/language/resource.language.mi/strings.po b/addons/skin.estuary/language/resource.language.mi/strings.po index 4095914941c3f..07e4d09881092 100644 --- a/addons/skin.estuary/language/resource.language.mi/strings.po +++ b/addons/skin.estuary/language/resource.language.mi/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "Pūnaha" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.mk_mk/strings.po b/addons/skin.estuary/language/resource.language.mk_mk/strings.po index 7d462739375d5..18f4342b14bb0 100644 --- a/addons/skin.estuary/language/resource.language.mk_mk/strings.po +++ b/addons/skin.estuary/language/resource.language.mk_mk/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Забрзано нанапред" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Производител на камера" @@ -392,6 +396,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -402,8 +411,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Промени аудио стрим" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -414,7 +423,7 @@ msgid "Search YouTube" msgstr "Барај на YouTube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -736,6 +745,15 @@ msgctxt "#31611" msgid "System" msgstr "Систем" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Промени аудио стрим" + #~ msgctxt "#31138" #~ msgid "Player process info" #~ msgstr "Информации за процесот на плеерот" diff --git a/addons/skin.estuary/language/resource.language.ml_in/strings.po b/addons/skin.estuary/language/resource.language.ml_in/strings.po index 27105b7331160..1b5e4a11b7c77 100644 --- a/addons/skin.estuary/language/resource.language.ml_in/strings.po +++ b/addons/skin.estuary/language/resource.language.ml_in/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.mn_mn/strings.po b/addons/skin.estuary/language/resource.language.mn_mn/strings.po index 441868558b5f9..91469a08309f7 100644 --- a/addons/skin.estuary/language/resource.language.mn_mn/strings.po +++ b/addons/skin.estuary/language/resource.language.mn_mn/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.ms_my/strings.po b/addons/skin.estuary/language/resource.language.ms_my/strings.po index 1536284e6e42d..442659e03d822 100644 --- a/addons/skin.estuary/language/resource.language.ms_my/strings.po +++ b/addons/skin.estuary/language/resource.language.ms_my/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Maju pantas" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Pengilang kamera" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "SenaraiLebar" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Masukkan seksyen fail" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Lihat gambar peribadi anda atau muat turun salah satu tambahan imej dari repositori rasmi." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Togol strim audio" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "Gelintar YouTube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "Sistem" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Togol strim audio" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Ekstra" diff --git a/addons/skin.estuary/language/resource.language.mt_mt/strings.po b/addons/skin.estuary/language/resource.language.mt_mt/strings.po index e908fd95ca88f..dc37f0bfd4134 100644 --- a/addons/skin.estuary/language/resource.language.mt_mt/strings.po +++ b/addons/skin.estuary/language/resource.language.mt_mt/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Għaġġel quddiem" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -399,6 +403,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -409,7 +418,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -421,7 +430,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -742,3 +751,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "Sistema" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.my_mm/strings.po b/addons/skin.estuary/language/resource.language.my_mm/strings.po index 6b03f89f93a96..64d53a2f93e10 100644 --- a/addons/skin.estuary/language/resource.language.my_mm/strings.po +++ b/addons/skin.estuary/language/resource.language.my_mm/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "စနစ်" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.nb_no/strings.po b/addons/skin.estuary/language/resource.language.nb_no/strings.po index 7e92abf4b7b65..56fc7da42f159 100644 --- a/addons/skin.estuary/language/resource.language.nb_no/strings.po +++ b/addons/skin.estuary/language/resource.language.nb_no/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Spol framover" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Kameramerke" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Bred liste" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Gå til filområde" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Se dine personlige bilder eller last ned en av de mange bildetilleggene fra den offisielle pakkebrønnen." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Endre lydstrøm" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "Søk på YouTube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "System" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Endre lydstrøm" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Annet" diff --git a/addons/skin.estuary/language/resource.language.nl_nl/strings.po b/addons/skin.estuary/language/resource.language.nl_nl/strings.po index ac838f82d46b8..102efedbf709c 100644 --- a/addons/skin.estuary/language/resource.language.nl_nl/strings.po +++ b/addons/skin.estuary/language/resource.language.nl_nl/strings.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2024-05-20 15:13+0000\n" "Last-Translator: Mark Peters <forkless@gmail.com>\n" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Vooruitspoelen" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Camerafabrikant" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Brede lijst" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Ga naar bestandensectie" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Toon je persoonlijke afbeeldingen of download één van de vele afbeeldingsadd-ons uit de officiële repository." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Verander audiostream" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Zoek op YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Schakel ondertitel" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Systeem" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Verander audiostream" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Schakel ondertitel" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Extra's" diff --git a/addons/skin.estuary/language/resource.language.pl_pl/strings.po b/addons/skin.estuary/language/resource.language.pl_pl/strings.po index eb50fa17cab85..06591da45becd 100644 --- a/addons/skin.estuary/language/resource.language.pl_pl/strings.po +++ b/addons/skin.estuary/language/resource.language.pl_pl/strings.po @@ -5,9 +5,9 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-11-23 17:23+0000\n" +"PO-Revision-Date: 2024-10-19 07:10+0000\n" "Last-Translator: Marek Adamski <fevbew@wp.pl>\n" "Language-Team: Polish <https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/pl_pl/>\n" "Language: pl_pl\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" -"X-Generator: Weblate 5.1\n" +"X-Generator: Weblate 5.7.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Przewiń" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "Edytuj widżety" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Producent aparatu" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Lista szeroka" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "Strumienie wideo" + msgctxt "#31110" msgid "Enter files section" msgstr "Przejdź do przeglądarki plików" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Umożliwia podgląd osobisty obrazów lub pobranie jednej z wielu wtyczek obrazów z oficjalnego repozytorium." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Przełącz ścieżkę dźwiękową" +msgid "Audio streams" +msgstr "Strumienie audio" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Szukaj w serwisie YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Przełącz napisy" +msgid "Subtitles streams" +msgstr "Strumienie napisów" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "System" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "kanały" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Przełącz ścieżkę dźwiękową" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Przełącz napisy" + #~ msgctxt "#31612" #~ msgid "Add version" #~ msgstr "Dodaj wersję" diff --git a/addons/skin.estuary/language/resource.language.pt_br/strings.po b/addons/skin.estuary/language/resource.language.pt_br/strings.po index 0bb8d039bb413..4c2bfe490e1a6 100644 --- a/addons/skin.estuary/language/resource.language.pt_br/strings.po +++ b/addons/skin.estuary/language/resource.language.pt_br/strings.po @@ -5,17 +5,17 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2024-02-12 00:31+0000\n" -"Last-Translator: Christian Gade <gade@kodi.tv>\n" +"PO-Revision-Date: 2024-08-07 02:18+0000\n" +"Last-Translator: icarok99 <icarok00@gmail.com>\n" "Language-Team: Portuguese (Brazil) <https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/pt_br/>\n" "Language: pt_br\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 5.3\n" +"X-Generator: Weblate 5.6.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Avanço rápido" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "Editar widgets" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Fabricante da câmara" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Lista Larga" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Entrar na seção arquivos" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Visualizar suas imagens pessoais ou transferir um dos muitos addons de imagens disponíveis no repositório oficial." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Alterar canal de áudio" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Procurar no YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Alternar legenda" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Sistema" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Alterar canal de áudio" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Alternar legenda" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Extras" diff --git a/addons/skin.estuary/language/resource.language.pt_pt/strings.po b/addons/skin.estuary/language/resource.language.pt_pt/strings.po index 640a3d0ce40c1..0e6fb77f758d4 100644 --- a/addons/skin.estuary/language/resource.language.pt_pt/strings.po +++ b/addons/skin.estuary/language/resource.language.pt_pt/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Avançar" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Fabricante da câmara" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Lista Ampla" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Indique a secção dos ficheiros" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Ver as suas imagens pessoais ou descarregar um dos vários add-ons de imagens do repositório oficial." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Comutar a emissão de áudio" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "Pesquisa no YouTube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "Sistema" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Comutar a emissão de áudio" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Extras" diff --git a/addons/skin.estuary/language/resource.language.ro_ro/strings.po b/addons/skin.estuary/language/resource.language.ro_ro/strings.po index 5299899785677..ba26320fe302b 100644 --- a/addons/skin.estuary/language/resource.language.ro_ro/strings.po +++ b/addons/skin.estuary/language/resource.language.ro_ro/strings.po @@ -7,15 +7,15 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-12-04 09:42+0000\n" -"Last-Translator: Christian Gade <gade@kodi.tv>\n" +"PO-Revision-Date: 2024-08-27 02:47+0000\n" +"Last-Translator: Daniel <danny3@tutanota.com>\n" "Language-Team: Romanian <https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/ro_ro/>\n" "Language: ro_ro\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;\n" -"X-Generator: Weblate 5.2.1\n" +"X-Generator: Weblate 5.6.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -51,15 +51,15 @@ msgstr "Schimbare mod" msgctxt "#31005" msgid "Watch as 2D" -msgstr "Afișează în 2D" +msgstr "Vizionează în 2D" msgctxt "#31006" msgid "Random movies" -msgstr "Film aleator" +msgstr "Filme artistice aleatoare" msgctxt "#31007" msgid "Unwatched movies" -msgstr "Filme nevăzute" +msgstr "Filme artistice nevăzute" msgctxt "#31008" msgid "Enable category widgets" @@ -71,7 +71,7 @@ msgstr "Descarcă pictogramele" msgctxt "#31010" msgid "In progress movies" -msgstr "Filme în curs de vizionare" +msgstr "Filme artistice în curs de vizionare" msgctxt "#31011" msgid "Most played albums" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Derulează înainte" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "Editare accesorii" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Producător aparat foto" @@ -299,7 +303,7 @@ msgstr "Durata totală" msgctxt "#31075" msgid "Movie sets" -msgstr "Colecție de filme" +msgstr "Colecții de filme artistice" msgctxt "#31079" msgid "Cast not available" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Listă lată" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Intrare secțiune fișiere" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Vizualizați-vă imaginile personale sau descărcați unul dintre numeroasele suplimente de imagini din depozitul oficial." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Comută fluxul audio" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Caută pe YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Comută subtitlul" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -629,7 +638,7 @@ msgstr "Animați fundalul" #. Label of a setting msgctxt "#31168" msgid "Show posters instead of thumbs for musicvideos" -msgstr "" +msgstr "Arată afișe în loc de minitaturi pentru videoclipurile muzicale" #. Description label for skin settings area msgctxt "#31169" @@ -639,22 +648,22 @@ msgstr "" #. Label for OSD settings category msgctxt "#31170" msgid "On screen display" -msgstr "" +msgstr "Afișaj pe ecran" #. Helper text for the label of OSD settings category msgctxt "#31171" msgid "On screen display (OSD) related settings" -msgstr "" +msgstr "Stabiliri legate de afișajul pe ecran (APE)" #. Setting Automatically close video OSD msgctxt "#31172" msgid "Automatically close video OSD" -msgstr "" +msgstr "Închide automat afișajul pe ecran (APE)" #. Setting auto close time for video osd msgctxt "#31173" msgid "Video OSD autoclose time (seconds)" -msgstr "" +msgstr "Închiderea automată a afijașului pe ecran video (secunde)" #. Setting to control what happens when clicking a music album on the home screen msgctxt "#31174" @@ -664,12 +673,12 @@ msgstr "" #. Setting to control what happens when clicking a TV show on the home screen msgctxt "#31175" msgid "Default select action for TV shows on the home screen" -msgstr "" +msgstr "Acțiunea implicită pentru filmele seriale de pe ecranul inițial" #. Setting to control what happens when clicking a movie set on the home screen msgctxt "#31176" msgid "Default select action for movie sets on the home screen" -msgstr "" +msgstr "Acțiunea implicită la selectare pentru colecțiile de filme artistice de pe ecranul inițial" # empty strings from id 31170 to 31599 #. Label to show the video codec name @@ -690,7 +699,7 @@ msgstr "Aspect video" #. Label to show the video bitrate msgctxt "#31603" msgid "Video bitrate" -msgstr "" +msgstr "Rata de biți video" #. Label to show the audio codec name msgctxt "#31604" @@ -705,7 +714,7 @@ msgstr "Canale audio" #. Label to show the audio bitrate msgctxt "#31606" msgid "Audio bitrate" -msgstr "" +msgstr "Rata de biți audio" #. Label to show the screen resolution msgctxt "#31607" @@ -715,7 +724,7 @@ msgstr "Rezoluție ecran" #. Label to show the system rendering speed msgctxt "#31608" msgid "System rendering speed" -msgstr "" +msgstr "Viteza de randare a sistemului" #. Label to show the system CPU usage msgctxt "#31609" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Sistem" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Comută fluxul audio" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Comută subtitlul" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Extra" diff --git a/addons/skin.estuary/language/resource.language.ru_ru/strings.po b/addons/skin.estuary/language/resource.language.ru_ru/strings.po index a96111907cf41..5727c359ff384 100644 --- a/addons/skin.estuary/language/resource.language.ru_ru/strings.po +++ b/addons/skin.estuary/language/resource.language.ru_ru/strings.po @@ -5,17 +5,17 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2024-02-12 00:31+0000\n" -"Last-Translator: Christian Gade <gade@kodi.tv>\n" +"PO-Revision-Date: 2024-08-09 14:17+0000\n" +"Last-Translator: Alexey <signfinder@gmail.com>\n" "Language-Team: Russian <https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/ru_ru/>\n" "Language: ru_ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" -"X-Generator: Weblate 5.3\n" +"X-Generator: Weblate 5.6.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Перемотка вперед" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "Редактирование виджетов" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Производитель камеры" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Широкий список" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Войти в раздел «Файлы»" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Просмотр Ваших фотографий или их загрузка с помощью фото дополнений из официального репозитория." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Переключить аудиодорожку" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Искать в YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Переключить субтитры" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Система" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Переключить аудиодорожку" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Переключить субтитры" + #~ msgctxt "#31059" #~ msgid "Select + X" #~ msgstr "Select + X" diff --git a/addons/skin.estuary/language/resource.language.si_lk/strings.po b/addons/skin.estuary/language/resource.language.si_lk/strings.po index 98e30ff46352d..bd6bc1481e8f0 100644 --- a/addons/skin.estuary/language/resource.language.si_lk/strings.po +++ b/addons/skin.estuary/language/resource.language.si_lk/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "පද්ධතිය" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.sk_sk/strings.po b/addons/skin.estuary/language/resource.language.sk_sk/strings.po index 6ee6325111579..6b65747ea8fb1 100644 --- a/addons/skin.estuary/language/resource.language.sk_sk/strings.po +++ b/addons/skin.estuary/language/resource.language.sk_sk/strings.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2024-04-02 18:25+0000\n" "Last-Translator: nextlooper42 <nextlooper42@protonmail.com>\n" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Pretočiť dopredu" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Výrobca fotoaparátu" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Široký zoznam" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Vstúpiť do sekcie súborov" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Zobrazte vaše osobné obrázky alebo si prevezmite jeden z mnohých obrázkových doplnkov z oficiálneho repozitára." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Prepnúť audio stopu" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Hľadať na YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Prepnúť titulky" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Systém" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Prepnúť audio stopu" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Prepnúť titulky" + #~ msgctxt "#31059" #~ msgid "Select + X" #~ msgstr "Vybrať + X" diff --git a/addons/skin.estuary/language/resource.language.sl_si/strings.po b/addons/skin.estuary/language/resource.language.sl_si/strings.po index dbee3bd9434a2..10df651d2f683 100644 --- a/addons/skin.estuary/language/resource.language.sl_si/strings.po +++ b/addons/skin.estuary/language/resource.language.sl_si/strings.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2024-05-15 07:51+0000\n" "Last-Translator: Simon <dolenec@gmail.com>\n" @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Naprej" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -397,6 +401,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Širok seznam" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -407,8 +416,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Oglejte si svoje osebne slike ali prenesite enega od številnih slikovnih dodatkov iz uradnega skladišča." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Preklop zvočnega toka" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -419,8 +428,8 @@ msgid "Search YouTube" msgstr "Poišči na YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Preklopi podnapise" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -741,6 +750,19 @@ msgctxt "#31611" msgid "System" msgstr "Sistem" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Preklop zvočnega toka" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Preklopi podnapise" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Dodatki" diff --git a/addons/skin.estuary/language/resource.language.sq_al/strings.po b/addons/skin.estuary/language/resource.language.sq_al/strings.po index 821be3cff7875..daa123ac04a67 100644 --- a/addons/skin.estuary/language/resource.language.sq_al/strings.po +++ b/addons/skin.estuary/language/resource.language.sq_al/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "Media" msgctxt "#31611" msgid "System" msgstr "Sistemi" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.sr_rs/strings.po b/addons/skin.estuary/language/resource.language.sr_rs/strings.po index b9db9dcd1c9a5..3db1401a49509 100644 --- a/addons/skin.estuary/language/resource.language.sr_rs/strings.po +++ b/addons/skin.estuary/language/resource.language.sr_rs/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Премотај унапред" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Произвођач камере" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "ШирокаЛиста" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Уђи у секцију датотека" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Гледајте ваше личне слике или преузмите неки од многобројних додатних програма са званичног спремишта." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Мењај аудио запис" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "Претражи YouTube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "Систем" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Мењај аудио запис" + #~ msgctxt "#31137" #~ msgid "PVR info" #~ msgstr "PVR информације" diff --git a/addons/skin.estuary/language/resource.language.sr_rs@latin/strings.po b/addons/skin.estuary/language/resource.language.sr_rs@latin/strings.po index 9daa68eceedf5..8a441a17983ba 100644 --- a/addons/skin.estuary/language/resource.language.sr_rs@latin/strings.po +++ b/addons/skin.estuary/language/resource.language.sr_rs@latin/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Premotaj unapred" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Proizvođač kamere" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "ŠirokaLista" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Uđi u sekciju datoteka" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Gledajte vaše lične slike ili preuzmite neki od mnogobrojnih dodatnih programa sa zvaničnog spremišta." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Menjaj audio zapis" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "Pretraži YouTube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "Sistem" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Menjaj audio zapis" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Dodaci" diff --git a/addons/skin.estuary/language/resource.language.sv_se/strings.po b/addons/skin.estuary/language/resource.language.sv_se/strings.po index 6c7842d4da711..4af05c808ee6d 100644 --- a/addons/skin.estuary/language/resource.language.sv_se/strings.po +++ b/addons/skin.estuary/language/resource.language.sv_se/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Spolar framåt" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Kameratillverkare" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Bred lista" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Gå till filsektionen" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Visa dina personliga bilder eller hämta en av de många bild tilläggen från det officiella förrådet." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Växla ljudström" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Sök på Youtube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Växla undertext" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "System" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Växla ljudström" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Växla undertext" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Extra" diff --git a/addons/skin.estuary/language/resource.language.szl/strings.po b/addons/skin.estuary/language/resource.language.szl/strings.po index 47deb8190ce47..af7dbb83339da 100644 --- a/addons/skin.estuary/language/resource.language.szl/strings.po +++ b/addons/skin.estuary/language/resource.language.szl/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Do przodku" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Producynt aparatu" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Szyroki wykŏz" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Wlyź do sekcyji Zbiory" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Umożniŏ pokŏzowanie włŏsnych ôbrŏzōw lebo sebranie jednyj z mocki przidŏwek ôbrŏzōw z ôficjalnygo repozytoriōm." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Przeszaltruj ściyżkã klangowõ" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "Szukej w YouTube" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "Systym" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Przeszaltruj ściyżkã klangowõ" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Ekstra" diff --git a/addons/skin.estuary/language/resource.language.ta_in/strings.po b/addons/skin.estuary/language/resource.language.ta_in/strings.po index d986fc92e58ec..8eed3914498c4 100644 --- a/addons/skin.estuary/language/resource.language.ta_in/strings.po +++ b/addons/skin.estuary/language/resource.language.ta_in/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "கணிணி" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.te_in/strings.po b/addons/skin.estuary/language/resource.language.te_in/strings.po index 6340b6ef5a5c4..77980f781d1b8 100644 --- a/addons/skin.estuary/language/resource.language.te_in/strings.po +++ b/addons/skin.estuary/language/resource.language.te_in/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.tg_tj/strings.po b/addons/skin.estuary/language/resource.language.tg_tj/strings.po index 5082713d64be3..1d6d3fb58838b 100644 --- a/addons/skin.estuary/language/resource.language.tg_tj/strings.po +++ b/addons/skin.estuary/language/resource.language.tg_tj/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "Media" msgctxt "#31611" msgid "System" msgstr "Система" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.th_th/strings.po b/addons/skin.estuary/language/resource.language.th_th/strings.po index a5343f6bcbd22..26aa0b2d6608d 100644 --- a/addons/skin.estuary/language/resource.language.th_th/strings.po +++ b/addons/skin.estuary/language/resource.language.th_th/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "ไปหน้าแบบเร็ว" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "ผู้ผลิตกล้อง" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "รายการแบบกว้าง" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "เข้าสู่ส่วนแฟ้ม" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "ดูรูปภาพส่วนตัวของคุณหรือดาวน์โหลดหนึ่งในส่วนเสริมรูปภาพอันหลากหลาย จากแหล่งโปรแกรมอย่างเป็นทางการ" msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "สลับกระแสเสียง" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,7 +420,7 @@ msgid "Search YouTube" msgstr "ค้นหาจากยูทูป" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -732,6 +741,15 @@ msgctxt "#31611" msgid "System" msgstr "ระบบ" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "สลับกระแสเสียง" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "ส่วนพิเศษ" diff --git a/addons/skin.estuary/language/resource.language.tr_tr/strings.po b/addons/skin.estuary/language/resource.language.tr_tr/strings.po index 4a79ac7b79f76..6b81f003546e7 100644 --- a/addons/skin.estuary/language/resource.language.tr_tr/strings.po +++ b/addons/skin.estuary/language/resource.language.tr_tr/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "İleri sar" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Kamera üreticisi" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Geniş Liste" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Dosyalar bölümüne girin" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Kişisel resimlerinizi görüntüleyin veya resmi depodaki birçok resim eklentisinden birini indirin." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Ses akışını değiştir" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "YouTube'da ara" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Altyazıyı değiştir" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Sistem" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Ses akışını değiştir" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Altyazıyı değiştir" + #~ msgctxt "#31059" #~ msgid "Select + X" #~ msgstr "Select + X" diff --git a/addons/skin.estuary/language/resource.language.uk_ua/strings.po b/addons/skin.estuary/language/resource.language.uk_ua/strings.po index a7f6a2f84525c..95a49e1ea8096 100644 --- a/addons/skin.estuary/language/resource.language.uk_ua/strings.po +++ b/addons/skin.estuary/language/resource.language.uk_ua/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Перемотка вперед" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Виробник фотоапарата" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "Широкий список" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Вибрати файли" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Переглядайте ваші особисті фото або завантажте одну з багатьох надбудов фото з офіційного репозиторію." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Змінити аудіопотік" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Пошук на YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Перемкнути субтитри" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Система" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Змінити аудіопотік" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Перемкнути субтитри" + #~ msgctxt "#31059" #~ msgid "Select + X" #~ msgstr "Select + X" diff --git a/addons/skin.estuary/language/resource.language.uz_uz/strings.po b/addons/skin.estuary/language/resource.language.uz_uz/strings.po index 10390dc46c40a..ce344e4796467 100644 --- a/addons/skin.estuary/language/resource.language.uz_uz/strings.po +++ b/addons/skin.estuary/language/resource.language.uz_uz/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + # empty string with id 31040 msgctxt "#31041" msgid "Camera manufacturer" @@ -400,6 +404,11 @@ msgctxt "#31107" msgid "WideList" msgstr "" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + # empty strings from id 31108 to 31109 msgctxt "#31110" msgid "Enter files section" @@ -410,7 +419,7 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "" msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" msgctxt "#31113" @@ -422,7 +431,7 @@ msgid "Search YouTube" msgstr "" msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" msgctxt "#31116" @@ -743,3 +752,8 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "Tizim" + +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/language/resource.language.vi_vn/strings.po b/addons/skin.estuary/language/resource.language.vi_vn/strings.po index 31ca7718e9ca7..b7eaf12c943e7 100644 --- a/addons/skin.estuary/language/resource.language.vi_vn/strings.po +++ b/addons/skin.estuary/language/resource.language.vi_vn/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "Tua tới nhanh" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "Nhà sản xuất máy ảnh" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "WideList" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "Truy cập phần tập tin" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "Xem ảnh cá nhân của bạn hoặc tải xuống một trong nhiều tiện ích hình ảnh từ kho lưu trữ chính thức." msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "Chuyển đổi luồng âm thanh" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "Tìm kiếm Youtube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "Chuyển đổi phụ đề" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "Hệ thống" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "Chuyển đổi luồng âm thanh" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "Chuyển đổi phụ đề" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "Vai phụ" diff --git a/addons/skin.estuary/language/resource.language.zh_cn/strings.po b/addons/skin.estuary/language/resource.language.zh_cn/strings.po index 4e16c73a1a14f..b4c77717fe2d8 100644 --- a/addons/skin.estuary/language/resource.language.zh_cn/strings.po +++ b/addons/skin.estuary/language/resource.language.zh_cn/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "快进" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "相机制造商" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "宽列表" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "进入文件区" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "浏览你的个人图片或通过官方插件库图片插件下载。" msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "切换音频流" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "搜索 YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "切换字幕" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "系统" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "切换音频流" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "切换字幕" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "额外" diff --git a/addons/skin.estuary/language/resource.language.zh_tw/strings.po b/addons/skin.estuary/language/resource.language.zh_tw/strings.po index e21333ca6449d..6039ba69424eb 100644 --- a/addons/skin.estuary/language/resource.language.zh_tw/strings.po +++ b/addons/skin.estuary/language/resource.language.zh_tw/strings.po @@ -193,6 +193,10 @@ msgctxt "#31039" msgid "Fast forward" msgstr "快轉" +msgctxt "#31040" +msgid "Edit widgets" +msgstr "" + msgctxt "#31041" msgid "Camera manufacturer" msgstr "攝影機製造商" @@ -390,6 +394,11 @@ msgctxt "#31107" msgid "WideList" msgstr "寬版面列表" +# empty strings from id 31108 to 31108 +msgctxt "#31109" +msgid "Video streams" +msgstr "" + msgctxt "#31110" msgid "Enter files section" msgstr "進入檔案區" @@ -399,8 +408,8 @@ msgid "View your personal pictures or download one of the many image add-ons fro msgstr "檢視你的個人圖片,或下載官方的眾多影像附加元件。" msgctxt "#31112" -msgid "Toggle audio stream" -msgstr "切換聲道" +msgid "Audio streams" +msgstr "" msgctxt "#31113" msgid "Search local library" @@ -411,8 +420,8 @@ msgid "Search YouTube" msgstr "搜尋YouTube" msgctxt "#31115" -msgid "Toggle subtitle" -msgstr "切換字幕" +msgid "Subtitles streams" +msgstr "" msgctxt "#31116" msgid "Remove this main menu item" @@ -732,6 +741,19 @@ msgctxt "#31611" msgid "System" msgstr "系統" +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" + +#~ msgctxt "#31112" +#~ msgid "Toggle audio stream" +#~ msgstr "切換聲道" + +#~ msgctxt "#31115" +#~ msgid "Toggle subtitle" +#~ msgstr "切換字幕" + #~ msgctxt "#31615" #~ msgid "Extras" #~ msgstr "額外資訊" diff --git a/tools/android/packaging/xbmc/res/values-pl-rpl/strings.xml b/tools/android/packaging/xbmc/res/values-pl-rpl/strings.xml index f3734b2fa908b..574621c0b4bab 100644 --- a/tools/android/packaging/xbmc/res/values-pl-rpl/strings.xml +++ b/tools/android/packaging/xbmc/res/values-pl-rpl/strings.xml @@ -1,4 +1,18 @@ <?xml version="1.0" encoding="utf-8"?> <resources> <string name="suggestion_channel">Sugestie</string> + <string name="waiting_external">Oczekiwanie na pamięć zewnętrzną…</string> + <string name="continue_button">Kontynuuj</string> + <string name="error_title">Błąd</string> + <string name="info_title">Informacje</string> + <string name="exit_button">Zamknij</string> + <string name="clearing_cache">Czyszczenie pamięci podręcznej…</string> + <string name="external_ok">Pamięć zewnętrzna OK…</string> + <string name="starting">Uruchamianie %1$s…</string> + <string name="first_run">Przygotowanie do pierwszego uruchomienia. Proszę czekać…</string> + <string name="permission_denied">Odmowa dostępu!! Wychodzenie…</string> + <string name="no_find_apk">Nie można znaleźć pakietu.</string> + <string name="no_read_apk">Nie można odczytać pakietu.</string> + <string name="notice_dialog">%1$s wymaga dostępu do multimediów i plików urządzenia, aby działać. Zezwól na to za pomocą następującego okna dialogowego lub %1$s zakończy działanie.</string> + <string name="asking_permissions">Pytanie o uprawnienia…</string> </resources> \ No newline at end of file diff --git a/tools/android/packaging/xbmc/res/values-ro-rro/strings.xml b/tools/android/packaging/xbmc/res/values-ro-rro/strings.xml index efe0a1d8ee195..847e3157c52b2 100644 --- a/tools/android/packaging/xbmc/res/values-ro-rro/strings.xml +++ b/tools/android/packaging/xbmc/res/values-ro-rro/strings.xml @@ -1,3 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - </resources> \ No newline at end of file + <string name="suggestion_channel">Sugestii</string> +</resources> \ No newline at end of file From 459d640aba5344c7bc3f2387fe9f4c738ff65b7b Mon Sep 17 00:00:00 2001 From: rimasx <riks_12@hot.ee> Date: Fri, 1 Nov 2024 10:59:13 +0000 Subject: [PATCH 634/651] Translated using Weblate (Estonian (et_ee)) Currently translated at 80.0% (4 of 5 strings) Translation: Kodi core/audioencoder.kodi.builtin.wma Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-wma/et_ee/ --- .../resources/language/resource.language.et_ee/strings.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.et_ee/strings.po b/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.et_ee/strings.po index 778c45aa6227a..c548086623bca 100644 --- a/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.et_ee/strings.po +++ b/addons/audioencoder.kodi.builtin.wma/resources/language/resource.language.et_ee/strings.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2024-03-22 07:13+0000\n" +"PO-Revision-Date: 2024-11-01 11:31+0000\n" "Last-Translator: rimasx <riks_12@hot.ee>\n" "Language-Team: Estonian <https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-wma/et_ee/>\n" "Language: et_ee\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.4\n" +"X-Generator: Weblate 5.7.2\n" msgctxt "Addon Summary" msgid "WMA Audio Encoder" @@ -35,7 +35,7 @@ msgstr "Bitikiirus" #: resources/settings.xml msgctxt "#30001" msgid "Select which bitrate to use for the WMA audio encoder for audio compression." -msgstr "Vali, millist bitikiirust kasutada heli tihendamiseks WMA helikodeerija jaoks." +msgstr "Vali WMA helikooderis heli tihendamiseks kasutatav bitikiirus." #. Value format for with bitrate edited field #: resources/settings.xml From 20eabd65c73e35235f200244425f8d2e22e3c58b Mon Sep 17 00:00:00 2001 From: rimasx <riks_12@hot.ee> Date: Fri, 1 Nov 2024 10:47:52 +0000 Subject: [PATCH 635/651] Translated using Weblate (Estonian (et_ee)) Currently translated at 100.0% (17 of 17 strings) Translation: Kodi core/kodi main (Android strings) Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/kodi-main-android-strings/et_ee/ --- .../packaging/xbmc/res/values-et-ree/strings.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/android/packaging/xbmc/res/values-et-ree/strings.xml b/tools/android/packaging/xbmc/res/values-et-ree/strings.xml index e7675cbb7cb6a..47c32a48d8ab6 100644 --- a/tools/android/packaging/xbmc/res/values-et-ree/strings.xml +++ b/tools/android/packaging/xbmc/res/values-et-ree/strings.xml @@ -1,4 +1,18 @@ <?xml version="1.0" encoding="utf-8"?> <resources> <string name="suggestion_channel">Ettepanekud</string> + <string name="info_title">Info</string> + <string name="exit_button">Välju</string> + <string name="asking_permissions">Lubade küsimine…</string> + <string name="clearing_cache">Vahemälu tühjendamine…</string> + <string name="waiting_external">Välise salvestusruumi ootamine…</string> + <string name="first_run">Valmistumine esmakäivituseks. Palun oota…</string> + <string name="starting">%1$s käivitamine…</string> + <string name="no_find_apk">Paketti ei leita.</string> + <string name="no_read_apk">Paketti ei saa lugeda.</string> + <string name="error_title">Viga</string> + <string name="continue_button">Jätka</string> + <string name="permission_denied">Luba ei antud!! Sulgumine…</string> + <string name="notice_dialog">%1$s vajab toimimiseks juurdepääsu seadme meediale ja failidele. Luba see järgnevas aknas või %1$s sulgub.</string> + <string name="external_ok">Väline salvestusruum on korras…</string> </resources> \ No newline at end of file From 502598686f84796eb602adf6f36b95dc48b40d2d Mon Sep 17 00:00:00 2001 From: Jose Luis Marti <joseluis.marti@gmail.com> Date: Mon, 28 Oct 2024 18:40:14 +0100 Subject: [PATCH 636/651] Bump Android NDK 27c --- docs/README.Android.md | 14 +++++++------- tools/buildsteps/defaultenv | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/README.Android.md b/docs/README.Android.md index d6c7dab5f48a5..95b585d50c49b 100644 --- a/docs/README.Android.md +++ b/docs/README.Android.md @@ -73,8 +73,8 @@ sudo apt install autoconf bison build-essential curl default-jdk flex gawk git g **[back to top](#table-of-contents)** ## 3. Prerequisites -Building Kodi for Android requires Android NDK revision 26c. For the SDK just use the latest available. -Kodi CI/CD platforms currently use r26c for build testing and releases, so we recommend using r26c for the most tested build experience +Building Kodi for Android requires Android NDK revision 27c. For the SDK just use the latest available. +Kodi CI/CD platforms currently use r27c for build testing and releases, so we recommend using r27c for the most tested build experience * **[Android SDK](https://developer.android.com/studio/index.html)** (Look for `Get just the command line tools`) @@ -100,7 +100,7 @@ cd $HOME/android-tools/android-sdk-linux/cmdline-tools/bin ./sdkmanager --sdk_root=$(pwd)/../.. platform-tools ./sdkmanager --sdk_root=$(pwd)/../.. "platforms;android-35" ./sdkmanager --sdk_root=$(pwd)/../.. "build-tools;34.0.0" -./sdkmanager --sdk_root=$(pwd)/../.. "ndk;26.2.11394342" +./sdkmanager --sdk_root=$(pwd)/../.. "ndk;27.2.12479018" ``` ### 3.3. Create a key to sign debug APKs @@ -135,22 +135,22 @@ cd $HOME/kodi/tools/depends Configure build for aarch64: ``` -./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=aarch64-linux-android --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-sdk-linux/ndk/26.2.11394342 --prefix=$HOME/android-tools/xbmc-depends +./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=aarch64-linux-android --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-sdk-linux/ndk/27.2.12479018 --prefix=$HOME/android-tools/xbmc-depends ``` Or configure build for arm: ``` -./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=arm-linux-androideabi --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-sdk-linux/ndk/26.2.11394342 --prefix=$HOME/android-tools/xbmc-depends +./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=arm-linux-androideabi --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-sdk-linux/ndk/27.2.12479018 --prefix=$HOME/android-tools/xbmc-depends ``` Or configure build for x86: ``` -./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=i686-linux-android --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-sdk-linux/ndk/26.2.11394342 --prefix=$HOME/android-tools/xbmc-depends +./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=i686-linux-android --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-sdk-linux/ndk/27.2.12479018 --prefix=$HOME/android-tools/xbmc-depends ``` Or configure build for x86_64: ``` -./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=x86_64-linux-android --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-sdk-linux/ndk/26.2.11394342 --prefix=$HOME/android-tools/xbmc-depends +./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=x86_64-linux-android --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-sdk-linux/ndk/27.2.12479018 --prefix=$HOME/android-tools/xbmc-depends ``` > [!NOTE] diff --git a/tools/buildsteps/defaultenv b/tools/buildsteps/defaultenv index faa33802c4f87..89889c6cf8c24 100644 --- a/tools/buildsteps/defaultenv +++ b/tools/buildsteps/defaultenv @@ -56,7 +56,7 @@ case $XBMC_PLATFORM_DIR in ;; android) - DEFAULT_NDK_VERSION="26.2.11394342" # NDK package version (newer API can be inside) + DEFAULT_NDK_VERSION="27.2.12479018" # NDK package version (newer API can be inside) DEFAULT_NDK_API="24" # Nougat API level (24) defined in package ./sysroot/usr/include/android/api-level.h DEFAULT_XBMC_DEPENDS_ROOT=$WORKSPACE/tools/depends/xbmc-depends DEFAULT_CONFIGURATION="RelWithDebInfo" From 13def10be37ac7e369ec564b7f155309d7f728a9 Mon Sep 17 00:00:00 2001 From: sarbes <sarbes@kodi.tv> Date: Fri, 1 Nov 2024 18:48:11 +0100 Subject: [PATCH 637/651] Estuary: fix textures (mostly alpha issues) --- .../skin.estuary/media/DefaultIconError.png | Bin 1685 -> 1889 bytes .../skin.estuary/media/DefaultPVRProvider.png | Bin 11082 -> 4817 bytes .../media/DefaultPVRProviders.png | Bin 10456 -> 4561 bytes .../media/DefaultVideoVersions.png | Bin 3542 -> 2323 bytes addons/skin.estuary/media/OverlayLocked.png | Bin 373 -> 353 bytes addons/skin.estuary/media/OverlayRAR.png | Bin 291 -> 315 bytes addons/skin.estuary/media/OverlayZIP.png | Bin 291 -> 315 bytes .../media/buttons/button-nofo.png | Bin 412 -> 377 bytes .../media/buttons/color-button-box.png | Bin 557 -> 198 bytes .../media/buttons/dialogbutton-fo.png | Bin 248 -> 248 bytes .../media/buttons/dialogbutton-nofo.png | Bin 242 -> 259 bytes .../media/buttons/radio-button-off.png | Bin 820 -> 943 bytes .../media/buttons/radio-button-on.png | Bin 856 -> 937 bytes .../media/buttons/roundbutton-fo.png | Bin 856 -> 1109 bytes .../media/calibrate/cal_ratio.png | Bin 2890 -> 2817 bytes .../skin.estuary/media/calibrate/cal_sub.png | Bin 246 -> 262 bytes addons/skin.estuary/media/calibrate/reset.png | Bin 1339 -> 986 bytes addons/skin.estuary/media/colors/red.png | Bin 581 -> 122 bytes addons/skin.estuary/media/colors/red50.png | Bin 579 -> 120 bytes .../media/dialogs/dialog-bg-nobo.png | Bin 98 -> 68 bytes .../skin.estuary/media/dialogs/dialog-bg.png | Bin 412 -> 377 bytes .../media/dialogs/volume/mute.png | Bin 384 -> 477 bytes .../media/icons/addonstatus/manual-pinned.png | Bin 695 -> 678 bytes .../media/icons/addonstatus/manual.png | Bin 596 -> 576 bytes .../media/icons/infodialogs/extras.png | Bin 1344 -> 822 bytes .../media/icons/infodialogs/versions.png | Bin 548 -> 344 bytes .../media/icons/keyboard/accents.png | Bin 5852 -> 717 bytes .../media/icons/pvr/PVR-HasArchive.png | Bin 738 -> 531 bytes .../media/icons/pvr/PVR-HasTimer.png | Bin 292 -> 430 bytes .../media/icons/pvr/PVR-HasTimerConflict.png | Bin 1390 -> 1309 bytes .../media/icons/pvr/PVR-HasTimerDisabled.png | Bin 276 -> 351 bytes .../media/icons/pvr/PVR-HasTimerError.png | Bin 1270 -> 1344 bytes .../media/icons/pvr/PVR-HasTimerSchedule.png | Bin 317 -> 452 bytes .../pvr/PVR-HasTimerScheduleConflict.png | Bin 1608 -> 1618 bytes .../pvr/PVR-HasTimerScheduleDisabled.png | Bin 310 -> 380 bytes .../icons/pvr/PVR-HasTimerScheduleError.png | Bin 1525 -> 1693 bytes .../media/icons/pvr/PVR-IsRecording.png | Bin 576 -> 531 bytes .../skin.estuary/media/icons/pvr/premiere.png | Bin 756 -> 249 bytes addons/skin.estuary/media/lists/focus.png | Bin 187 -> 68 bytes .../media/lists/panel-mediamenu.png | Bin 2133 -> 106 bytes addons/skin.estuary/media/lists/panel.png | Bin 98 -> 68 bytes .../media/osd/fullscreen/buttons/record.png | Bin 707 -> 884 bytes addons/skin.estuary/media/overlays/extras.png | Bin 857 -> 531 bytes addons/skin.estuary/media/overlays/shadow.png | Bin 403 -> 363 bytes .../skin.estuary/media/overlays/versions.png | Bin 359 -> 236 bytes addons/skin.estuary/media/pointer_arrow.png | Bin 1993 -> 1554 bytes addons/skin.estuary/media/pointer_click.png | Bin 2577 -> 2226 bytes .../media/progress/texturebg_alt_white.png | Bin 151 -> 94 bytes .../media/progress/texturebg_white.png | Bin 90 -> 102 bytes .../media/windows/pvr/archive.png | Bin 417 -> 396 bytes .../skin.estuary/media/windows/pvr/record.png | Bin 260 -> 394 bytes .../themes/curial/overlays/shadow.png | Bin 1037 -> 1305 bytes 52 files changed, 0 insertions(+), 0 deletions(-) diff --git a/addons/skin.estuary/media/DefaultIconError.png b/addons/skin.estuary/media/DefaultIconError.png index 7f1bb07de32bbe6fb67ae26c1733567b6d0c152d..e43edaa0cc9a90d20c743294216762afbc88b99e 100644 GIT binary patch delta 1875 zcmV-Z2dwy&4dD)uBYy`dNkl<Zc-rlq+iz7x6o<dlKo!(LpoJ%EXh0MNtCXl*f{G$g zO%zn5785msB<P>u<=Ge&H5CYG)SxKfgDDY2iCiT>KtLlm8|o93HX&lsMtXdhb>gWu zrDyLoGkfndzoajxJ$u%yuX`?Q*39H~4;w~M`cx5I5WocmaDPDoTmTmozy$?x0bEc3 z7Zkt+1#sEYpcLAi1I7W9fLnoDU<^<Nj0A=N*UH~s1TFy`z<HomevSb@0w;ld8o}jK z5@a3=%mE$*rUE0Bp4$oR0k!~N1E&JG_^JUG0}FwPj><d=tOq^@+5))Jn!(w?i@;ou zeQ=Mwfh&NmNq=lkD-r~3K5$qZ*5KK;3Sgeta#(E31#rDJ4cHHClmSQ5G)ZjR4@?i> zpj!HpoxtQ2j0YwIJH+Pdas~Go@H5bqv?gc(O?s2q{zN%}8w#uezL0rWT2&>Z&$Uvg zOoOWhb_365D1h;d)TuVB;O+wU0(E3hb-)i&$4r510Dry%Mv+}rOC1}s0&W4Y2@^le zx`s$y7o;8BJYXGAN%^Q!>fDf4aCgecv7De4!~0x(kScIvfUkTFlM|w$;)B|>fUA`G zO*KK)m{8f!G=O^@m=c)vnkuYFI=DwL%P~Q{^&BR|mq>6`z-rPJcXX*{ltX-4;NB$N zu}9mg0)O608n_w2Q<f^+gNX%xhTT+xHYsDblxaha;;Te}%K>j#Y9cQHGck*oi!hm8 zYb=zrMxMP$%9<%<YfIHvalt)mX%u+@xF6V6^jSCXJg`!+oK^D7?xN3jN!d2wO%b1Y z1(yR}wg~M&AHT~3%aq`)0$!l+M4$r};k}~q)_=tTHygOkAv8^REBk?F8{Q=Goi}jH zJwVfdw^F`4kd!-H@Luu-ZWJc7*ax&Y@JfMZ6W$!)1|Q%W(c5>>13X+Jv?%bRKr4p# zkm6LK0+V#)9Ng22|9#srDSA;g5O{H*6^}NzE7p6VYj9(M+Z8KtPT?8-!P9_N&{@TL zPk)s8qcd>xlsoEaz+g-8G@%VfKa+W`OK=Y;8(#ywrxZ~rhNlTFCpOk7*FCZ?OWm!L z!)y>7fxfgIjpDPKfu$De+=&T5=WV}n<19f#9Mi1$<|Q2(;>$?=T&dv3V^^B+k`Apn zl42d)O%5EE1bD8XIRiJ|k>h;Aa|sPhR)3szaN`_0&<8x%&>&*yt5k5K9sl>cgy#cV zWS^D_Zls5%%LP1N(1<!pFBRNXo(2mx;rWCHaJ36?*LfW2*?{L88vJ_-l2vd)2~nxw ze)njrW=O0c%^a4fy66JjUm1dy6nKBS0N0T*Xi0$A=>pu@3_|k_@2m@OCo>AoCx5&% zF2EhnFf?EAT3vwqCF9V1z&o0SH!ca#T*Eu!s*meT0kj<YIV`qDlqXz(%VW+k*4Aoe zXwB%RK`av+wY4W|ouh=jzEL*54YPM#J4Zx2hh=%x5ugoUMgg~3+4x=PL}dC37;D1o z#_U+t)E1ZEP63A$jUR0ZnhkiG2Y<;Oi!<1Zx;pTYVg>FcUWV{MmS!~IO$Y8%toQmj zUycfH9q_kO1u8JdC)HbmrU9>BY}8r?>%pAu>>S*=IPG&E8ZI4my(MUI;MI$5!<GBC zGd{opyrbMvmjW-&%5^F5>Va*_&;Z`nXr`iSYt&i)72s{V0z8d$gQdXxSAT$4fTn#F z%2Dy1H*k62H4Br{Vg10<%n@1A4?J7Y0AAIYv&QXgzXT3igjYP;94yfb&BUH1^32Nr zj2>-4J0L!bKmSp<6ln(VJ$6#(dN5(3-!K~>Yp|m>nOt9wx#rA95>*~^YSGT5fCKmh zd*^v&MW130Js?TosxjA=gntwC`qp`10?@AYK&>XBUHXfBVAQ9UX+qNkR~V?g8yNjh z9{?M)AF6$0W2JQR^??^)F3_C{bZI|Wd-T+ext^;%FryuF(_@#7huawTwqlNW>MCPs zUE+gZZ53c^)5kt}^Sa9rTA|h1XR{ESyLDP*-qKxW&>E%gq!k>Pn}06m1DDDG+9j#$ zMi+&-*dNyd%*yzH<-F9f#Z`f>Ha6}B?#A4yoy8%4;YLcb1`gmfCQ!UKL(o2ydD>~$ z1-sr#+=aPo=5hS&wbN1u@FeDPTQAcdUwgne05@Y+719*n%!|zvh?%98R&a&L!eZba z>@3D=>mVkTa4|Zk+JB}NTtPc9H()G4KZiv_N5r-%m<8iRC@IHf<S{q;)?toz-wa&# zXwPM_VUF0eDQQi*_UeG9MqyUfn}9nUm3au0sJagK``t4Gu2|zShj`DD`JmP*wVg8m z`c~es<D^7s0oTiB;6}{7KDS_Q{vCr!n;40?Nc|f5+dnX&s7wxtZnR<|_CLwQ`2;Cl zwI7s1oAdH!9H)#G&hiT2f&#dp04{(F3gChQxBxCFfC~!Xf&#dV=^tFss<&ZXLNNdU N002ovPDHLkV1lO+Z*%|v delta 1669 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 P00000NkvXXu0mjf?UqJt diff --git a/addons/skin.estuary/media/DefaultPVRProvider.png b/addons/skin.estuary/media/DefaultPVRProvider.png index b5dcd1a8cc78a345f869b219f907cae402edb04e..f2f874781a188058dd7ecb220a7496ee19f6788f 100644 GIT binary patch literal 4817 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 literal 11082 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 K$W%(12LB&&Z8f6+ diff --git a/addons/skin.estuary/media/DefaultPVRProviders.png b/addons/skin.estuary/media/DefaultPVRProviders.png index b9f7008f9394eca47c39f7ccafa6ff626464537b..3eead52ca25fb6d076189b5046f0467c2708f594 100644 GIT binary patch literal 4561 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 literal 10456 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 azyKHybKk?Y$Ociq0Lt<ja#b>BLH`fCZ=S;d diff --git a/addons/skin.estuary/media/DefaultVideoVersions.png b/addons/skin.estuary/media/DefaultVideoVersions.png index 3c1354acc79a2e77c50f8a98d247818cc89c993b..0967bd0062025542e47d444d763cb37a576e4b87 100644 GIT binary patch literal 2323 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 Z6?Kc<M_B4Mw$<!_nB8&F4ZBEZ{tb>x9?}2+ literal 3542 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 diff --git a/addons/skin.estuary/media/OverlayLocked.png b/addons/skin.estuary/media/OverlayLocked.png index 271fe8132f72df692cb42e6278ca9e67094f692e..61fe5d50fa70cf8bf41cc5a0b23ff5c6fe419241 100644 GIT binary patch delta 326 zcmV-M0lEJ50^tIXB!4JLL_t(o!|j$aZo)tiMc+o0Nki?5G6fQ?CF-0YH3jlvxWbAX za01sBwC+--fg2=B$)~X_6M4x(RtS<AcmCsj@*mC2I&sZ-z;K2$+_iCoGxIZEm@;7m zyi-$AvOD8@#XnAk*7_c66>_m>#KVSKC=?3~)8bqU+iIzq#(x@Xrlqzm(51u`lBB)7 z@}u{;y!Mi$A{{PkYUX=Y-_$Tmg_}1v=Dlj(SUUgtq`1DD0#LH)RX3EKtORX70iX)% zRVOO}ZUjIb)ay=Gq9OM63$+awbyxY|5pXAm6MXXYzc)ghnX$J+XWRUSxA}36WM<wS zPM1~A%nUbz`#+O^=i*25Ww^KCJwKdvKW45{WyOtX54-};C(K^qQ*Cn_(S|de;cgUn Y0IFf9b}%KQOaK4?07*qoM6N<$f+~}m_5c6? delta 346 zcmV-g0j2)o0`&rrB!4_fL_t(o!|j+cZo)tiMgK%O0kx}?DJ;=iqRt6YQy?FPE3CKy zCva^+n=WM<as#B4ycAdjCtk9|D}>38JMU<o=Fg62WJ8y<JKZww-gjU<1M?5XOt?$j zOwWDG^rVT#dXV&{7uBll{j3Yy{d9O5B`dz$iV_Qr8;oPb9Dkq98AZim2iS%MQ*v^q zEZ7`?786$ju)|Tb-WND_0F=>iWKGRnXZ2lE!z>nV*2q|N;)+Jn+3&cpzMDvrDqDAc z%-5=pydt#uSdvtG&+8+v{(Z+5fZQ7+2VfgZOiB9aar&3+PDY8I-4G{&5wNQexE-3M zUFCYGdAmlC#8Vs^ZeKG<Vn)F28^P=rv-`^G{QSajuRlg<V}7pziMfpB750gClAa|! so)CK}7ipTOKMx6aJ$q&nGvV6y37;K^<Qs~Y;Q#;t07*qoM6N<$f~>})&;S4c diff --git a/addons/skin.estuary/media/OverlayRAR.png b/addons/skin.estuary/media/OverlayRAR.png index 312e23a0cd07f4d275280bae12d9a457e61fee68..982ac426d96aa0b8c0d35f191ae7d7093ff9b21c 100644 GIT binary patch delta 289 zcmV++0p9+j0=oi`BYyz`Nkl<ZSi|kt!HEJf6vpu{vJA{guz)#P!_;5_mN6ylQLqdP zunA#1@G2;t1W$tCN%(tPTr|mxG7G!B<T~&BL6SEl0sZC}$T{bnbIv*I_%MBC{f`IV zJLmoz*NN|WGG(3aDmqn>mWs|P7hV|`rm@TvCt8lh*pW7#n1ANPm`xG3VVug#abm>E zjD0amr{y_Ovt*y8_AIG$Sr$%+IlCmbV{XZ@a-!oJ#jbR=6gwv-oS$N6Cf1A+PP7~z zutVzLzsHG&^$%=KQ&L~biQ!#ita1nceNI#?*s^6oRazTfd``|JeU_I6f4%l~&+Kbu ngI|sMSXFNX=Ng=I?k{sUI#pwC!}v8q00000NkvXXu0mjfIPZs) delta 265 zcmV+k0rvj80;2+uBYyzuNkl<ZSi|j@%?X1*5Joiz!BZdwc(Mj-umH=j48bxiz$S!r z;8jpO37!POlkjqj3HfpT3u5M-SKqqMd<?6j{gRU=I??S>CkXpQS6PH&j_dk=x4GyJ zsKe)u)+oKJ8J@t$n79)>I_A*f&H8j4>dCSiXZBfRlTDIk*nf+DE*BDI*_F%UYQiL@ z995OXm@5@9A;ni&LAYwi;M0GU_4uo|C@$?=S&O?#Cg4!L$f^Z}KPt3HpJXZ4rm4sI zp)ryb3V&2+5RsD;P0q#H%78!yy*<sDY;-R$%%3tE3BvJHMJ^acr^XGE%C+SkDPODr P0000<MNUMnLIPldQ&@e| diff --git a/addons/skin.estuary/media/OverlayZIP.png b/addons/skin.estuary/media/OverlayZIP.png index 312e23a0cd07f4d275280bae12d9a457e61fee68..982ac426d96aa0b8c0d35f191ae7d7093ff9b21c 100644 GIT binary patch delta 289 zcmV++0p9+j0=oi`BYyz`Nkl<ZSi|kt!HEJf6vpu{vJA{guz)#P!_;5_mN6ylQLqdP zunA#1@G2;t1W$tCN%(tPTr|mxG7G!B<T~&BL6SEl0sZC}$T{bnbIv*I_%MBC{f`IV zJLmoz*NN|WGG(3aDmqn>mWs|P7hV|`rm@TvCt8lh*pW7#n1ANPm`xG3VVug#abm>E zjD0amr{y_Ovt*y8_AIG$Sr$%+IlCmbV{XZ@a-!oJ#jbR=6gwv-oS$N6Cf1A+PP7~z zutVzLzsHG&^$%=KQ&L~biQ!#ita1nceNI#?*s^6oRazTfd``|JeU_I6f4%l~&+Kbu ngI|sMSXFNX=Ng=I?k{sUI#pwC!}v8q00000NkvXXu0mjfIPZs) delta 265 zcmV+k0rvj80;2+uBYyzuNkl<ZSi|j@%?X1*5Joiz!BZdwc(Mj-umH=j48bxiz$S!r z;8jpO37!POlkjqj3HfpT3u5M-SKqqMd<?6j{gRU=I??S>CkXpQS6PH&j_dk=x4GyJ zsKe)u)+oKJ8J@t$n79)>I_A*f&H8j4>dCSiXZBfRlTDIk*nf+DE*BDI*_F%UYQiL@ z995OXm@5@9A;ni&LAYwi;M0GU_4uo|C@$?=S&O?#Cg4!L$f^Z}KPt3HpJXZ4rm4sI zp)ryb3V&2+5RsD;P0q#H%78!yy*<sDY;-R$%%3tE3BvJHMJ^acr^XGE%C+SkDPODr P0000<MNUMnLIPldQ&@e| diff --git a/addons/skin.estuary/media/buttons/button-nofo.png b/addons/skin.estuary/media/buttons/button-nofo.png index 7b7e1e4b3b8c47a1ee33cdcde578e37862165b8d..47f813659787b7b50b0ada1655375521ec5e76a1 100644 GIT binary patch delta 350 zcmV-k0iph!1Nj1wB!56jL_t(&-tCz|YQr!PMW1CUPE8GkEPAY-wa4lrP;iLtSeCm; z*Y%iAX(|7$2kF@}XfYpz5JCtcg!sc$#;S@yWv>646<8^(tUiJjftp19DfxZD!oti# z*;b4u!N`#Zjznq)!G+Aoz{u<)Skv(M^y)iX?=P$r%2r)4oPQNK(78{4$H8)YFts?* zx(l`>%R~oLixZ8zU_-J@bTAzjOtZR=XuI3i;>!3Tdk9osE&f#`C_xEIP=XSapadl- zK?zDwf)bRV1SKfJ|1G%M5{;c0PqqEQLUtF-+Z%qXU?DTO3l26#OLElEapIX1Ey+P` z=E|8j&h$)M#aEW)_`<}6#LUqtX&ULdFtKbGM^lS$TqyKJmu-J0bK}a*tHt*)F%lia w#7wq@iS{`59z}S?vE_#zFCl~wLI|;s4--L9Ouk1`g#Z8m07*qoM6N<$f&xUM*Z=?k delta 385 zcmey#G>3VDN`11Yi(^Q}y|=ec=N&Q-XnlC!&qqTmNL8$Mja|0<YVIXo3VDTZ^j@U8 zvl(s)kj?M>uv{|R!H#q4?KnY3R*?V)5OJWIZQF8tzWoj>{_kqK-^%0C6zr)Tpc%4? z-#{SK!({%6(~Yf-^BRr(wlGF7voTn@w2*->r7Y*>#or9|(pGZw<7&@e`^`Po>7#`4 zM9$*xm+jM&ZraX@nloc3bGpju&c0=0>Q?0m?wjUh+?MG%R#<Ycp?llfwZRwuyPH*P zji?u6fw}|i7&!4E>p+&b$+?(g`CCIbOt{-v`KRqZmrlyIcL&z?O8A9aGL)Q&{7`kK zN%CalwatkqrM<)696#i8`st4Y^GrMEtqi`jCHd$q+rvK&uU&bR{hCPF7QO9%XP#-~ zWnV8QfB49>Ylc2&o~$?67{_D7CU{)qkanN0eeyBpimRp1JAa>LZoZc&3Jo3mSb2t; V=x!6XP1_lOz|+;wWt~$(69D1xruYB= diff --git a/addons/skin.estuary/media/buttons/color-button-box.png b/addons/skin.estuary/media/buttons/color-button-box.png index 11d89b320d4dec9b04ec68a4d4dd5aa6fddd045a..a7a3b1af7c98a9b89ae4d15ea23af33d4a9a355d 100644 GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^(}38Ag9S*cu5D8QQoWuojv*Dd-riZr#h}3B8aV&Q z|NU$3nn<ucytA+9M%TeN3$uh7bs{z-9BgW35Cb!u7`VX<#TDD5F5BBZIG$$d3{=1g zkrjd{Rt0Ne^#*BV<(>v6#dKni?&mXu>TX~KGXxF*<$+o`5^P^G#CtMNkF_$J19TXJ Mr>mdKI;Vst0Fg~Y1^@s6 literal 557 zcmeAS@N?(olHy`uVBq!ia0vp^(}38AgAGXbFY#N=z`*F884^(v;p=0SoS&<gn3A8A zs#lR)0F-B7u(7WwNKDR7Em25HP0!4;ReHaBzmh^`img((sjq==fpcm`rbks#YH*cb zNODznvSo^ry&acLg%!|%+|-gpg^Jvqyke^gTP3i$RzNmLSYJs2tfVB{R>=`$p+baj zfP!;=QL2Keo|$g4p^1@#xuu?=nSrH|iH?GifuXs+fw{h+v95ukm4TU+v84hODA{o- z*c7FtSp~VcK`jIFY?U%fN(!v>^~=l4^~#O)@{7{-4J|D#^$m>ljf`}GDs+o0^GXsc zbn}XpK}JB#a7isrF3Kz@$;{7F0GXMXlwVq6tE8k4vP2(h3($M|aQ^{0@DNJP0|rYG z(Esyf65a#DZmy?`V@SoEx0ep`9xxC%ykW`j_ce2!JT6Y){*kzr6&$<};{FzY-t%YX x+}d~Y-tQB~&vyp?^N&zzado0Dfox;UQ`XhSEFO6Vd$xg`;OXk;vd$@?2>?JdtJMGi diff --git a/addons/skin.estuary/media/buttons/dialogbutton-fo.png b/addons/skin.estuary/media/buttons/dialogbutton-fo.png index 088365fe49339996afedcabe48e1d3011ec46e50..e5bb91567c0d408824ec86ba5c2cf81467821dd8 100644 GIT binary patch delta 212 zcmeyt_=9nRPW_FIyoU@#TrWyzbwnsN2_9;?sV2#A(b->kn}Uk%nuICg4JB`{U9zwG zwe3m#-Jfr_IxcW%WMbtK(Fg!B7&BhXtXO|X*w(l5;;J8)OCB7v6FEJ3n!D9Oy<^R` zv33VLt!_kd?=ER7-SU0AoiRcq%miO1)_;N@MAz<q(l}k?Lrm3%8kU+TQZkF?OI+sc s+ro2Zb)hkHuIBsMEPIb4Yd*7{zuU%2mGk%3JO&`}boFyt=akR{0J3OT*Z=?k delta 212 zcmeyt_=9nRPW{cZypE0{Z4b-2b}ZuDDHhC7Eww;0upy(3?MCo{SOMQ3w>NK{a>ldg z$zQ1%>cvH7_dWGr`3Y$REO2OKV&xJ6F&K6+W~+VSir=$2eWL!u1M8l8*uRKg*Yn9z zYDc1&#m?M$?#%f|5C1)5Y<}^;gWOY|NLm|xVTLr;Gq2fqV9h=zxgrjoi^qS8@U*N; tJvwEX!1sjXpKkZv_nwTfu#u^Lwz)9tI>wW?a{e&@fv2mV%Q~loCIAGKSq}gJ diff --git a/addons/skin.estuary/media/buttons/dialogbutton-nofo.png b/addons/skin.estuary/media/buttons/dialogbutton-nofo.png index eab3633a430ad2487d83aa5f4b720f7b1a71c162..7d3fb1338c570c563982a8a706aec19d5f9697e6 100644 GIT binary patch delta 231 zcmeyw*vvFRrT&zsi(^Q}y|*_v@*Xk}XniQ&;URUYh4YX`qR6o%Rx^cag@q|h855n4 zGKr)=6>X@yu6|ejch1Rj*TwJSv>91A1QZ+^7#NvYI6m|@rn=><Zng^LHa<72<f5`v zT#LQ0wQT6@ZK^Mq?Ec+RE$zSR>hJ6go2Om7$G>LH0p9aG4h;!ly>LT7411P2Yb)dH z9;C4L)je2{@>rGk;)T*3n}1&kjW&&J?moY?=(6Rl$LA%T3%Wt}!t}m~&ll?w)oHrE RnVA6yJYD@<);T3K0RS-wT66#a delta 214 zcmZo>`ouUvrGBTUi(^Q}y|*_v3OYK9v_Aarn!>c6X{OZ+N9I`*_Iij)U0^Of5%Ph> zNM*?<TkC6Q>ZNYvznklO?(MxTbNv=LG%~SriD(1>8DASWbNSzYe84hW%!e!1f8CrN zPaAEcmo0o~YhkdgQF_|Jzi}4J8h1Z>TKD{@9k0xx`V;}6K7S+=AVxj7&oEoxVD|ck x+4>h3ri)rR&ymc&<9Vo)sa_mm858TYC(MGUH!v!(7w9nnfv2mV%Q~loCIF+yS4sc? diff --git a/addons/skin.estuary/media/buttons/radio-button-off.png b/addons/skin.estuary/media/buttons/radio-button-off.png index 956830ac4751482be45d4d48357a056c2764e9c9..b039ddfb55c11ad86557624eed5a770fbc56319f 100644 GIT binary patch delta 922 zcmdnOww`^0ay@gIr;B4q#jUrqeLX?~MUK}`HoZEd@9L7tm&7^S1b8fXRJR(iO=`Nu z!n}=_Mcj=wS1BV_g(Y{<hET^XT0%Sr#kO{FvN-;lu)t%vhq0jh+)bAc-Tok>b^Q0d zJ)P$&yN~bQWBLF4<2k=e^@Iif=|{IRX>UMA^+=o#`%_oje%sy|#wVzJA}(9L`H94W z`EmQ!Fl?+SKl)^1MRvc6T17Rhjn-wionEt*-PTK_&RLoIdu4+0RuhX)kM*Aah{zK3 z^Eh@S#cADX!IecX*L(GyD4l6wXFTbH-k~@POSQ<)nRhQeO8m9eWBbS1Q<Il6->FY2 zc>d(fqHjf=C&E@v>sV9z@Ndr<$tTM$y*M0r=?Y`yxgurzt5-L)n)Yo^I~mcx<86r` zlUqmi#p$Q@cK-gG`;D<+V)nA7b8JHb!wi1rs+>`=*Aa;`@E2N<qW=9Z^X~2sjPsa2 zv^-98;oCg>fc(94A7@!|Pvw%kz;wWI-p}2acGV}mGv|3%RrXg<yy=R#%RY<Qr^=^s z|8v{XS3ZTY+%hvt>c)m6`F0-<dcEe$Jz!%k@lpSfyYC*k+0Q>r+7aM;!7JNwanItq zWnb!w{hwdI=$ayusJ*tNahd)BUIre6IsFBOY3w#<8k{eyKd`#2U95JuOM1e!$jqld zo?Q6CX^~@BpIRyHdz&}&oNHYLi%q85Y*y{EK*Ri%VkH^BSpG;hNPX74-{mrM^O3cM zKmi*Lhn`=qg7K%+)@*szX=zf+RG0VeW9ovA)SzSBCbf)p8Iyi&sQh=TMDVUc`qI)H zU;ek(G4)?ycjyvc_EdJ0%YWIeK~|1j4R+hPy|>S9*G^-r&r@ePz?1*K=}&LsYC|iR z-7_Dr-*Mk&2lKn-><uciKeyB!{(gU-^<nR{wINm>Oc$K@-~XMsLfKd3MA5A1{asux zH#cS6ovM7;qAY0n4mXY;9(QV;ssp$B%$Rj;ONmufxaU%rl-XCjTu#MSyq}U8eA#P? z-&Lcj>!a>XN#kEV?QBuKar{=FsX;TZMNXHStmo-<V(I<~DoeeOg)P#1d-deuGQHU= zlM_!{9MMP?E6$t1vDIW&sptXQbD!njpE)ezv*y6fphY3)r=1A5jmlg)@vBMHZ;7Nf s#j5aUUkn!fuM9|F4a1GTU9Mwl71n>0x!7dC8v_t{y85}Sb4q9e0Q1(d2LJ#7 delta 798 zcmV+(1L6Fy2ebx|BYyw{XF*Lt006O%3;baP0008(Nkl<Zc-rmRze^lJ6u|L`q9!LM zBA1}KC<H<xmuL<bt`Hl=%EC%Gu&@%vMi7MS{09U<uv1nL4utDOP!KGw7UTr~070-3 zv^hZ#zA5hAM#UfJ-0H6QcABf+o6pY7&FmY4d0WjWSiuTbuz!LTtY8HzSiuTbu!0q= zU<E5!!EYSg!YC_v9AN((;IYCeEl~%j*d*9zlT=i}t>kWuQJzKSn0a!WW0Cxg`H`bF zdf+rC(!Dd57^1n#c+Cv4#F@lAAssF7Fc;D_mwpVU{1i;V|7{VsA6MdDFdQAQBiu`7 zJmVQ-jB$-?Xn!P58()ai;2PH!J;SAtooIktIR>D}WQpq<YG|Vy=UV$sOKG$%GE7ng z;5eAITy227OuDSkZM=mow@&IT$yv#f4<FnoUD>5cxS!~`lk4EnMS~T&iCxLl7dCiH zI+CHp{XptoZfR=QT9Gr7XDeKAf-Bi6jevhW$aQe|W`8-YOS}9Pi7>$v^4`ZNPiN=R z)<eA&c}#Lmgb7}iP9z2Vy#lbqXDe`0a;=65-UDEtdI6^@0{^fA*Rv16UYOt`0Jbrh zf}yq|uEU>#DcEG&2H+@6@Hqe*0&b`foU#OO0B|0i5w5Ec-1T}#*t1&0KdV~9p8iX} zAb9H5K7ZW&0{ie(v`$)`kAmA@^GU0GmcC-dKTG#Kyd_zlrAOq|hJ2Pb49G;=A?B5* z_5I{--dk~F>`0!0@V|%4b;?=)d$@_ecXE@KtJg5=*Dxbc07yvruE@Agt|f76i|QiI zJ4^1kR6I_qU%$cBw9-=3Dl+}QrbP`OiPO$k;(s(&QPY|h?nO|Q%OHYe{Ho<Ly#ZD& zds+6YmYJy5rB7L4kXV)RVhpmtX;kad215ty*KP~;$uUpX;^raCJUKZ=mY#JwqI$QK zeulfHbdcERx;<RO<FE?mAs%b5;r383#1*Vy1uIy=3RbX!6|7(dD_Fq_R<ME<tY8Hz c_!r6TA00%SiBb0L4FCWD07*qoM6N<$f<v!=5C8xG diff --git a/addons/skin.estuary/media/buttons/radio-button-on.png b/addons/skin.estuary/media/buttons/radio-button-on.png index 450867d873f41674f7795a1df5e31dcde2b61988..b1e2c366bddecad286dd1e121aa97a6cd0b26c69 100644 GIT binary patch delta 915 zcmcb?wvv5<N_~N+i(^Q|t+%uNJwyT}4%ANzdo=%)+1ch)7DoXmiHDji{5&OZ9P|#5 zZh4?6aws^Uy@}6BXrA`T?)F0(JwjH}8ZGLghk`mv&IUwpnKN_!;eE$UbLP&KoXNWX zJ750ux4-wj|NHFsv$MC~Z>&6YP3v|*0&5sDsz>77yLG_*MN*H7cgOo(E|Uy*S!~ig zA{3W9DU)-%kN0GOytCYzzR`!hSKVEmn)dUY40q&f&80oHM{Yd*R`X0qdFQi-J8m3P z@9Q}sYbg?NUva0h@*|14+m0OmTp;1~Zl|J!Uan$W{Oq3Hv)(`D`yg5#RLi*d$;^75 z6Wo<r%P&{`j(Hni#Js~z$9SRKeXCO`2WnM<QX_ZW$*=aAuWGuJ!6tRtW%&b}ukF(Q z(!_luziQbc_B_MjnUhcDq|Oc5`}Rcr>#pK+juX6Q8ka>aV)L8PS(2uoot$ibt4~+* z)uvcgj!T(uR2H$WGrc_HgK}<S;@Rj;j(-!D)*thih~Dbh*qoyq?fd)O@;|X&<}$@y zQPy|evZqwPw7R_S+s=nOU!7Fnu5#BcHsFCm^l4VF2GvtPXYT#LW$~m^!ot~=QN-9; zal;EmEu(3F7&f&ssQi7<6jFJp)t2Fpis!;?W2Fag8E)8p|KM)Rr(>{oZkn-D!t9#= z!39F|*0$6aR3B#i=W7?>vT^=`X@W<B1N4~!d?s#`e5m#Js?Evtnst}rZPs4OXMNC9 zS+(Dd(<bE>w_RhkEn~UIuHJ<j@!ngH{YmFb*Wa__SJLSayP`!b35T8p`x(j1ZEv5+ zx?mRH{F&3Tr7Ox^rMwudc;4j%+*uHLntfTX@MqQkO}}S~)fY+4U`jgjf1`My2eVIb z-nQxDn}z56|NT&n|N7py-g8n5%nxXPW;q`qSR1!X?zm3OUAxKc{Y#?QT(7lkQnlUW z9<<{w+x3NZJxfwQnB>kmp4SlfP48Rs+f+$&i*tsPd_K)n-(Y_A{~R~tM^^qts=l{h zE=zS=YI$T$>gJwZv#g8jHGOxR>D%hO72;l@ulCkxk7eb{4SsU3UOY{>)nx436e%x# zQ1-|Tf7e^<7kSQ-ICFOM(jRJbbX#BCF0kEGRdwT7CEtd*bN7}0IrL0Azv9I55B&U} kzu5D0tvP@P1x~Y{ps~noGBckF4+9W*y85}Sb4q9e02!gL3;+NC delta 833 zcmV-H1HSyJ2iOLXB!3@CL_t(|+U?n2M5Iv|$MJ_Hij=N~A(mJoBIY8oO3P}AC8CRN zHj{2z5nV*=)w0=abXSTPV(t<}wwtA^AtIJ#iHKG6zEjO;YMSp}%$ZTEKeK0A@5w&r zcQ+UBIiL4E=Y7vSN09$3qF@CpSiuTbu!0q=U<E5!!3tKef`1jPU<Lm_;M*Dv46s0g zUF_Q~2^JWj;np790n^GFkK5L0brej9*o|itXO=0({yJ0462CD&VuYLn6XDPd&tr}r z>ZPJaJw43fnQI;raSBW?88fs+I_1$Coh+K`X6SVWOq=0m8M9&)6CuhZ?|H}bYrTw- zHRLuYz=SveAb-uU<#QCOnJg`dLfd1Y)Xy+!01gUkIcRX)3~|DG;zrCZ`?GBmD)hpH z`6V7Wm<}_PEo$w!`PcbOo;$LiZ_0}ZwQQMwI)VnTn}PJ(aj#mg{44Is-c_B52>qs? z^+3Ud$rZkow$nNHEyn}-WF~&$E1x4ABsgmB`=BNF&3}Vja@ky1M1(=pS2RfQk{Lv^ z9r!YKNf{Xt5uw@iwG<?H8-N|E?7$bt7nG4T5fQ4`G4JjG!TSJg*m1LUS<1?Hvx+wW z*bfry1F&ibK5sgwtQ;HgDggeIeZrYghO+Y0ohO{AYz?Q6)0B~~H`Z|C3BRS!VrP^Q zhu+d7m4AJQPv4&MV6vw-zr!O>dZ%@=eZm9z;?Pd3w3p7&X9rHTq%tY)$&XZFFWqNu z?RwCE9=gm#+oZ;l+?&eY$vsTHNQnqFY?^+$g8x5UtW%ELPHX?A|I6o=?D&no_#(o1 zv4-hF0U&H@6={a-z@B(@-bc^(xR!k{>OJ$s6Ms(_G8K<Q!fw>GBBrL5X4D3pETlU5 zDj7|FPJT#6laXZcS7Fp}GsViK3suYhQq^*v_D8H*wliO<TK2nImp)>KZrfK_>3TJE zGsBUib!ib18d<qkfZHL)G!qsl4--riGuy~QTcOd_v!%#CI9rN1e3(``JzOTiUIolO z5;`n9czUP@;tE!<f)%V_1uIy=3RbX!6|7(dD_Fq_R<ME<TzP&2PWMY}<a-|%00000 LNkvXXu0mjfp>U5p diff --git a/addons/skin.estuary/media/buttons/roundbutton-fo.png b/addons/skin.estuary/media/buttons/roundbutton-fo.png index 8443c1d498b37695ac0194ea6c322f0f0022ec60..a191f21928ec1eb4393dd202acb98326718cff67 100644 GIT binary patch delta 1089 zcmV-H1it&&2Gt0VBYy-ONkl<Zc-rmU&udgy6u|LshEjDVW?@W<spN-=nk;IQb|E4{ zoiJux1WLQHl0_sgBi&W|Cul*D+Jaz22smA}g$U6Um4c)cN^M6lrj0mJ0&R>E?2K(( zSa(8K&VBFP`_44ytmoal&pUTA_nvp}t-HnQiWCMmuz?M1V1EM}*uVxhuz?M1U;`W2 zzy_}IcrDQ1*+?JT>1I1`(ny+mo>69*GK<V|hgoj26yiqeJQwlO^pj;TJE(ibC;z3$ zb*^!hr?m-Akz<%aGLF|(nBW56@w}G6^&IA7+GDg(;xy+d*8+GQqkO_fftHr|jL-R7 zH~0X@X%%emK7U8~O6$*38<P}7xzI|1%d}|(4^brV<^Rk8MTT^N*D}U94c>lB8W`t1 zYqflboB5Vq0mckJ@IH&lXSjpuAY9nRG#yEUd$>Vouz_$VH|R+c+{F|vLC5|rOwpAT zxQR(N2cPb2W|F2Pz^^kA?^(y_R}-uW5B`#!iA`#E@_$8W@DL-3&f`a-4n?B<4!2SC zILxRnp0I=YP#Hd(I2Rf?8>&kmU;qs<z(*mvbeb9A8Bv_cV~=Y=$UVcOYPitKsP7Ed zb5Eq2Ar+5kcPj+BX84dQE;Mq;XNFVE#h<STM~P0C3a49!bLzOz&OU!#dKir{?5j(s zS#&O|hku9)Z&2}(;e9H(kRj)%OXtxVc|YK+T5#3}xS6eL!CPsT2fu^ncvl|Ws~Y^a zJh)3WxJw?~p&GnJ9^9fDyh$FsK{a@TJb0aIa9SR`UNyL09{h~|MN|*|OSPlC+Q3g& z8~Cw2_@Qd>AM)UPs=;^V!E>s?bMoL{RD+B1;D4W0gMX3-FDmZTzgNG0%cZ7uO)c2r z&RoEgYQdMQ|ENnLKEt1@s}Nt|lFIyEs-ElmZs~$bu+v?+zAn8RYbS$`m}iT>*QL)n zsV={#-1fh?FL@}@sEF%*$wQeFs`7inb*qmpTdJbAa7;~pKXu=>7iA@0fh#JwQ(Sbv zD1X}G(sq7Tu(-50N>L)(;?k1)3OMl?jaGw+x?Xymu|&D&8IN{h^nFWf_&%{U@jvn& z|3teq`WAo{1`{d?%rO|18*f+3L-w-}oV&pOI3?p$%x9hBK~U}ka`F0A+?{DN?B;eb z?rnB66R(Msz%$>&bP(<|d*b&eiS4wH$$xUjJNFFP>fa#w1;YwQ_<&_^*kwNC2rCjz z$?XvThF(u&>?`!~wNzu0X8_ET=OFimCx8dZQ<7^?`YfIT-5eD<WM+wDbW@PMK|cqV ztY<{v;F2*PFX8QI8sSLOPd|HktBS*iZgHKfwR`x`f1`;$w!J)FDnkaSP~i~^%sO(1 z-?$lOQAM4d5pQ4v8`!`GHn4#WY+wT$*uVxhuz?M1;2Mh;MXBTU>sL%f00000NkvXX Hu0mjfvv?Wp delta 834 zcmV-I1HJs!2-pUYBYy)QNkl<Zc-rmU%}QHQ5Ww+E7FDBeMBRxBZhR0wS`chkrsx|e zb<@<hP|$^i*sZ=nQ>5+6;!^uZ1+`86S_CbUnvEN$n*^j?n8b6=op^_Nf%_xj-ZOJ% zCXy&w5gFLP1~#yP4QyZo8`!`GHn4#WY+wT$*uXUf-Hb5H3V)mIQ{)EWmf!qfmrYif zWrXf00Vd4^i|lZRJP>#6u*d}ICIypXjCINnLjR32>x_|VDli?)Q>dGIKMKs#(G+0X zc+2IZu+_LEOPg{qQ=B-#R^x;zwP5=B=oDLxJpBs6OjGj06@(Jgs=&0e<_%ko540)) z)5|ySTtWCouYUqCgB%Cp3c@jiF@qWAA{bW?E*Opx%n)Zmxq@)UP)uNYI10`cgd=)l z0P~FPSh#|)O-p!iE;g<p<N|}4j+HA&qeqcO50RzV!KKCyoQ;9=v2z6>9|+8p3a%hz z0)R<7P5>X5Jf{7Fvue14ko63v!%;QERb16u2&xx6r+<no2y-66r1+^0T&S<YSs!>z z9aj*>{ejn2f;T*YNjFed4-sYB{eUM`as^@14|r8Ac-0U1P%Ze-1DIa5TtRBDXVwlL zR}Fsd9=xC$yx<<3Qw`3!2Y*%#{^A~dpc;JO9$ZunF1iQ*Q4PLv4;J0w9(<cL@Kw_O z(uJhGw12)Q?X#r&;PKUtmX<s`t@QAP6#yKRc#hO^5B=1%-Y2bym$))*d%_Cw60wn} zs}{ANCXbhCDTOJ-FOS!yDlf#}_-=X7l^6SNc}QGSO9I=LBqE~*{5n~!ho8drS`Mga zE*%U8mWY|y!86Hf5MK-55#JK)2taFaaN>qMDt}`<=fSz>v5oOm2DMIua!+F&)RIUd z`q&G`-J?&#uw@rpLAYCVX&I+&Vc9!(x$)sFKLgV*x%P&==9SKoct2<CJOg@{9%P&^ zJ;=CyLLX}_vd-38yi>o{qJr*pvbdzvR?{@e@}VVmYg#_E%Tg0BANnuR7`?Y%s$BUk zi#zOB)=P~(CLvG=))NLcuz?M1U;`W2zy>z3femb60~^@D25$QJ3-Rb7fL(%ZlmGw# M07*qoM6N<$f(U$s^Z)<= diff --git a/addons/skin.estuary/media/calibrate/cal_ratio.png b/addons/skin.estuary/media/calibrate/cal_ratio.png index 51938f7d1ce81e11d35ddf4f1fd4035b4f1bf8e4..a8ee5a32c833b1968af1533aaddfff530aee9233 100644 GIT binary patch delta 2199 zcmV;I2x#}p7J(L!BU=i{Nkl<Zc-rmV>2uZA8NlK91~*9|32RD7r|FcL&NO&ZmMmWT z&$oCb3!zC`X4;0yf-}W7tDzLv?gtyNV_#d+m2{<}=gAkxz?FUEALrG@*6nQukh)ha z0g=HKlj;Eu4S__BWc+>ZI0enrv!jzc0VEa>Iv{kQ-9}1(#GXZkft7qaRy#Tv-IJjS zG6J^*lVJfek&Y3QOaT*<8UhcKD*+Rc&<2z00UaLDZ6lVTuT4wPG7sXoMX;ljx?`3= z=%nw6B@j9Y9g`dZRDalM#1eGOTCBu+9F4Qlvlv1rP2a@m_FG@a1<PS{Cq;8FGLIOI zSqma`(lZ;AyZ(PFuEwTi5jtsQKDFn4e~Q`Iurxv^6*K!}9uYAea~4PFq;hRO-S^j~ zU8@V7G%}w&@Ykne#sDvL($0KqSD*K^oX|--^Q~RowYtzrJb&}8)MxGLLMQFaw^BF2 z3!Rpk&zX5d#AM7{IHA*~%qI%{xk=aRLZ>}L`$XYCI~j8Zc%jpL3k8Pu-Kj5FM4{94 zp?!Sd$Cz-fE_9k~ae$$Hcj^Xsq0>;G##m{OH5rQ*ROmF%XYp~Vk2mgGUFbB;LVQ&E zV~)F47dlN+#(z_A`(LXY;FHkH_>4>TCuY*Qw`hBwjQT#_tMJjURWK`5_VwJsufCqp zyApcUYyQtTZzY6osfX)nyk7Y+*5ioK+Z?)U^!9sN!!aIM=5`ah$)j64rO=_T6P7^e zr0$j_5IU)!!|U-|i|mVpVXOA~`LnoaiG@zuK8-6DA%6v4HkpHVbD7)GkvT%g(2me) zc;+8k1fe5ygpSM+Ix<J-4AHe5ng1tx+yDJ!;e^g$mg8BCnP<P3%xe7d#oOoNOD9U` z6!&?&9k0fEEyr5Sk@<3*-u3^N;&UfU=oE8(dysxuOXk^c7JVn)-t)c>;|nKD=oHEP z^7dQL8h@4fO8jcy_g}HA3!S{ae35xHEb}vcf6cBgbaJ^7ml~P*@&TD|?dnn&I+-)S z*vQOR_PI9SoBDMpQ0N@)tA=L2ddP16+-1AE&^gRE@j-3JTFAAVpU(VyulRR$p>uHN zqcvv!Nsi3V<a+E&QWrY?{H?~!7jtBOH)rNsseijx7drd+dt9jPSo68A%}*6N#$~&@ z(D}KW@qUe&k5zJQZh#j$hh+YTMrK}QXqURs+1EcBoB64Mk0*7Zvz__xYdh9lj?7m} z$Xx0|r}oU}V?4)he!8^dN?qt3_ic@t&*s?8@0GNhOI_&1tvFv}<`X$~^OL0?|5Ds= z!heO{&CGw-(9BO&@-bYrs|&q*zN<0wxg424s3`NTUHz|4ywJPk`x;+!d?v@W`O`RF z>BsWN_}0l6dUuBQ_i7y4Cv&`l`s<Z{Jm+K07l6>ap}wJgCdbeou~Ey%wBidu=<H{+ zaOUH=e*b@K`E1sm(E5#7f@DLViHRITdw)d4YqfkfN1aeRdN<su;TTtQU7K&|#lLF! z$j<r#5PCP9iTgEV-ppqY?&tpN2cfstTk)X6NB>KK-Tb*~pOCv7Z#nrw?~*qw+tshe z^Z>hNukcfUhg*#kPQ1{&x2xZ)Xg42JHL2gJ;5+CMdJ7yceOLeU07HAVsjm)ruYVeZ z-tk@i7dv+I>C(>VM#-7m&4u3aU478hU)zy+dCzE<d97|}7dj=Rz8n!>lyGfc%>ch_ zXdgb`8P}B52YEjFbY;(!Zw$N@tW@}8uh`LBm-^k>&eF4ZocS+?K?nF{nG2nrP8^te zS)N8OUX5e%N{*ikFP{3H5^pex(0`?#(Zd}-Z+<=&a}4mshIZHHLTBI9S1Ww<M?d@a z)3KPzxLX$);0^6Ur>~QhrM?z#?s?yI%x2!L^QCTR7dnTeUc~_aFy7er{nIgD!2oY) z7djaR_?6N#@BcGnQ!$(IQF<9uH?#|#Oiq<Lz^@(hqoGqVTjI64p<U<<;eT4a-_X80 z^|{Q~;2VczZfF-e1ye76mL6ri(Zf{cYxRQ$c;D<QbPA;Y{93)}?G*QWroErZ%)5HO z0p8FqbP8UpuSCSnf}yWv7}|HIK0m+!|8Ki_ll3y5+3f#{n$E5NHe3DCKgzM2zciQm zPVk$%Hi>%`zm$`&z5zdHp?{|tdfwgqr97{@dOyC4C-H8aiz7|hxP&hB?u7n0jt%gq z=VGk2XHn>`(K};ppv)sCa{RapEg|&uGJj*>#~6<V3n_G(>q!ZjOI_$Rd^a!e1q`Jw zbQ<Y#NxS*W)Lp9!oyHCA$4Y<9F{ulkREKt{3!P33?YmRIW`Wz!H-F5O@i`CI|0=&b z|JQM$Enm+a{Oa=!GaYfW@*|AKnq@Y+Gu-`rw1%VHvd}_jm^NxT$`cDMbcU&(SI=`x z386Doy;!Q@D8I4LLT9+%i5nFkVb!bd3Z3EmpwfTqUW${JSm-ptN0p`iQ@mq&g-&x) zU#ukavzAuqG|9)Mr+>aAbD`73)XN;;ZyVZ$PE!Z?vThf#6z43b&}phqN=bdmZZ33E zPu<Wibdpc~wxM0<v?cX<nIrV{jTK7$N0|$q)KkA5?^-sY)4tT_4DAR#edC#Tbwj(* zNq$%V(a<h*+P<sL%3SEAZ{onze~`J*Nj>!+;@6f(=%kbSj5^GPPWmSIJOeND&hU~< zWqi)<aU(9icss?r$9PF5p$k1CBGzIh*5hcLP3TMsUFbsZ)`Ti_*XYn$lRN<<lNtgK z1qdC4ZV8hK0w4|u9fS@fYQz%s)w2ZH(UT1V9Fr>n6Oqsclj;FKlPduuk&G3So(doi z2pxnDq+q1<N8GfiU`LY?0vH(x9fWQPK<I$b0igpz2ZRm?9muNZUXEZ#k$)l@K<GeQ Z{U7C+qZ$)F88`p{002ovPDHLkV1kE2efIzW delta 2257 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 W?_YE2#nH=az!R_^>tB!gE$4r?29i<$ diff --git a/addons/skin.estuary/media/calibrate/cal_sub.png b/addons/skin.estuary/media/calibrate/cal_sub.png index 89c3b1d91c03255195f5aea7f7afb5c3f9ca9de8..c39c00916a0867e4284c17fde86116ddb661e4f0 100644 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0y~yV5|YMqc~WAq+pKfJs@?~)5S5Q;?~={8wD90SQs47 ze7|orQOa&Zbkoj>94YK~g54TEIQ~$m5wPQsZ{a`0{;(Mv7q<*t-{Go<7j@0hP5CpG oZ~F~`#tYctvKVeW$XRpDUa2u}>Uek88|X0xPgg&ebxsLQ0LrgqcmMzZ literal 246 zcmeAS@N?(olHy`uVBq!ia0y~yV5|YMqd3@r<nGkBp+IV{r;B4q#hkZS90eH^cvuV% z?0>$9$*Q2UsrIb|P?}6|AU`cMw48<E`Pz334DSqT8U)(-1rB{+bds=h;5f|Ak@$l_ lkw;#k<smbf7J#(bWHG<l%p%bAGJP6|>*?y}vd$@?2>{NtC?Eg; diff --git a/addons/skin.estuary/media/calibrate/reset.png b/addons/skin.estuary/media/calibrate/reset.png index 9f5ca01d213e33e4fedcac6e4a863f4d763afe39..0f235ee077cd478a4e03a60fb805c03368873759 100644 GIT binary patch delta 975 zcmV;=12Fu%3fc#d8Gi%-007$SUEcrz1EEPoK~#9!?VH<66mb;Czv~4rxmH+Nc0(-* z=}JDBrcb44y;OowNjDLD$e@2fC<~$>@+Ip{PzsEmg0Nl+LQ*7?!Z6V^x~W-|r6K9J z2bbLDH>0~VJF}ylvrnG+oio3WJLh+P=lo_p#KMpW2#A0Ph<|_xh=2%)fCz|y2#A0P zh=2%)fXsW4vuMLko9{vUKd~GlNtW1o7bm`vXv6{lns8>J#7He}ER<lc#8WI<7^_w; zda*t9Zli3R@)B%9D!fQR%zyf1bl^DZ=fkSCgO}LCan2^ULVM3pBSi5OyBL_fV6t05 zj`}i2pz@2kp?|u!CuHX%>v0!ZL0gR=9pBxwYL%kj1O_mLDK{;@=W)lB-2@I|)IF=# zRhp9lZg5ku*O<h3%$QIvG**NC2pVR&`T@`J8bFnA+c+aPQR$|HxXCc-{bVh(e51Vl zp}Bf4^hwy7`6C02>x}msr3ERrV>e;N_=GORM{d)M^M42V#7uh%EAyrie4~TW&PB?| z8IV>c+EpQ%H!ah_=wgD-$QTZiByZbU>Y6uwqF{_O!(i-5zG9`lodNS3byZFi;rp1~ zqU=*No%Yq^_U*M6W4EHaZgD27uo+&b6XG6z(>zvR=(X@*q_f#qpIoNL$r!0n;-k?5 z#zv0$d4FnlGDb2KO^1bt;tJyY{whGZb1||)(Y&-!Sr%(FCC-5)D4HP)O`~XyX1Q}9 z9-WePULGs_Ry6UZXAm2JciTWl6ite0kTn5#w+*CE(QGvhqFVMl2hxiPjV8x5$Sxi4 zj%D_Be5Of=f0$;<QyTv$$u-Bhg!m3^8cih1On-SghDc>0?{u>HP^kPLhnW<#mn04= zej1#uM<Us$Orb7lFLgRq%^{~N#Pd3nC=JR(sg8$kMmP<U!ck=!Q_Kt4K95s6AXVXL z2VK;e$RrB`jIl`_>t05M10;d{x}B}+IW4OV8heMs)oDCnFrgk68QK)F$H1y7oZa|q zhJW4me%3Ib%l?gA#(Z8d_ztHJ8J33k=<Ol)&T@4O1Ne?eBx3EXJ7gkTNwN+f^hC3j z)}_b|r@Aq0v4~ONp5y68(-4E);;wV{a^1`@m)v8V`(7AW$@@V;eCB@lzQU2k2LVC6 zWre%nMVW4ZR<qL(VTkpi2yXjc;n2!zWJn_usaOFoA~24xc#m$hqtl+&kw<>3D<A?Q xAOa#F0wN#+A|L`HAOa#F0wN#+A|Ufn{sJ$=(Oz3jj#2;s002ovPDHLkV1g}Z%n<+p delta 1331 zcmV-31<d-|2fGT88Gi-<003~}l~e!#1p-M#K~#90?cCpsRAn5;@%OHZ=C2G_t*x-! z6p^T;rC+90REt|TMX}Ht{T92(pnpIx_aX{HZz8%1Gz0^00@1oC2qKfVC=!d!UFjEV z7HR92k)JM}GcoMW%z4htInOzJ=KF$y9p;(yeCPSh`SqML3xCEKV~jDz7-Nhv#u#Ia zF~%5Uj4{R(mZYp7FG@Np>E<k!A4*54jxzpG^MF&pvgrAvz`JR7(9scQs5*o6lD#>& zP$~`pFXU89OUe>REqj1B@-8B&G6w>%4fq&Xkar=z${q;724DoZDeppj%+9-jCM^Nh z0@nhIfNo%JgMaobfMMWC;O(r7YsE}D%36;*db%t6dtSrV^}PjbP5P!#M_Iu%dHg?H z(le62QM$g6w4#XTA;B~e#uiEADF;z)Lehq!1yaOK)=Jz-U2k^P{O=6V1N;%!uw73K z2Y^w+gaL3ddK5;H0@(@d57^%Y;9=lgz&?v3W*hxlhJX8tTE-+@=d<rn;C)Ci4L<Cy zz+rqSX>O7*)J8k?L11r_i~JGz95^05-%k9w$(y+8l*HQMJY=n;$%NnTKPByv)H`Ka zizJ<mdGAX}*NQyWkYH+j$oCV5v0KvoIs+CLNPjC~v_l|IB?#gtNjJs}SiL}gmb9+b zFv=cCr+=hxJ;OLAX@SRp)e7WONsH46BV-_XoQDhot3BEs$4w+oc(lC$OjbSL58Miz zO<G*rsQR{j<gsQ<N?McfTl}=7uO)3uYpuv*4GAXtVCy}?kfi6c7}S{%Na#)010HR@ zWCmUfPfN%^E(PxNXtz7-LSkG2T#vgFSzeOvm4Ebm%%<vBxg(}LHcLH{4p-GOA?X!K z9U(Va!ARN~6NV)1%PEZ2lFrwu>2SzELT<9wc(fVLyo%ah1um)c+Wo+@p$Z8Z$a0T1 zUu9m!bSr`Wm^P1uC<MX=ve2W=c;;11w*+@9Y9q^HUQbQPKz!z}3w1d)4<Q5j%cITw z%zvwx&gcFvpW0IqGLSPKZMrkBV!Eq5+W6F-ijaYP@6qOl%&Taw&(W-ls0bOz2yRxk zkvl>ZGR5s4ZALP$A`&u?VdBp-?gmx`sHI{RaF0j3&$2G0B4i+^fTJ;OW&w`}sHNhG zsLw{maTA{95=~L`c}$<2mb5g$bji}FuYWju%Q|%e@Bwf#rcD<x6rh%&sINE&vo0hT zRo~2=9^Xny8pv?%81Ss&gxoy<c@JOuss^K5(m9X8xF~6324QTBmhEGE<$a|yWl)AZ z2jpM5{Z3k8^hL|`F(UVap|%KQp`=mIfsv#gDTJ{jL4T6;SoQl$r^Wz1lyHE)lYg|i zi6Ayd?@i*s)a8^;oxyrN;Xp}l&DtetlccU1%j}9?ds)fW!iq6Tm*o{m-gmzP90BfY za<S)uG2BnmW&w+Vt7(4LSio)6IDrjy_j?fnnS=Xp3%7)-rGb9nP^}l*={JV|0GohM za;~F~I$?xVgg^lP#?AD;lSAL*xPK}5BRTXvO;G}=7zCap+@;)uz%}G^b49-D9A(ve zTrTOoCXSGN{UYhXBsG;z!aypIUP*^i4C18R4SR`e38e@}Sw4DzEx6y6C%w#j8n?6F zM+^Ijb~@}$>Pp<X!aCf)XIhTCF}f4?qn+P?AAl3UG2F$amiI}e`xUM+#w-|Pj4{R- pV~jDz7-Nhv#u#IaF~+os{{REnc4(38{e1uc002ovPDHLkV1f$#gn0k} diff --git a/addons/skin.estuary/media/colors/red.png b/addons/skin.estuary/media/colors/red.png index 9140f97bde3b55423cc710e95517db01cc2ce7b2..00e7804c0025ee3194a0d510caadc2b16ccb8b33 100644 GIT binary patch delta 93 zcmX@gQZ+$2oWar4#W6(Ua%rC<AA<tNVXHs?+k4sg*_zqPn>L!B*lEJRsLNy97$MrW x^zoW!J{1YGlU%L;7jyXOMylAq`cyC7z&cBX|Gmfb#Dxq%;OXk;vd$@?2>?K!B0B&8 delta 556 zcmV+{0@M9^#RQNee*uMQLqkwWLqi~Na&Km7Y-IodD3N`UJ4nM&6o&t%iVrFZb`WvM zP@OD@is+_QC_;r$E41oha?=+yX-HCB90k{cgOA0kgNw7S4z7YA_yBQqa#D1W63<Nv zEn+-yxgY;M|IfYW0z$3CG^=e4&~)2O#$#eSvnqDHB7h+Jf6#>)nOVl1BqiWmU-#5a zbr<DX{(XN|kD9d@5D<xHnPJ+*8^lwaw!wLyILr#NN_<W{YSIOXAGxl0{Kh%&vcNOL zW-2*P93~d?Ev&RKE0`MbBymL5bjlaf9;=+UIBTT}YuuB+FqG9-mbp%A2yrZ82@*so zsGx`vY(#0-e@U^Br1O}Mf6(<y<Wk601S7`+%FrOYe(*o|JzFa`G43UWVnF+g<9rMQ z9lJoi>NwxWj#EDYg3rK}-t?Di!1O2S)ut9X0(!TBi|eMQ>;acMz`&Cso3bkfX$iR; z@P0<$lmYs0f$lYLZjE!CJ^(42RpJIXI0Q!Wl)dirf9}rKx&7PInBNaYmvWV%s9tFR z000JJOGiWi{{a60|De66lK=n!32;bRa{vGf6951U69E94oEQKA00(qQO+^Rh0Rs>& zA$U}7V*mgEK}keGR2b8Bj!6yxAPB??`u_(T4PMlYM0;ZE1P}sxz|dLcrlcMhd%S9s uVW??qCg<7Ee@;TSHO2Ski-2Z7douxQ1{LWxDUebC0000<MNUMnLSTZqKJsP& diff --git a/addons/skin.estuary/media/colors/red50.png b/addons/skin.estuary/media/colors/red50.png index c91df948ba5c3a5ccbccc039d042127761c7b2d8..1b9ffd98fc602c4d90a0f071db7c06d99de8e43d 100644 GIT binary patch delta 91 zcmX@iQZYd}l)>K9#W6(Ua&4a@7lR@Lll%7a?O#4*@Lvp4s1%ZQ<mC%>aENxatUjY! vG5J2D>iZ4%{{LxUN?_>qoVDhiH|K|%wXJNECA)GJ8Gyjk)z4*}Q$iB}{xu>K delta 554 zcmV+_0@eL^!vv5ce*uMQLqkwWLqi~Na&Km7Y-IodD3N`UJ4nM&6o&t%iVrFZb`WvM zP@OD@is+_QC_;r$E41oha?=+yX-HCB90k{cgOA0kgNw7S4z7YA_yBQqa#D1W63<Nv zEn+-yxgY;M|IfYW0z$3CG^=e4&~)2O#$#eSvnqDHB7h+Jf6#>)nOVl1BqiWmU-#5a zbr<DX{(XN|kD9d@5D<xHnPJ+*8^lwaw!wLyILr#NN_<W{YSIOXAGxl0{Kh%&vcNOL zW-2*P93~d?Ev&RKE0`MbBymL5bjlaf9;=+UIBTT}YuuB+FqG9-mbp%A2yrZ82@*so zsGx`vY(#0-e@U^Br1O}Mf6(<y<Wk601S7`+%FrOYe(*o|JzFa`G43UWVnF+g<9rMQ z9lJoi>NwxWj#EDYg3rK}-t?Di!1O2S)ut9X0(!TBi|eMQ>;acMz`&Cso3bkfX$iR; z@P0<$lmYs0f$lYLZjE!CJ^(42RpJIXI0Q!Wl)dirf9}rKx&7PInBNaYmvWV%s9tFR z000JJOGiWi{{a60|De66lK=n!32;bRa{vGf6951U69E94oEQKA00(qQO+^Rh0Rs>& zH;jPWG5`PoKS@MER2b8J&M^*vKnw%HN#Nb^a&$Bl1`f8eoNN?=!8X{lTit12MVgt@ ss9El(B$5B*h4QAMuFW>*=$|1hC+m+3ip8m4Z2$lO07*qoM6N<$g26ubPyhe` diff --git a/addons/skin.estuary/media/dialogs/dialog-bg-nobo.png b/addons/skin.estuary/media/dialogs/dialog-bg-nobo.png index 78ef2392d9e09ad464404a601daf5e1f7e01eca2..160d5599f1d67b99aeb6af530cfac48cf0e80c06 100644 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-5RU7~0v5~+4DT84nr=%M Q0tz#Dy85}Sb4q9e0AAn>rvLx| literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU0wmSG7d!(}nw~C>As)xyo;e8QaIkDRr%=Ca vLDP!oY#Y8@S<TVvv@k$Jgsau*;aR3jbCo)yic`{o>KHs-{an^LB{Ts5@pv9f diff --git a/addons/skin.estuary/media/dialogs/dialog-bg.png b/addons/skin.estuary/media/dialogs/dialog-bg.png index 7b7e1e4b3b8c47a1ee33cdcde578e37862165b8d..47f813659787b7b50b0ada1655375521ec5e76a1 100644 GIT binary patch delta 350 zcmV-k0iph!1Nj1wB!56jL_t(&-tCz|YQr!PMW1CUPE8GkEPAY-wa4lrP;iLtSeCm; z*Y%iAX(|7$2kF@}XfYpz5JCtcg!sc$#;S@yWv>646<8^(tUiJjftp19DfxZD!oti# z*;b4u!N`#Zjznq)!G+Aoz{u<)Skv(M^y)iX?=P$r%2r)4oPQNK(78{4$H8)YFts?* zx(l`>%R~oLixZ8zU_-J@bTAzjOtZR=XuI3i;>!3Tdk9osE&f#`C_xEIP=XSapadl- zK?zDwf)bRV1SKfJ|1G%M5{;c0PqqEQLUtF-+Z%qXU?DTO3l26#OLElEapIX1Ey+P` z=E|8j&h$)M#aEW)_`<}6#LUqtX&ULdFtKbGM^lS$TqyKJmu-J0bK}a*tHt*)F%lia w#7wq@iS{`59z}S?vE_#zFCl~wLI|;s4--L9Ouk1`g#Z8m07*qoM6N<$f&xUM*Z=?k delta 385 zcmey#G>3VDN`11Yi(^Q}y|=ec=N&Q-XnlC!&qqTmNL8$Mja|0<YVIXo3VDTZ^j@U8 zvl(s)kj?M>uv{|R!H#q4?KnY3R*?V)5OJWIZQF8tzWoj>{_kqK-^%0C6zr)Tpc%4? z-#{SK!({%6(~Yf-^BRr(wlGF7voTn@w2*->r7Y*>#or9|(pGZw<7&@e`^`Po>7#`4 zM9$*xm+jM&ZraX@nloc3bGpju&c0=0>Q?0m?wjUh+?MG%R#<Ycp?llfwZRwuyPH*P zji?u6fw}|i7&!4E>p+&b$+?(g`CCIbOt{-v`KRqZmrlyIcL&z?O8A9aGL)Q&{7`kK zN%CalwatkqrM<)696#i8`st4Y^GrMEtqi`jCHd$q+rvK&uU&bR{hCPF7QO9%XP#-~ zWnV8QfB49>Ylc2&o~$?67{_D7CU{)qkanN0eeyBpimRp1JAa>LZoZc&3Jo3mSb2t; V=x!6XP1_lOz|+;wWt~$(69D1xruYB= diff --git a/addons/skin.estuary/media/dialogs/volume/mute.png b/addons/skin.estuary/media/dialogs/volume/mute.png index 8be6d1eb68750d62e8fa42b1a01af8c1e38c4f24..733404d006b9728b1cb8c716497638946a0df4e0 100644 GIT binary patch delta 451 zcmV;!0X+VI1Kk6VB!8qyL_t(o!|j;OO2a@9$A2l(V9}dr&wT?gf*uMQ?4f?(#f#p& zd+_2_5PX9k^acDXQd%sC;>{=UA?is9Bp`a3!V)*hZqiD@9SDS-|7?EA&de_H-s*Lx z*(yxAIY0;K03DzMEN92S<0JuRO2qB~XF!yrs{(ORP0=s741cuc-p|lAfFW?7qVvT! z6zy05o4^B5%h5G}4lpk<w+TpJ#+st(=pbzY_hr&qLpb#%0tIpe7byOk4$`wYZXE&V zfT!_!iF|qo=2GVADZo1W1QtfQtt*lj$+4mq1JA%lia*GSe^Lvtl2QO=Z5!wpi0c76 zf6=HTpk)c!1AjJ?ZEKP{i4CL{?%(glK5z%D8)ySfpPVS54O}Lq4)AphfY+9AD&$oj z;F2`n<aGjk8$6^6tYrB6Ifb(-b679&DR2mU88qrGAWVU4WmI=4v2oEHYy-OnN34NB zgc`Ugr3HmDHYi&pH<Bx%A>;?pRp63+vMZWjaJGkqq&yt}7qT3FPDMusRUeJ}M12V? t0&5043Pm-Z`@7`;9iRhrfDW*f{Qxp1TIQMh;#U9w002ovPDHLkV1oRN#lipp delta 357 zcmV-r0h<2Z1AqgNB!5RqL_t(o!|j+q3c@fHhdVV_@enS8-JD!qbnyZX4h|m5(rhk* z;0-*7MNt%=uaH0u`PVeXf^Ya1^9)~}P4cS=03>}TAwd!(K~KT+V9**8B*7mDu91C= z#hDh|MsS7za(C3F9&@F!1&7F02!f?<j9ic%!ZhPdj9o-+6o0`9a;wRKMXtVxLXl+R zGdX@$B|`+~$WxI64>?s*mD5T#6<i^2&7`1to=QWVlq@XcrR=mx@L>5ecnDVH0dq)p zt`0&3!Kwp1V4kn%v`fJdNp%x-F4#n}&>cg6FWBp!xYJE?>iaQcViW10=8o=`GCYJ_ zbxY*s(ZS4c)n4)1RmTliUDh@%Ohv?UZ4y<IYr+CI$+eAJBhR>%i71s@;#Mwdve<!j zA><y8I_5DK>mpHCY>{JMBR{>>ks$rMMS>(qf{=UwOO3B?V-YW&00000NkvXXu0mjf Dl5m~L diff --git a/addons/skin.estuary/media/icons/addonstatus/manual-pinned.png b/addons/skin.estuary/media/icons/addonstatus/manual-pinned.png index 1b05c1599196b2331d210352b1a246245318df3a..b9f6e0e03b405becb1dab89e863d20df05ac5ef3 100644 GIT binary patch delta 654 zcmV;90&)Gf1*Qd%B!6v5L_t(Y$DNc<NEC4##Xr05ZlF?Vu33SWP*4e4T>_yF27yp` z>5|Z;2!!y~dZ?2Jg+L-AvTj@8r7(-?QjmcwtO|*TSQI2v5&z_3xGk>f>#(!4v+mB% z==(jr-@Nzc&G$Pq9-X5FFA(89m#N?wZWRIow9rNaFwPSONPnSarK?YEdZv$ZFBDL> z5{fCTuwqK6TLB*xmVbal%u<7?T4<-2`>X&JglOlhN3t#sFvB#>Tym9r_{>2ruxQ}} zd||<BUtVX7M*PME4MbV7(F#!-vIap~=_P42C}x3e8*Q^dvCoD-c*+ZwFxM8vISA|- z4{AzPdSl#zlz-B?qf=5g!8dL)&ZHf06*=zZqYMB)e&9FQNo@a58q-O=wa^2R(|!#p z!%Zltpb}}29pWtacuc8<rfFr(?p11;bL2^p%0^4qFpoIQb@tJ3z0Bq8bx2Bcc5yrV z2$=*WTx9|uH+Fdw05%z6gw0%o7!RqXnoi~o()3s(jDIppBz=<I!YTSla>aP>Wh#M2 z=72!@Bs;|8R8m8=(ICtc!cPe$_z_ElGYR(doH$7a={6XI2<<$|B)CYFqtr7&onZn6 z+Bt(R{-b<3XoDz=Yy(qyzqVnEe}vO`od#PJ*{G8)>evH7Q;z%u5hB1(W_U-l<9k$( z;##x1M}J<;%TzDr)sW4Pt0#Xdw+!;6&g|sp8+g_4YS59thC-VO>U%-?2NnIB0XnaZ zJo&7u3ULb?pgTL{DK!;lx)~JdRgS@37oMvDDpSmszb^FjyBVN*{gNb2s@$DrGC<e# oM{D`JceY37Z{;j;rcH#u-tWn3Rs4JB00000Ne4wvM6N<$f_iZ>MgRZ+ delta 671 zcmV;Q0$}~71-Au|B!7NML_t(Y$DNc-NK|1Mg`fEiR0_>8E6@@ODnYYNAk@MjFbX$q z6514n5bc^4wQ`{lNJK;yZKl9YVHVY<AR|W@6%rRA3X*?_KRFprgV$zGi@9@WuFhPm z_g$Uuo^!tQz2CjprQ<Z=2Ex4METtSEUj+d_O|(!84D*nl&wmD5RI0kvqQ`o#{Du5# zS4^V{DWp-w)Goi5Qq$kh9;T_pshYS>CwEu^N(pkEFD}V^*ux|fG;%tx+{H(BbCP)z z@8T14ZtL+XL)7DQOi)XNB?~PPp*CX>pqWnMjs^wHk+e{fISRZMoZ=DBSirdw6xa}0 zGw#*6%Js_e7Jno|t66PGnFL?C%rIkCyk(TNmzQDyeE5K0U?s8qKWIpY_1Z*tg{^(+ zQ<@u7Kmo;4ATz{iZu5X56HU;}iq*?hF_V?Yz{o^PRX_LH%SCq3ZNAJUY|oMu+3w<6 z<_PHog`8&uFPFA>69Cp3V1RYI!6^5rqJmat9i*wThJP7kkZ@`xa|=i4BF;I-y%#A1 z>X`xjsg=wS4^l=Y6^;fW77#uPDa40ZAe2t9lcz+9(@VR9L6G3arF4Q*MA%OaBUC#~ zAV+7<ppE}1uMMpcVV)!~zUkN2Z}RtX6nC~k0*{4S6K$SqwgJ#!lOG~X82HX4Z)nW= z9@U|!R)5UykXy5Is^@a6&*Debfk85>f1oQ4^0?}zY<wffXx-}0Yj88ZiJ$9c0{WIy z{!SkKl>s`TwN3JAmFME-Hb6Hv$PG1|;a?0qdSNrTwVsEW&j1x`)RMo<b^0v~5~xN$ zB}ro{$-9k10#?xl{nm==<iK)uI(#Ts;TX}x#26C?&L2|K%eU5gmVf{N002ovPDHLk FV1j}FIuHN= diff --git a/addons/skin.estuary/media/icons/addonstatus/manual.png b/addons/skin.estuary/media/icons/addonstatus/manual.png index 14115dd10ec470e91050a199f870a734cda216c5..23b3437b14b221914fcec1b1f0a1d7258e8e0a3f 100644 GIT binary patch delta 551 zcmV+?0@(f31i%E4B!34<L_t(Y$DNhUOH@%9#(#6?PA#%R(+~?up`aAR+7wY6T@^)} zHVF~*@gJ0nTDh<`LqrsnyMi`_VPC5jE*wE}5kesfQcW%ja<~x;md@kC`#JY(X5M#m z-+Q0uch7mxIrnCClyewBfp?stmHi~gLkx=icS@5tv;we~4uAUTVlV|TK@Z2c%GzdW z*ml@L2SqB>5|<bw=K!3@vK<RSjff|k2oLa=S&lp08WE-+(8dstC_^2HF><C`e&;dI z_?2qQ@R84K1>gk3$cafUs$COGi#y5)FTD|b;R>Hvu&W9yfrgqi+gCR@jpcjdl0Rrx zhxFR6?)u*%lz-Dy^nko37P-X(n(S(cAuKmn=;lXsOPnc8bDuq2WGCbHW^Q4JPuCRJ zQ)dL*IL{m=m+ROG0l+->=%$kqmg@s}tI)%9%AAEp0SDL(bT$gu#ZyX@ndEk3Js4z$ zF8Y|GxA7i~(m<GjYLyv&QUMl2pSEdRzmI<38OCIttbbdLFiNj|(gp(dgA^zLzxl>n zPPzBjQC=lkcIIVhIq@vMkfAA8Us4`b=d}cTS%<6jVSu6WSio>d9RWaX`Wk6}uWW4q zbW*D!dr|FC+Gqf}US<EMfoNqMAgh-Fz|9z4EC4lY-nGAqntd7o_34i!X+bUVsw4oq ppfy?HwIKIrCgo>%gc3_hf(_ZbVyK!Z4iW$W002ovPDHLkV1f>F`pN(R delta 571 zcmV-B0>u5m1k?nOB!3%8L_t(Y$DNeTOB8V!$3HVWTZgRBG^9XMC@2NdE=4aFJtak# zE(sCz_7{|gI(e`zOGFfwoeJtu7)5pJAm|E`hY$);kaF`-k=ur_xVrQ0;Joa(v$HSH zbD8h(eLnO2o|$J19pNk{kmNn5X=Fdq@eq^L_?jyX{iYFs7k`Y?MJv5!0LN+PC|6kD zkfu`(Ti8Pin@Zv$Bg9>R8!@(%K~N#$@dm;NtT4?nms=siDg-pq&oEi20x?3|^5i0q zc*d`?Wd@)4!d3uIFo4_`)4ZDXM&;s;3>!IV<9G0t%Y0_eQ8vqgfm+l%S2yT0GJZRL z2Ol-9Lwf6|yMH2(aZQ9b<PR~$Egn$ks0I4Tc)3g)Kf*`i9$}LE?BxPG8FfD926hzq z>PpSj?O+?{n8D&w6<<OCFv~sKXkn10>Hz**Xy-Xu&Oohz1MCJ`Y6a}#DQU8dal5t| z^fE;&oy^ct`wWJtA*_H}Ws0BJz<klKZPM}YqpM(tv46-D^VDE|DAwU@T2G+-AW4$I zAHMUBQ{MeNN+_+2lL?txirmGQGBx4((@K!TF37*=YD9Tf-HQQ#4zxK<jg|xqWQ+X8 z0Mw*!q2(VGQ$+xDQmaMfDK&?A!vW~}rt+M6`ltpF)2jgB=2{77DFCY1tXKXz?DS;- zdDN+Yk~E|_HI#fzL<g*(^IBI%-HLD-!x4*e4GxpeFFc9x{0FntX6V2&hIjw~002ov JPDHLkV1n8N5=Q_4 diff --git a/addons/skin.estuary/media/icons/infodialogs/extras.png b/addons/skin.estuary/media/icons/infodialogs/extras.png index 7f6aa6ddb05ea41c94e053edf201dde0f4c8e8fa..15e89cc35d7345fa8278c14bf7987f82fc50e904 100644 GIT binary patch delta 810 zcmV+_1J(S%3bqE28Gi%-008|9F$@3z0{uxuK~z}7?U>I?R8bVizt3Wv1QTRxH#^OQ zU{p*92dxO&1V(MrrHicp#aY>)TUQeZ3c?5}RIb964lVjYx)B61O9VA>m`@AeJl~t{ zeeVf55OnWm-nr+T?|J8(ch0%vpo?7%`qu$2$KYwy9o<z%9e=u}tCIG$qc1Ayy*}tT zQkwAH#Uwwmt)EQN6|N!dFhh|FQL93c%=rP5jC0~C*&xp_J)}sGAVm+u<k|4fPZ%e8 z9)LDhUC}ZHvN%RAhb#rkF6Sz3aRK_+afKGSNmP_>vgmT}&=&{bHv0e^F&3%3x?gYv zz&^L50NP#aw}0u4t^U-kZTBtO!vK=3x#~MCimr3ra?;^KtOdgzWXb_>XtlnTJ@#m| zoc3}EK+Xn`aTD{o)e)lrj9Sm1yGffd11tcr6yoV30L2iFB>)yo0Nqr8a!6vfa16jP zEg?x;1}bz{DQDGmOpc@(eYTdZM@mSN5_)9i`K%d9j(^5#i(Ml>0O${S{aLRx%!Y9F z1Mq{!po<0pSho}fA(}bx01jxj@;R&nFnIc0?FaWHNpE!4LEH2!&@Jw($@|@;qTc$> z%6hGDo=e{7rKEdW_Pb~ffDxZ1^PFeR`y@vIm<tB^h9v2e&#Gx1#qNton)W??@@e$m z;5z`RfPXh{@iCV5AGsBfO9Ajb806CoPt*kL3i(L&d9Kt*H3IM}4uB9(*tII(<*`A+ z2w-wib*FvJ(Z4oG8~Fn+ss?#ml9V=va8oNr`ieG<^t4Z-2k=#r)M=TW9vJBlEK^md zPooD=k|bq~0s~4J=_w5u=~<sf58$07>7g8hz<*O~{+mu5J@jdeQS2Jn^1@o8n{G<p z^A-(pi=CJPpkNVz3KMR(P5Qo$KMg7XFcrVy<lLki3Q&jK#Lh)EoamPM3RpH|e41ro zB7V!XHT?vA22TrdiN2a|KceYJk1gRla%_n+c-eQX!Rx|D$2ttZbr|34Z~>{q6{-%G oz$3qQjdi%-i@5Xu&yc9~8z$i%@!Y$ua{vGU07*qoM6N<$f?Irlj{pDw delta 1336 zcmV-81;_ff2EYoC8Gi-<00374`G)`i1qVq)K~!i%)tSv}9CaASHyh*TU=^f#N)Lf( z6@;p2L0n&uLXRT7_;T~$>mT8IuczA9tEUP91tA_3)ZjsSvO$Arq6v{mLYKxg(ap;y z>F4{)Gvmzcv%9mqkvd=a=K21f-}5$`+1;7hp@V~iQjt+MTYp#-{s4~T6q=u(uZ)k6 zKUFT5pJ#aro`m<oyP<FsUWdQHnf?9!Zx<F8zMq(w*w*Gi&;j(zot>RW8RjxvW8D*! zqg>Ts>DJFf*V&CUJw@{=xX*eZs0Nr)O_pxIPIR5^xVX4DO0)BDmvsvGeuo$L_V!+3 zn8!CZHjdBE&VSx1O3SA`#VFR&VyGrOuO2Mj{vKVY$E~fc2^#!>j={PKuk7ybR)+vC zH#bYgDo44hfiH%7s29@B&C+#dNwd=wzK)K;`pfO@?T3(VPF|{pYN2DOmwF=IoFrXm zk`#K9f;Z6t=mw|4n@BglUsCfe)kX)Xr+OpZ_=v7EB7d*W19~;k0q9kVK7n-O2c^AO ztLOmr)(l8DUUZ$Jy<xAR15}t|Cy{Qv$IZ>n`$ccB9n~Xq5Z5}rv0bM~;s@9O^gq46 z^6TsC_mcOQDDB0xqk5?)I>2UeV_m0x&c`2>oS$!C+}L&`=XGp=X*aGNt!IE{Y4<(R z;t)yr0)HET{tDy9bs~9F*Z|XRTqj!Z0Gp{Lrp3XLK1Oi(hQ&1XF_+xW!?m@wd&u2J z2T03@bmQAmy)^?`HTE@Ia$ig>MB+Ze2B5ECT4K79fB$RP6wK$wccRS@V6&z6Q;WH| zxe7ry&;h)ep2nnN@{k&zVpA}m8{dgGLx5(}jDN^@KO6iUHh|#YdbvD^Sg+TQk^46~ z1^Kjxbo1I#GiVlcfX(>tzWzi&*NA`5^kwurVIu*~%ZZVZk(b%K!(3Usz}_>aQ=sN| zn6D34X|UI1xKApT%DlN^uy=`?Z<~G}T?-omj+h+jXV?JF*;g^CeUX^YvHuCIc1us2 zC4azXjQ6s)ln<I!($6MSpuoo@Hf(uhP)*ggWeS;D;u!;yM?--+HbB?#nACnmKF%H? z_DA%QK($m8>Grc(0&K>3FZ&-jX127nw50o37rY04T3%k(<?bbO^$pcfE!8wv3szQE zHcSe4nGNXssl?dWSc5myyZn34F~kjX=YNKB$bCsQRLk7GL;r?|c~7A%8;!;Za$9Z_ z+d;9)LAv#_?Bx_%HCy82?1ag#7#$t`19SCol?zubCl@la#4`%n1NhZ!X?S?}VUt}k zJw2`OgfoY$TsUJnxsaJ9o>9miz>Haa16ECTMYURemiXhg><Yyy$Ch1@nI)c4NPisw zZ>n!h3TMmZ@{s9{Anu&W+{l|_Gn_qKwal1)6Zdm5SF6>^<ot<F0pE-+f9>*o#@sQK zqg<q0H)2m0b0R@KT}~$(2;>MClJ~K8e|<3oB#`F?A}{~tv}mtb?i(6nlK0N_X0!Ps z(v8)gV$cDW>&A+G-Oz}{=Dww5|9?ZQ;->R_w%d2cG+H?ql6zTN?*MeZTS}25&3#j^ zXP=x3r}Lc85ho`nhn1sTZ(p>Y0h&cKAzR{F91%$x=&{NmR4?^J2iPobtm_ny;=xuw zR1ft+2WW=A9&CveEAFGkh*q?i4|gL{sJM^UBNqAxj)?ZgF6K{6N!s^B@iBhn%aW$I u{b$y~|ImFT2XH&l_fv`g3krS!rP3|rZxJT)D0S%o0000<MNUMnLSTYW&!=4g diff --git a/addons/skin.estuary/media/icons/infodialogs/versions.png b/addons/skin.estuary/media/icons/infodialogs/versions.png index c4b29b9c2d6da6ae6207c3dd491f7a1e2fb69824..e790276746a5106de40a748dc0e26a1822acdd47 100644 GIT binary patch delta 328 zcmV-O0k{671lR(Q8Gi%-008|9F$@3z0Ut?3K~z}7?Uy@J!!Q&@uPH=Dq0nVdtRV|f zlk6mGU@=aWP_Yd&HjP7bQiK5sMe>Ib8eKK|yr*M7do))S`di(p01DuTjGSt<B5JK_ zIhiPfDQ(GtybdhGoGIHI_6%!}84oZxYTD0{1K`LSdm+}c=6?u)8w>a@$a%a2V3Wz% z0I(Ye@C$OD=>S+{01E)RG~k{sO|l?9EY+fzaCw_8$Sgn=oTiPeq)L+1=|R$?8t;$R z*J?^pObGCo<Lzbv%3)Uk1yBG5Pyqh`c=P)2k|cXwz_ZtXl_afx{R#uNrN6Fi8DqR` zW_ueOuk=U$&_R=4f|=o>t-QEV_WhFi?K9@LpJsk<VBc?i0r86#tEI+;Pak{JauyZ9 a9Gw8ZfI7_+@$>Zn0000<MNUMnLSTY<LXo2Y delta 534 zcmV+x0_pwO0;B|x8Gi-<00374`G)`i0qaRbK~!i%?U_MN!!Qs<6Be*3l?pp9#{s%2 zH{lE%23D|3H@!nqf>gnxE&p4S$f+idlTwBvKk3W%cxIeQtSWKy{eGXFGZ)gHEvW!& zQUTVSw1DMudAZx|=J=(1hDw<DlNS{I=+8LD!xje4C(&lJxqrh&yrDlD#xoDP>Kc@4 zJXS?f@Xo)Ye>BX)eCVocsm2&F>2|xFV*x&HKK5pgcC-%6ePO&P%W{TcD~Bff(VuY` zJ0zd#uNwQ*u|1l3%)K}Zz~T>RTJbTGIEUm@{ZwN}f$h=wV7_t|fIE7Frj;W`66cV7 zs-J44R;$$&0)HQ&wQrZl>A+mq_VDrTL*=8*cNf5mM_c3DW44EnHXkb=Z9eOK8-;zH z=lM*AxwN!buwS!ZRn_fuI$gVoHh$73b_Bh7La)<Z!7&&2YXziN32RaT)}#WgNd;Jw z3a};>U`;B(npA)_|F-~qsl7|L1?(Tv<tJtR$!SLD34c6Edq^Q|HknNBrE3Sji8VzY zt9<_1W0fwAIM|9hQaqHzIV7LzryBb-@Mv*?rWGF}iE~Ik)lW6XXu#oa*EUl{Kl(Ec zV~6BZ{Z-?!3T(5%Fc0>FEOQe?n*d2S+VsoAp9J`ZF$c=WC^Z>a9rySd$8{CCk16 Y#beV?_7BQUeE<Le07*qoM6N<$f;2z=PXGV_ diff --git a/addons/skin.estuary/media/icons/keyboard/accents.png b/addons/skin.estuary/media/icons/keyboard/accents.png index 44120615ecaccb18f85e03ef5ad638f6ef64bcf8..f9872a09dbe39e1f58f5ce77d9bc9fbf5e42ccb6 100644 GIT binary patch delta 694 zcmV;n0!jVcEzJdxBYy&vNkl<ZNXPA$&r4KM6vw|$%1t~MEp#IkLM~dgFbu(reryVf zXkQTqVc;SqiWZex;7CSU5@<qD5!50gii9ZX4=4yRB2meja0-Vs26dSHwD8WIH#6^z zPSVuE^A?wL@AusAJ?DMTeLPW2H&YbCqI6R%u2Dz}IZSE23V&x&2UJiil2!@-HsPQg zIdU|xMC32+k|RfF^JL7B?JN}tA<Bv66!=wyILboC{40cL26_<UcKUK=hMgLdtEY1H zMVE4%b6Cxi>eVM_B_;i!R!I+4ug8))G!zgO*AYoIdZ4v=zeG>A3+zO68LtOXL&CS5 z=Ma^A|C6icAAdIfw4Uj-wyR8z9F=LWE^9IiA5lnqR3=9y+NM^8vk+lj)DD%%QHi$d zl!k*!T5r_peO#WW)uZ1Bb1MyT!w1pKd<u}8P>8Ta+zddZgnuOf(cnjTj1~qNVVZfq zatGId+s*of4R2(GIVNbbGmG*Wv;G#|5NDMAcIHJkVSlEbG0@${Z!><KN7czoq8ES* zcKi1RF=LN?(2wvnO;lS*@~Jedbpho{m8{ly)t)_%TQwM4W3-b{gYNq3_=g$Cb7(yx zN%~~GO`esFX8fs`)_L2aFMw`&mI%IAg8J~j8O!xjGeW2>1G&MBhka@~kcJrXL?yF! z7xPQ?VSlNKJ#YL3HD;#N%(U9K5e|@9UZSb85_!iQA30=$lZ4|Q89gCFoJodw#7Q=1 zwM7+gi8IQ9;I`-pRA8yi;CdkDn?ZCfzwu*M)X6MPWD!E|5`_MT3b@*4>0~p)rHT+S zLOz5Lp^JUyjaN`hBlj3fLwu&4T3oYJ3q$|S=PzlMaFuY`gwcZXMT=JSmB`=vMKRq( c3-`<Y8~T8aJf$kvT>t<807*qoM6N<$f?H=s@&Et; literal 5852 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 diff --git a/addons/skin.estuary/media/icons/pvr/PVR-HasArchive.png b/addons/skin.estuary/media/icons/pvr/PVR-HasArchive.png index 1eebcf8a711360a0da3d934d13be2b6191dd699f..b59158d769d219ae021953611465c8f2ee9e89da 100644 GIT binary patch delta 506 zcmV<W0R{fz1(O7jBYy$fNkl<ZSi|ktJ!n%=7{>A6HEGF^f_8FH6D1YFK?T7fi?dD@ z3gV)(?v5_H2s%0GAP(wQ&`lHs3q=$<sa^c2i#iBJAyTPL>+%Xxn&jN%hJg6Mg@llE z|K~l=d!BQ$>eGf%YMT*j<N)OX{#Qa4-`CNOXJ`N!YG~pR_J3ms*6;<dv4EHOUNpcI z&fpXd#_Tt111&tmEbe2q2!I(}h?$prRA>SxaU4f*6N}vjFO8p_?DqRPT*l!Jv+e{k zj-%L%clgv#7tG)i_WfnHJSyP?E@Dq!fT<{ZeZVfuFpcx5qyl7NxMrU--|8KwaV!;} z7H%&WxZl*G5`UExfM&E>(Q0C6FMx7R$LCQUja~qyFyJsWfnB`-ve;gSp)pibJkTBz zz{XZ%Zw6S8U>Sy1u-prvjaNZiw7y{}1z;gkt!TYT0T6jUS8x^9@FZmk#9?9<KMDkR zj)y5G{G)rgT~J4~a2N0Mq~R)V;n9Fg(Z+S$=`>Ea19Qn&yu~CA^pT7$T*Vdq>i0zS z882dh-PoRJxOrT|jczZ8Eb(Ski9DZ<RIA6{J{F~2i9TP%V?4lzl;z8EUd<{Isf`Gh wYOM2e^!QSIPBw4eL&r~ijOYK|@ZSLV4f#}Jh>)Q!pa1{>07*qoM6N<$f-~dp$p8QV delta 715 zcmV;+0yO=T1mXpdBYyw^b5ch_0Itp)=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi z!vFvd!vV){sAK>D02y>eSaefwW^{L9a%BK;VQFr3E^cLXAT%y8E;2FkAZe8V00L4; zL_t(oN9~s}Z_`i|g^eh&lOQo6h(u$@DGSVWWr3f77`n7erGH9nENo>&u$2D*3oulo zq6I%2%GwEum8w$5Zp~6D3FW)ioy>FelGhHP9_h%DY@d7IdH3Dx1Q+->@wGD-e7YHO zjY__|(SVor?m6K!y%cVDlPF7;nx^-%m8YAnBHe{Kzu!c8@+>s*!yxNi(>Kl#Uplv2 zCS51ck9N@?U4JbWPOEI^{omX_Zsxs@B(fa*yw%XR{ea*j_>BNVTi`0oL`*t}O!B&6 zx_4A&HXyW&#lJ+<7HkrEXGG##Y}ca7O$S*ymhRicU8!z-pf0*9>j?H-ZJ&DORiZIQ z>qU85oItf>`#LW=?YRrlJBW(pzDAe~IcievHX$FPxqsp00+}w=9W@`~=$<T2!_<Hd zN7cTv3HTBRj>0T{ppj}od*#7Rn}9EI;6#qKMydgO#6!C_0biD<!StSJq#8&9d0N;6 zd`SYMX4YHNNHt)p;lNv)fG<g4z#x09k!rw5m8r(V-VLZqG!LEHvW!%=;n*hPL%QJ( zMSfkwOn(6LoJ_aQ(gn1JOvk6rA1z@XuTTpEo7gPG(eL3-yrA+9pcrMIleu?CL}L&Z z@v6)iRd>Uc?fJVh&waK+S~?ofWkD)C9XPl5u<t!djBNoXQC40Cmk(Gk@G&9tTt=!b zP?ZE8mV2II`>ZSf3v@`C=Ll~QV2|q&*Apxri$u+`T6owt{m)YE6mfs*J$C|$$Vio` xCWA$KJMOGWhnq9=i{&{#_x}eM_`eVYe*gtjZ!+rh^rip+002ovPDHLkV1nx;M>_xj diff --git a/addons/skin.estuary/media/icons/pvr/PVR-HasTimer.png b/addons/skin.estuary/media/icons/pvr/PVR-HasTimer.png index 120b7f13f1a66f6f6e94e828d8b8d2d487b1fadf..4b733b2ed742f082b39ef624f3dc90f913a940f0 100644 GIT binary patch delta 415 zcmV;Q0bu^50<Hs)8Gi-<0047(dh`GQ0d+}4K~z}7?bkmqC1Df>@ZZQAQHW$)iAJrE zC^SBTMxnEnQle5&D2Rg4sHPYnf&BuytyBsmI;Bb>Az9p;%r>vhC3D9eckW)oNhX<p z?|Yte&U2nKlQbHQTpw*fpRE-@QsoFCBw3cpvP=jesk*+_41YA6&7#}GIKo8P+N6a8 zNfoCdgfi!_hy&c>N5S(PLd{V>+ZcA^`Eox<xEiBA#;=}~pv0zef+nVL*5<vu7a0iH zi<|G4IPX~reqsjK*#6%_G>R=eMGo9_1K<*y7^(nZErR%s!)~Sc5$pI9Sv=7J@s@Cp z6+CncU=p)9#eW>mV^-#0vH1BoiCvW8qDtNVwPDorB9R%#1{UJ=69(JtH-fvj)HC{c z*@kJF=4qN1jpnP!?j(}yu_U0ow<3P+r)n4M#3*aCPD;>L#ftXpWPr7npc^U9t2nQH z8rC&CKGY}KH-K*dUlYK)b)Z$%I#kd4Gf*<XK2HgfFBt!K{1=dIdANO0X}bUb002ov JPDHLkV1oQf#BKlp delta 276 zcmV+v0qg#*1Ed0w8Gi%-007x@vVQ;o0P9IaK~zY`?bR_#13?(Y@eg7lsGU{7#(L2* zhe&N_3h@y3){;it3#8TqBxUM=T`r)lLQ*Y%*q35iv$O1M#)-scdDSq?j~VzLV`#tF zp8zsnQl%}sfdKZCQ(j4a1<0u89`PLyT=0b$#RD&hDJQYO0Dl2jSR8P~oSY{t7Pv!v za2^Zv`6T1Ez`OuU9y!Ju@{E|3dw$>@A>K%8#|PxBxh~g!z!<VXh$S}|)t-#$g#d<e z$pj%Xr!;5V8#Mq$Wt?k2uyO#a1F2HA0qz5K0sgX<iL7(`@xVc99kLGSfbfsy5Wv6w a&KpvjGs6*+xeZwW0000<MNUMnLSTaZk$T4f diff --git a/addons/skin.estuary/media/icons/pvr/PVR-HasTimerConflict.png b/addons/skin.estuary/media/icons/pvr/PVR-HasTimerConflict.png index 643e7030a76e8ea4d14b961204166e3a6e6049fe..049ccc802fad0358259bd05f10d93f95aa85d032 100644 GIT binary patch delta 1291 zcmV+m1@!vv3Y`j&BYy<sNkl<ZSi{AaOKenC9L9g=+<RxHGgI1*eNLx!cqOGGRt&O1 z7hq!4xWR=RG0|k=!Wb8rh%u6gE+o-Jwsy%zgAxoYBQXI)sgFdY2-tQgt(3l;b~?{{ z@45H5=oAJe4IQNXlbhU=bME<n=lg%>JLd}5bv5@fB=<SYs(*K}k`Md)`vI2N6RA{c zrMdOJW*`cD1#G^TB@oLn_ad+xIKAuyRU#Ds<LbMBb3haDGH`gcOR%q!{9EAYYBR7M zI14D?G2r*r6rl-xT$%jPGRg0m59|SU029DRt8GMa;6uQvXkhkk09*tP0HGBCc&7s5 z6!7KUTCokh2Y-|*D&Bkt!h0Ra18)K4J2sMKcENKM7PeN}mw^4ik!ls+B5SP=<?{A{ zk#stC{`YfnPbsslBR&<6Cnk&eO#Z@!L3?RzDwWc=0O;@U7ge3r^R5Hl2VSbQ9UySK z)l5!hKbe^xebO#W_WCLp0Uw{Bi_tJ-g<8K41k5ka{(o@t3}7z-F#o+TZ^J~oqV7Gw z+rYck0F+Wl!(#Nx2%{H|?G1Uq&{ChFG3sNQP_m_!EysHE#hzZferKwyd%gH;Jbl~K z@J`}j<<X1DU6f}saO!L1{9(3qXK0B9ky4|zMgv0lbcrmb(ix_+gF;51MJ9VF7K?<# z;o6U50Dox&`04a<TJ&dZct|6SIvfRlXe(OOFVgClpnML~5VXY#G}y-o26i(#+D<YV zzbh-&SFet7<>$R@@698m1T;cOPW<samq!7u379rdKXRI6M-k5#pf!2BUQfw4MWmst zHX|AzAGfAwa|cHTkFvIH8qXIfrBO=bDorG8(tjKa(bO0q8kV?D4yQbiQ_kW#c>-cW ztkEaQWHKw(0051#w#c=y;a#FQKtrd6tNa@z3gIIJc%FyjDwMrIF7qelZ~^7Z$|#9t z9>-%#Z2;0E=@#uwZHicv_}W4#A1O^dHIHXcBE1<*BaUg9C?P?Yuw8-Y=?VaawlCAr zoPVzkz~JC-@t(DjNXSyyt_j+roKNG~e*=oTV2P$!6B<DwGma@79NR$q)qCC}8Y<QX zz;Rp)e0&YAqbcVupqwliCQ|!^>IyV9HRAgkccBbIQF3LKb!vp~Qzvw701}Dr>j>K| z+0rUz(>UcU2nkA|wTq#1MAyXdJk5+>K!0l=*YT>%TLYSs9jy&Odwa)}=gbe6-MUQ$ zHG|d?@GJE-C{F*#<fy$^^U`@}7C5f10^nf=o0%(wY6CDkJCSQ^Po5FszD@br@2~>F zn{)dvk(PEI-nau0OpQY^a|zSDncr7s+B=_O+K<)d1H(f@c0M=%d2{jwUDg|L9e)qy z_$bH6u|4Y6^w6^{MbEa^FrwQimRyv(*soj<X(sfV?pN`(QJWK1{)&R}&SbeIJjVRc zJ|uY%0<=QcJ<b9<@jNKZ{Ypph1g2$x)<_xDYaiXuwek}p+1*iF36?G`<6|RO>o?Q1 z@dGkLpK4Va5z-X+a|0O800t1YKz}1B7hUSYojTd~1~cBSv1phF6N$tv4Ac}03t5KK z;}~9s;`JlsroO=`UPXwT?idKQ(%StDts7q<ecd7ww&?5Y`_GN2=E&!AOiWHQmzkrK z8zT}d5D649Oo{77DL7#i58xqLm+U5yNZk1LAIbt*Rs1FO8~^|S07*qoL<FuvV1m80 BW#0e* delta 1372 zcmV-i1*7_%3hoM!BYyw}VoOIv0RI600RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_ z000McNliru-~k>E8#hcN`=I~;1p7%uK~z}7#g|)XoYfY`fBU=4OlGE+#H2~4X-sN` zRJ4sk6~qg8yr916t3Id|q2P-zDvDs=3W`dh2z?S?6tyUFj(^CJgL*6n;Rs$LUfPT? z=_R*LGMVf5t^MugLnkIyZJN>QdivOxwSM1PoBv+hh=}qxn#bR4!SY8KhGxB+4;?zB z0Q>gsTle1O%wMz&h)7;U-V%{3*Ce?Z=RiarYWZF`Z`_LkAR^mDq}1|#|M>teRtcT} zI)Q%!NB+_hh=0g+EuSJHyVi{RX99>wAR_-25ho(gUC@vJ$p8<u{GAYy?!Od3L<%Bu zQbZyVx$i=B;CBz9h<qa=`$Z(P4u&Uz0`M{L=DHN=+zg1w=gZ?Fa%3$rX&pQ#A{7zY zy`H#cIrzC!EFyOU`+;j(L*D|=gkku`YAIc9s`^r;8h`B{FBZGL`tplHVr|gdUzjNr z2Bz!PQuXGWZ;H;9;@<^WwQxJ|EU;YH7)brr+Uc3{^K-KkH$}DSE1a$5z~M*>{-$GL zX3Hn3RPg%KPd;|^U+=vi{R-gl;lr$Tv53gyz*E46<uPk5o*y#t!#ES)yiF$gKRp{t zY|cA^0Dl^hN4eQWH5tKdxJBm|Vsm1u$lkqse|Z~Th=pNz064G=ZLMIFfKNYu2XFCJ zb`6&3=}O~yimIZ3F^+AfOrvp>+47k2@^|44jZm-G$!4=_m*9L?sZ+xzAHGYsdzl>r zisu{Dfy<1d=4_JGHK6$gfhT~{j6x<G_h53@Gk-KxSYHNK_YNYWQ<Fd9&b-3#6$wr~ zoOKxE@xf=?_-+DFB^6}2W4F&xe?;O8p&!#5{!?e{4l_HKZm%CG7K`EReC5FS*b#<% zXGxsFTE$vLtdh$Hba!Rw+?*nx^^mwi+*~AXmXWwh%A7JA^&?89QhOQL+|`>qIeGja zrhon^o3@6Cb*&kLF%DxONfKhQ*l3AL>3f>l8rFHORUTpRE(yEZ11OFcdo-TenG2_J z8e*-(^8ymPND@usC36IRfxr*2#sh5-i6Kd}1z^$WJ2rJ!+XEOI`@AqRoXcfGOC$kk zNV8fbiOv9)j&y_0u1*v~tu#eoV&ce0-G3_4A|aotw+9f%5`x1iNUSs~Ut{Ak_yL|e zG95KKJ2&H;B1=s$mWFufs_@1+I*hglFfcH98WTw)^1^z#NZc%g@xWSC1z#)VH+GRE z%ABjA>X0~Lg+KwN5#`$h=<DmBN#e!hP3hQKvva6=fNRy)r#|~XrYEAaHE(<c-G7Ey zbQM5CknU!omT3=Qe*RRYw{PgE$&T)<&i{unm0t1fgj`P_S6zMsV3?VLdg)t&U?sk@ zP5QRp%B<^ZPX}(l?Y5{|S$wT~=w5B=4kS*nvBSoWI7;Z)IKs%DhZ))P2!4JK^@d>O z?7Wo(FBs5`gAd}=Z%>K=hKC2!FMquFa<eBp$>Omm@Th_@pcd`8mL+Z>Nuajy8U5)G z2tprJ@w~JSU;PXxn>U)F!T$CE01h5JsM{~QlFo39(y<rSHph(@7@SokZoU0N@Iyl) zN=J694qyE!^X}HkRLG^Hqq|oC+O?&nGRKQk_(_TS={Km%yiZ*J5o1==F;$h>!j{21 z*>d><6i<ibvLU0Rqx_;D_#;-WR5&#~%R*^^MrD#*x<)Ql#}7Ot$y1B7SS}&KWBbq` e0|Ns;@BIlwl2g-=&TC@;0000<MNUMnLSTaK0F+Ju diff --git a/addons/skin.estuary/media/icons/pvr/PVR-HasTimerDisabled.png b/addons/skin.estuary/media/icons/pvr/PVR-HasTimerDisabled.png index 9bb421c2100de88c3017503660a7b73455354619..591b3f3c9d83c00637ba21c3670bfe98c66386fb 100644 GIT binary patch delta 325 zcmV-L0lNN_0^b6VBYy!VNkl<ZNXPA!t!@HA6otR#r=kK!Dk`xE#MTj#CBbYprm9Vv z01BRf2LM9?qOvFqqG}XL9SO-KeSlWfRMnui37ZB4gzRo+m+k<ef@9~-x#!NDGh+zJ zUP!PfQUI9hMFA2p?XcZ4u7XQC<Wvg6;n!B7%njC`2?EluXnz7-{X@Jb!;Dv5f;?4z z>G7jSkfg<u5%0P_xTU~1&$>p+(!gcFJepukoeL$w16kgf?6UHLN2YXxRv}LnhaQV? zE3a`!kvbL@pFA;HTXlmMKGf40LV_?owz;N;4Y;H|btRlYN&tJC1?lPaMH`bouCI}! z2$GDtg(0N_#6Edv%;bi^4l9w$O!{`DVublPPuiK_KM+LvK&W5mSPHx`JI)Bk$$#e+ Xbptx}Y8Q1f00000NkvXXu0mjf9x;)@ delta 249 zcmV<V00#fx0+a%fBYyzfNkl<ZNXPBfEe^s!5QgCf5)!H~1c4-=I06!dfFocL6$Aku z2^tiVpsHUSBsC4`912sK{x0361VdNu^l5fFU)DlCTUI52RromKD0a00Y<BdtfYcXf z2zO(E4mX5}Ij})E&@==XB0T78HZdx#b0T1={THIrDFU<%0dm@0@#F2w1#nr=$OYC2 z$LwAjxF87o_^5>K3-B0}m_$aL5d@d<s~}t}Y9&XRE?We_PlU!+W+59H=6H$B<)%ad zqz>>7C=P2eP-+g;uN%bq5Bz~jE|%{BKfRrwLNoa0nunz800000NkvXXu0mjf>n3JY diff --git a/addons/skin.estuary/media/icons/pvr/PVR-HasTimerError.png b/addons/skin.estuary/media/icons/pvr/PVR-HasTimerError.png index 26a61cf3368f7eeaf9126c1d9ed399b075659abc..076b6a8fd825c5d1df732f82779e17e3a1f91d9e 100644 GIT binary patch delta 1326 zcmV+}1=0HU3BU@FBYy=4Nkl<ZSi{AZTWC~Q6o$Wj&P>i^JTo!L1jQH=6|_zjYFbjP zS_PrSdINhgmFSC@mp*8q(3Ya$4Xj$ERSTLoty0iPtzx7qt<{26M2*s1w8m>P6JqS# zk~x=sP9HLcsI8bu)dd^Q-muU5_rL!2?=39La(RiN@RGyR?|)kiWMge@Ex<$e#Pa3K z2b$Y`-VEqK9We2EmOwm(xkbQ2;Pg{}&`*Toe>{B&a2d!4mI8ZTbO}DsBtH%ue9;WN z37iFN;B}zkMT*b^Y{~rmo2MjyZa%OCm<F@~8(*{$jR4jIW<~>@g8|S8tOa}n0PsZy z#BacX!CJ8atbYP}GAf?<48r>aNCKY%>1Q^Qr*y%b3=8F%c@wZ2*xz5pkHyww_4R9B zoiwSqxw(0aWm%r0Q0PuH61h2N?%ezT)&+e4YHMrz06gaFV}Lb4Rc3AiIge*HO_O9Y z`Qwm4;N8ZiCiT|!>x#5(5h^aWOGk}zt#rEK@ZrNtYkz8Ln*R#`&v3DP27Czw`Y%c; zY47OZ_|BcQ)z{0MXjIXKK+V)r78S{YsZ-VP;>F@YEJo{%8&p?UXVnE?0m1$Y(P)&0 zOPBb1`gC&Z>KNPIt(F7=B45`<P}fC~u8ZM4Js$1E3Gqi|r6e9F91bI;d;)+Ns2fTt zX>V`m=YJ(jnBw>M^6EGUAs!h3(lja3b+vu<YS-=RvYVQl2WQ2mX}Vpp7@Oak!jxP; z(s39ub0&(Waku^`!sE?U+Qna1MrihI?(EzNKq{4T-q^oiaa8q@2g!gUnMfQwv1`}D zDSkh=Fk;$NDz<FFM&N21ZHEq_s4CvPJjQI?h<|BWjPm=rxnTo(Kvy~r93WI#nVl2v z-Md#GT39$ca`dQFgg^-BiFRNZ2HlA`qgSq^aK;RT$HU02Tj}b0Ks=G4vps^*5hdN; zPPDF$U}0f)CAfY2cG!^e;;~t?yd}Y)=w;f)8w@gO_imEO6e+{Nu`JR?8dnJ74Od95 zTYn2m@0rbbjOk~7&yIoSmX@*iT3gkC03krA3No1>(bB@jMIVt$Brtlqdy@l+Uw)-$ z{aO@7K@p$`@aE)@x_UJ`4H$;u@oG7E5JEv<bl%4?3{)Y&vXQoh%yfe%N5k9Hh;Uql zNA$Bnfnr(NF;El=-42Z`v8AfI_TLY{GJjD7<P7mMe8&z9$LUR>Y52SzO4qJq*!+12 z+v;Ng+a$ldJUal9NaSW=DCCMz$Z-t=1@M)YF?{zP(x!<k1j*B9NY>Pl>&rz`6((%n zLh<__AZ&>u1UdNy<b}i80a&nL!Tm&6*VTd<Zwplj6vsh(Wjtxy?oEE>3i7)hn18o! z(z@tF{JFXKR1d?JErWCq3Q#(K0e4zkvp1qmn>LM~H*a3k+3C|{$mdfX$3cH@F4mYb z%<bDjC?I;BQ6D#s(p9Tztggm2(wMOr?_9df4<}C!?ooR9$Pu?J7^LOnN>>v?xV8;a zf~Gxo@w&)l5>GIQV_8hzwmo@m*ncp+B3lhSjJA9CxD}5x;rqRIcPiy59xsaKLqR+` zgn~B^#EQk-@^7~#@0OGd3HW^Iy8Z|QS%s96^A|2KRMUv>+ehb5hp^h(P`w^NAWai* zK>@|{=QDc63eMkaBjEE<QBm>4Wi+TbckUc%!=P;Zc!)$u|Jev;n!ML0lP4Dr^VhX& koIZ7ms;Vk<UGLlX4~wlZIs(tC_W%F@07*qoM6N<$g7zbcVgLXD delta 1251 zcmV<91RVRo3ib(*BYy<FNkl<ZSi{Yi`%hD67{~cRBM6f_EL;kLfD~{;RLu4dn2aS` z%zl}3H==F{;RiR}4=jecu_bJQIW93`!>w01?ctI^q0j>54ut|i2otLeQ4$!-%5^RM z?m4wH9f9c%7k83Ra?acHexL9AywCf-JuViD1uqhp|BV3`9)DM1VIes9^xbs>r0GE@ ziTGVJ@Qe|rI<yz|+yLo?62CdL&%X!*1rFUt!hKH-kXEuole>=XY7uTCx{uHkHsXg} zUj>_pe&o=bCj9o)0BHmf4~aG6y>0CS^gKpfCtUx7fjozn=6U)(%Ye~oyHstpnQSOL zZr;trD?73iQh(bUgu%%Omz8%m3Tz;!wx-7Op2;MvSF2NMYHQ!>>gr0WVp(6_H9)^b z5amum);0^iw6uiT*;#gRd3m{SU|@}@sIVxcQfqT->*`&TX=Q9|tS37=J7!A*&vUTQ zdtVTLYz)4-x(d_O6mDf?AoKWf>$kzdwx2>mYz0IP@qc|{qD7RJX6x$gL|uJ73JMCq z%fM#opPrsZZ+AD+!opB~^yrp|i5~?5Yn?&?ola*r&3hR-kG!w158p&ZK@uBl4^$Q% z4Kdw2!?l-2L_i`GLKG8Y(<+q~<IvFd_5p6rf1Q~@dUzOQT%ed3XfIttOL{uQQAg~C z>`-v_%zqi^Z`^>C3~&^Mg{=?v_QGH=@Ha5~__4A$GZQjltlfY%^<ylW&G_BC2;G${ zkWePLb|yI)Lq?-LQ|(1X5Qm3D79WR(t5;zd7~p51q@=`ymcsIPr%qWFaYD$+#Lp=y zn4g=&3}xm1!-r5`xBz)V0=lX2bXpynwOUkN|9={ygKjAH^+8!=Bz}4DfS-ZM$w}dz ziHWtG&`?`tTpTzmD7EbA)3`e^0pq|RI=Z@{Z_`7g(V(%Z2~18dO1-^N>g#K#X#YNp z_w?{HKuhUOO+$mVNDyjcD4oo)1jq!TDD&|_W#X&oXl;X*Mr;pGC^No9xsNx>DR8;p z6MstF55gc4@iV|yRlTjLud@~l1W?7tLlzVS35j!5(sNsLvpskdja)}rs5yHUVlOX9 z{QRC$=I)Ncn>YCxXm4*%8XOr}y?ywwts*2CGJikFXjGMy1$}F)J-9~Gh-SSWv=w7$ z^bR!dpNG`T^U1qJ*@6AIPb-0!0fu3Gr+*$jS`nQ*X;qLI2b+`xeM`&JIcKk3L-mOh zFb*0qK0c1=*;%xvrb142pa}4X{N+F_EH3h2hNGjSeeCC-turK6NmX5U?p<goFc(~v zn+t}^2xlxh3R6_=b93_;rxhW0b44Zf>AH9k!y_a74P<3y#S9M*<Cf<kt12)641Wbv zfAAq{zsiJ??v()nQ2yQi`fC_dtD&Nr;o4-b2QW`r;m(9@{UcJbEC#6H<N^LRmV{N5 z2^GyVXXqbrS)%pNQreaD&ONEr+(7*n78bT=AU{7JESVT;Z2VK=;l5rK7>IwP!p+rY zl1R*LdcCc)qhn`2PPqd~Phune{aom{d>Pe8BEfK@^?Zpq1cv6kIyeaZpMFAVAA_1k z-r)$oqhH6ID7W2C(l$Ivhs<cX7^9-wn5KL4$_k8BFL`-+_Ov_q`wP&2BUrcT$A|y` N002ovPDHLkV1oLUQ7!-g diff --git a/addons/skin.estuary/media/icons/pvr/PVR-HasTimerSchedule.png b/addons/skin.estuary/media/icons/pvr/PVR-HasTimerSchedule.png index 6530abafdb2cc356fe344f79db1c8441b6190a4f..9b3fc7e868b906ce91f580a3f377883280396e56 100644 GIT binary patch delta 437 zcmV;m0ZRV80>lH58Gi-<005{x>8=0(0gFjQK~z}7?U=nv#6T2<zi1E(5d^CR8|wuN zi+u<?L2Pyl%e=u_YA1+bm6y<c0XuDM1hLu9N?V&$E8?+<yFv0ZJDE-5k_#!4T<+XC z_uM%%<4GdJGJjNID>T%610S#AoYZK<+Qe))CX<>k(7b=!m49<}Ul@i-G`%1Qth%ob zj%@(9K(`L^dpSrS1Bbwa-D);796mdeKH|8844<rVB}t1a<q+5c?tqb1DKt}YSJKl1 z9)T}lA9yJszgLAM`M_ly{5mJ8$~krt$9j{rUErI`F_(#TB)v<TNa`E-TakYZoCE7~ zfPV^ffP3Jk{D0;rJ@e<p_mQM-!R&jIz9mJH_6!lWF6Duwr^NrUq@$F;NxPD?>LRh} zauJ5%v~B<B-Y#$k^yBwWU@fBnn?O{cufJm$FBi%X)>mK<2N(jA9A-p@;w{%&c@d-1 z+-;A8jiDALNh*1o%H^2vVCL;9sZDHY?=C92t7s7`yDsclCzcfC6ydL({6>PjR9%_W f%0_Xlv55Tu4}!I<=QAsT00000NkvXXu0mjfetXK8 delta 301 zcmV+|0n+}&1HA%}8Gi%-0006sAte9+0R%}zK~zY`?bbg|!!Q)baZBw`sjx9sVqr#Q z<Pe?NU_cy#ow;H`%>|g@0PWZriLDoawUuCszEXZ&LX^K&96Jb!zBfAWBPaHAY)mfX z8Kzgvbd3Lmp?pd>rUk<vYo2_h$TI4Mal{Mfow3qP?hv~m7=Nd15jSD51!BoK0LCQ( zrXCBBl?_>wF!x%`tn3!?!ASs&37^DVJM1$yJTR)oo)D|*m<@YJh}ZnKOh0|XHy4f_ zz>GJ9*l_jpaGH+{I<ymKJR?M0Xz1hCy;*H%?)j=a8;V`q5-Yu=3)XzVG-J;D%uab8 zK?m#)Vug!~A10=dSgH0GvG#xTey|W1m*ChQD(|h{Jycuj00000NkvXXu0mjfXyJyj diff --git a/addons/skin.estuary/media/icons/pvr/PVR-HasTimerScheduleConflict.png b/addons/skin.estuary/media/icons/pvr/PVR-HasTimerScheduleConflict.png index 69610505f971ddefa78df09f112cb20d0510609c..ad92e46f82efa1405313af3e8277660a2af49c37 100644 GIT binary patch delta 1601 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 delta 1591 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 diff --git a/addons/skin.estuary/media/icons/pvr/PVR-HasTimerScheduleDisabled.png b/addons/skin.estuary/media/icons/pvr/PVR-HasTimerScheduleDisabled.png index ac8051974c7bcdad8fe93776d8c43d9e7e909ef4..278e7cfaa86efc4b51c656ae55247081237a2721 100644 GIT binary patch delta 354 zcmV-o0iFJ~0{jAyBYy!yNkl<ZNXO-ttxH2u7{-6oX)xG^!DwADoHY@yV7cZM(*$8N z>0huct_(CRYO)wiCk-q^5DxeUY{6n$S=wM1KY|K(yC1vf>>Uhz<KgAuIp=wv_q;hx zs0?$}3WAl7oG3>mBg|}h!SWFWZ534uRWm_B{lioFDGIv3*?$IC?AZ7$&2@(yP{MNp zo4Vc>=RiZo8WlEq;Yk9lNSP0sjOGVh<$^En=}IKvh+X>JO2nza838v8vV%QPV?DO( zgaWU$ZGt}LlxG?dTNWuZq{-N3I3BY{4G)huTC^wP@<=aZHuaxHJ9OBliq9nhYoWdt zpHr!gn)>&eCR@beojQtZf{DIiOvp`EHSgFL9ziolfQbR~liE$vkL5dzAWO#o<f~io zuHmp_EGaXU5lp9A)LsB%vwMiXSxy_dHxp&{4RgId{K3~;`Tzg`07*qoM6N<$f;H2j AYXATM delta 284 zcmV+%0ptGs0=5E>BYyz>Nkl<ZNXPBgy$-=p6u|L@M8aedgHf0~g3)B)5v+y|VqiBi zGa1Fy`7d2aEFwJ*WvQm$+TKef+%w*uU+&kry@ugywy1^iR?H~aehEYTVGB7U1w$Q6 z9=s`LO0N<|jWzAUSZE|;go_r8CTE199&CxQrK$m=hj6E(uzvwwTA4i#Go{`1(nbhS z*MQOB#Ld6-FEK2pRAR9O!Y<mEh8+=vjsIA>OYd-{o3jGwb3hO*2G5V(m{TfHPPAAd z2)95%*D}{;w49l6jk_CyRhtnDO)?Lbe8D7RxzAZTrFaGXu`k2|4;MAe)`^93KZs@j iqrV5!@o>?OJrw}oo6WZn!^d*~0000<MNUMnLSTY+c7j|0 diff --git a/addons/skin.estuary/media/icons/pvr/PVR-HasTimerScheduleError.png b/addons/skin.estuary/media/icons/pvr/PVR-HasTimerScheduleError.png index 1ea58a639367c46450f563a2ebc14c8ac639e2a0..e09bfae0998637347b9ac3fd62cd6ec869c19dc7 100644 GIT binary patch delta 1678 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 YgOvtvS*)-&00000Ne4wvM6N<$f&;}lo&W#< delta 1508 zcmV<A1snRE4fP9<BYy?FNkl<ZSi|j?YfO_@7{^&O+q{5smv$hC;jZorH+2qi%a-|t zE!mcM`LH<S$9*uFUlixiOmG+{shV_y(F-l~iijeGW`&kgXv-zsoQRt!1Jp`YF1ED) zJ?|?x!vuv@VarbPOW*fB?|II@&-0w;oY%{1nud99UjJHQUVpE+{V(jlC(P+|Vmg>M zQ@<yeldxC8bA`>t?Pt!M0mEFz&YLi@xtvfC39nq(b3C$V!uGudBaG~Y61EB3yBCr$ z-h|ysV%dTaMz)eCY%*>Ayd?iKCR<F@5^m!Ad0UmwnCy!QdpC)I1tE-VaEM#PBVyM} zsbh5CNqkGZIe!abc@wrYQ^&Qow0Ji)HARZV;w^;&!PfHf^7zrw(K!igernrLPFEzS zBs`N>4~Pwu&y7aoy5W%#(Y@i}c57Rk?XpJWxOnN(xXxs<ci*^yTYY_~GZ->v6-s)x zov=J5kn!TDQ$sO@lf)vOPPcSmaPY+4J9i*Hch2=ya(}YpKuCz|yO0p~2@c1d#o@Rz z6BCht{5Z@F4X{|P)e?y$a8@}+eQOBeQ<FcMVN>?b$;nwYHa2E1m&<V^DarLiXsBBl z6$Noj3`7&K*U{1VF+3c+#Kdt`ULKlSTL+7!(zL$`dyx&9-aA0tpH3L#sYecnS;*%* zzu|J-!hhIUya<dy>9J!Y)|Qqbo6R<RVb2L81_lOlD^w~lqNH(g5RzAllae4<yB1=) z7m)}CG4o4(iu7~{A|lwnd=gj5<GC9sv}^?DQZAk)YG`N(cH(GiimND&CXb<s-`R{Y zhXa<gXCYV}2Ie_~C^`yd85wBp?1C;k8=^H4kAM48lAWzqE3Cwv>)2FbwEDz~f&v#W zY_(fL9w?$b8h!W>cDo(6;bE94k0eZfMMmP{?%inV=zy`#2&35y_0F9TlE(_UT-2OC z4GO7e3T$(8v(Jdr+41$JO^%Y-7!=3HK}sI0{QNWAz5f8cy}h`4tq0m8M^KWSjM`sx zsDG-eVo^&Z5Rjk(e?JuX`Jy0vHQIW5JR{6vu|#%VzivCs<+>E{@sKejB%nAd8kcwN zLPuLWm4!y=>rJR7FKILyR1#`FAHtv@6#Dw2(BB^dg1>kX+8fQD5hfIg-ZdDF&Qn~D zTbU3KIZ;A+Bnl3Kz;`9o>04ni)S<e%ntv6gAy27(%7>V|BOp%+1N@)x^RlIAEiLto zu>Ab|ZF+-#oJS#1uUiK>i4q3{u!#Kppxn3-H8nM?C?<Sby#bf@?}wQBCf}PxuzVTX zl}gVDD=RCDZ|Ul?p9%|esYsM0Fo30qa_0PpA3&$oKK2w*T~`MK39~dcL6?~c;eXO4 z^lspjF@>)$Mz`5Rp`_a5|66~5&#AODhip|aq%@I~@<pAJ0=>!fS56fQP`>>mb{lG= zy!(A{5T>I?A)y>&$1nmZ{5>~sdhFQq+STsvii>={g9(K!C=iOUP}J7flc-8Ir%EZ& z<q_d1Aum>ZoPq0of1r;x@p~g9(0}aM!H$voc*A_~AeuWnJ$D|{YPFxVQT-8bSnrh6 zBr+0kVapa&>1x=LpbS|BIeCQbqlB+~|2^1-?6A;IA)^JtJePAh&e7Yq$Fs7sWF9@i z>J0{MO+f*Q$ZJXpse%NY-@FMbs`?7bA0>rp66U^QTRP<Q2bg==3LkXl=6{aT;us@S zZ+rBEm&fA;Hd?K>Yk0hoViKpK310*SiK^SX*WNoYfRvP!eKQ_$=lUg{lbxMrY-kv& z=kr~qu`y1Cj}J<zh?bHE)eOPGkFV)hxpD>6Z*iPgPUem>qQ2a-=LDHpG~-F(pU~gm z&#uB>+o|H`orY>{B;-rpgk5TdH&i4>zT^$4Dd$=aXTo8#xoFjnrKYCtdm`#(31eW+ z^h~AK)5>hUb`2ekjp)2^0avK1_gE~nD*Mn(!VeufRKn)el=dfSqmCKh-3Dm@0000< KMNUMnLSTaZIOL@O diff --git a/addons/skin.estuary/media/icons/pvr/PVR-IsRecording.png b/addons/skin.estuary/media/icons/pvr/PVR-IsRecording.png index 6d9a7069217d80c8fb192101f55d26cbc229ef0b..ab31be29bc31d7aaca31c565333d6fcd939c900f 100644 GIT binary patch delta 506 zcmV<W0R{fR1d{}iBYy$fNkl<ZSi|kty^9l36vp9aObjWqpjH-YR@oK7LJMJw{0DXl z3u2?S_Ler<2wGWbAr@-4pq(fP3L*+y`O)~XHfkX(D@2rav#~jXESt=oWQKtF!a#;3 zcYf!dbMAX4d(>)~q_&u_CJs;^<9{_|alejUT$KT2XrqHe*nf}hSi={*!XjSaTiF1! zIEmvp7}0N3A6-1g0`6cnH>!j#n8(?OJO#Lq@3?_$cr)zBwNs78u<uXf0uB#|dQhqw zID)-+hfkw)!931m-(SU6L^T}6IqWG4FdN%GJ!Y3>IEFK5rUGP9aGen&-|QJDFqaC@ zj%u%$c;2*QCx4nL0G(*Hvem)PJb-$^iZ7yR%;W*oq5!MV6n5nSWO2M!p{;18)IhHy zfc|D{Zv<EmW2r(vu$%|b!^@y8TPs*f0ay%ID_gHq0EC}EDR~#x@GvC>VwhM6OD#*! za4)3@|LAu3c^Mti#Vx!q;)biZj{9S#qK7NEIcS|>19Qn&yu}V27{M94xP*)NHR?q4 z8P8*Z-Po2`a8Gd=SBE`3btV)xIv1AO31gXx<Fy}$%u39E4{#SBQqq?dd^Kx^rOt%0 wv?9;T(c??;Ik|X?4jn)7F|PmbhW`e@Z;MW3w+oP;;{X5v07*qoM6N<$f^385)c^nh delta 552 zcmV+@0@wYM1i%E4BYy%1Nkl<ZSi|jCJxc>Y6kIW8_x1!UTM^VoYt!23PY??mZ2|#1 z8#^sU8~qR3L<_-SqW9rPYbDq!f~B2l1e55@85=phz1&`*xbR3Iuy1zWdowRW{GZ== z&V?wP2qE#Q7*!#Pi2huYz6sG+mvYooYSDL`tw8IeQ0q%XfPe3Mfl_O}lyjaCL)Md@ z2{GYoy^cgTq3gE7!NIq$5x8y#zdr|#a||F$w_wFea$Go%xIlu}xWsWl0JiBXwd)s) zlbMp^q9ss#$4gC;e*w}0@EXch>iy+f-=|F+!^R={VkC9VyR>(*0O*2f<E~9ki*dJy zY&gT38->;np?^{{MgTZ!QMcPl{>g;aJ4P%3>Ed^zma!Dqd|aZ!p=-BoC+{-w<ViXE zBLK>4q;PovaNQS<brJ*0r@ZC^pbd6c5&?jLFed;l&v7b00-&pbf&H8SG+|^5NdWrk zYBmHY2Ee0{EYniyYSsZbP6JIooCtt^4x}}kEKpV9j(@AhOG_Z-GA-4j23$3y95*(j zs2Snsfdxfm^Mz8D=`pDsu|6;`6=W^N$4;<O?R7iC+)jhO1Bys5FK2Sfd#0_3%LM&A zvQ)!{W6h!a<*Kc{kP1mZr=`->Xe`wBkj#)~zLs<Dx~J@RH1SyKnwtWNR&%%-jfL6{ qb}Cdjzada4an5u9fAD|)+<6Dbpg>W5@-smI0000<MNUMnLSTZLV*=m+ diff --git a/addons/skin.estuary/media/icons/pvr/premiere.png b/addons/skin.estuary/media/icons/pvr/premiere.png index 4c6c11d8fbf1a704888734158e27cb189507ae67..e69547192358a654c37fc55f93aeed09e3076dea 100644 GIT binary patch delta 197 zcmV;$06PEl1^EGx83+Ub006c6H|mq@0Vsc)Nkl<ZD9^2tu?>Sj5CjK=L`Dk)AAT9m zD1rjM5)uk<Knrx>fJ!KW6lt7*g^?u)eA(@ebi2AtzUBoyjzV?<R-iZvITw=yiRP!* zkN|6Ebux;$x{4T`)Y%%bby4+cWoKvQQ`N;*5V5r1kYL#C-coh|F*q8{%#4nPUB5AG zaqGk}<lt})d81~s`9$3T#Kz<weo>tE=?uI9X$-smHW$pU00000NkvXXu0mjfN~KlG delta 706 zcmV;z0zLit0rUls8Gi-<001BJ|6u?C0flKpLr_UWLm+T+Z)Rz1WdHyuk$sUpNW(xJ z#a~m!4=M$%AnK5zI$01Eanvdlp+cw?T6HkF^b49aBq=VAf@{ISkHxBki?gl{u7V)= z0pjB7r060g{x2!Ci1pyOAMfrx?%n}Hwa7H9Z5+^a+f2lwVt*>VDt5f03;hV97eg|$ zj5$e)qviOzhmWs!5uVlh+@GUI%~%ZZiNv$aFm2)u;^|G>;Ji;9VtH95J|`YC>4L<M zTvt4P<D7F@;F%#anV2UI5sSGdR+^aQO^tYpIIL<q<qIi~RnA+SwPKmo@5x^n$mlD} zT&FpV7#6Vv34bCKlu<wtHX^j@q*zGMezb*u(Dh5?Qpi;RBgX<t&>*{h@IUz7t(Bde z@R9;ip#8;hK1P6!U7%KRobO}Dsht4+XW&Y2_)Aq_=9BbFLkk}P!ENB;x}hn1z~v4w z@MOrQ>`H!`LN*J$pV2p^fxcUyd(G>uzK_!fAW2=tZ&rYVLtr#V+3PLd-PzpRzh~<G z{QyX?a+YNnmbL%@00v@9M??Vs00IC4-mS^800009a7bBm001r{001r{0eGc9b^rhX z2XskIMF-;v6%QOF=ll5Kks&C5%Sl8*R5;7clf4atKn#WNEF`+DKtahE=@^0mn1qG_ zD6&E}NWml-f)?|oz`zB8NJNTQvgF_A^Rv!7RktUIJj}fMVc4wk2LQd9(H?-flOi^$ zL?r*T36UI;a&#a8o{Z80nNxL6)d9Wf<Y!ic^(0fG06G?F2DktfP?_Cf1o;jqM*fy* zF;IsfKx!H`*FZe=%>V&V3f%3v-ZZmVx~@Ncg&9e|SV7k-Y>~)F+|6{K#9y7U1o90+ o-vEMm4wO#y5B@@QkZ+FwUI8Abo2V5l1ONa407*qoM6N<$g4^{!?EnA( diff --git a/addons/skin.estuary/media/lists/focus.png b/addons/skin.estuary/media/lists/focus.png index 007c9c995977c1f6e048925f5d20d424c4042bb8..353886878d5141abc8c6dadb01b0bc2d32675e10 100644 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-5RU7~J3h%XFs)*|^5(~M QZ=f)Pr>mdKI;Vst0Fe_9JOBUy literal 187 zcmeAS@N?(olHy`uVBq!ia0y~yV7vxo`*HvY2D{}_n}Af4r;B4q#jUp&8F?8PIG7dg z@1NH=&zW8H1mAy`)nQu}iLTAK+J%*lx(E@+PKRnk#BtDBt`PaGr#qP9l^lc^-TO5` OuJUyCb6Mw<&;$UM*GWbI diff --git a/addons/skin.estuary/media/lists/panel-mediamenu.png b/addons/skin.estuary/media/lists/panel-mediamenu.png index 2e9f61edc150c07a997c5a66cbdcceaba046cece..090cf7d0b4ae5e493b5012912c1c32e6e0446b45 100644 GIT binary patch delta 90 zcmcaAkTpRpgn^NRg@J+Lxl~#LkTUdiaSYKo|MsjUBZC4D!^Xpmi$1Jxo%l~9AvuR9 u#b^1}ZyUtf9R)-V7tB%c=vt$Hob}5DLB<nHP2GW-89ZJ6T-G@yGywoAr5?io literal 2133 zcmeAS@N?(olHy`uVBq!ia0y~yU_8XYz&wM41t{`nwsjc;1BZ;Ki(^Q|t+zK$`?VM_ zv|Vi4@qJ;<|MbF@d|Sfwm<m~Z7woWDK0ETPitoNZJXeFI8|3%cv|s;lZT|Oy)635; zKds&-l4SM7JnK4-wA|;+L(cWDYF2%$S=Gd`FrekE)<I@w5ydy|H+sAy>Z6?mofy9= z9Ta8)3e7evIAtI+cOggO`iWopqMS739!_}hC+gV)5m%1tRad#9oOUE|KYzOV+>fdU z+oLOj9v_HndbmecMDb7a@>qu!1*d_;0*VSw1B*KZ1QZ7jyS(5U$lwRrJ+Q$KvVY)$ zA7-};M~A?`VWkq*fsTfe9tEK2S7I4REVzpamKq0&9=IF;%Y6e81@#l()d(qyoGY-H zCZH!M&Zs7MsMOkG$qW~crR4_Sq9w#7<$h&oIo;^qAZ={(az3!0jW*9q{PQaxs6w@T z!&%97+b;+?Ic}GEIOn0JVwb=+<vB(@-QmUuyCpTG6U)C({IWp6o*|~SM1O(v+~dIJ O0)wZkpUXO@geCw`+~-^X diff --git a/addons/skin.estuary/media/lists/panel.png b/addons/skin.estuary/media/lists/panel.png index 0140fe0449ac603a12c28effede6305a2c36b1a4..516c5d422ea47dd23bafdf6dd0b19ac4a235e208 100644 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-5RU7~0u^iwjGPSL-%B)B Q0)-hoUHx3vIVCg!0A8aEyZ`_I literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU0wmSG7d!(}nw~C>As)xyo;e8QaIkDRr%->u vqG`o*whdpdtmbHSS{R@q!qw{Z@GO(+Ew!0WiBivj>KHs-{an^LB{Ts5-3J}5 diff --git a/addons/skin.estuary/media/osd/fullscreen/buttons/record.png b/addons/skin.estuary/media/osd/fullscreen/buttons/record.png index 703b3bb22b10ccb8abb436842fd3d738e3394a60..b8bae56fcdbc9b0ba7b2272c2ee0d18635d6fbcd 100644 GIT binary patch literal 884 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV7B&jaSW-r_4dww@6be<hL6WH zH2xTh{$)&Zb`e`QX>zNxfKb|%3En~%^(0*<K9p1R+Zy5&5#k&vxa6#75N~R|gT0z> z(dLJHoGv7ly|;W{egC;+YxVv0-|KcfpI7|O(szAS)P|bdkGvHagc~>yup}@UFwS7` zVNj!hE9M?;{L_ARW8MPx8_aLm${1g7_x#IQ!)nJU-x!&|7m@izeA&vze9_x1GHh)P z$_KvH*Zk3cz*)dOgYnuOmL<XV2K+XxY-S&y3CkG^%uAND&}#UR%|4I$vO_xW{O!UO z67zP)G8C4{RB(T}R@i%Q!?6Vnk8WLyXI|E@P3T%S^Sf5l*(?EnXV)`JtW9_un4nb3 zvXXfR-<;J2e;*n&GiYT9NOUIj2|j3HIQ4&<z2rrk$t&N<F5ya8v_!u~dadahl?I_# z>p!o%_y3L3H|_=h?*GzvwYmTF$~Uec=7Nq}at~LR{?<5W&#}OdSJmM0%O!R}!dB8Z zc>XYKbG3V}wV~O?Pj&eh&I@x7%=VwL@wTtS+t-EH_AtIF1rj9|+4mUAtbjz++^dfn zrWrP*rk|BQP!1x}K}0o(ShM*VZ$jTA7w<P~+ag&59)~r!bDsG*qeV~4_pQZ&-1IZj z2Y5CJe5~O<V9a=Bz3Bt5BcEecH=aHbY$NiAp+fLsjK1bNFE@s-?{x}g^J4jo6&s3v zG0o}Bn3tFk$neyRwa4AyxW&<@k2n_m(PJ>zj;S{~)4;ImwzdU-Pw0kk5jWng=Uh<5 zBHlRpKoI9^UB=~yW}Uomxn^RWV2St(uHDIB?_7P&^v>i|_q7ar&gdmOyuM3pwN3h= zXOo`4>i)N4#trvOHpstVez$q&=8n}5H`X3t6tX$7d7{)$zlHVJoC<edbL{Fiv-<Hn zY~y{6Jkj5b6})>m>+@d}F(%B+Wx}2C1V~MtlU|$iuol#alw6t%%uNiQu6{1-oD!M< DNG4}C literal 707 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrVCwR8aSW-r_4dv|FKI`HhD6)h z+YR1Ndb{?BbM9}eNqZdlYgyiU+AO<}e!=^#^NI!Rs(Y7gHsBW8aHS;8D0$yu{zW%) zPSnhJ^X|=?wPCAm-{#$8Vc5gTz`?+%z`)YLAi%)nKnCYQ>0gQ7f6JRMsF-liZLe#~ zb3T0|_s`wR8XxiPH!J2d<j5bWlR2^ZLUP8}Q*w{D#n$LCNHH1k#^xEk(7$Sby_(?_ zdx1ohip-C%VukVyAsi2!U)Vi<!2k8P1=|8ShQH50dp0)4zkiZ{pO-;{d4YU`eze4& zh3oYd85Zy~{1jua&)_>y#vl`J@WWZ2QG@%zh702R>fa{*eZkPMhe1q~_upq{7X~hd z8DSPboP!xYG2QU5Y11zaKOD%&P|4t<CHH3`UqcOppEN@Omjlqcr#F|gF??D7k)h!W zL)4G&_9l!9whfWa3~3BY*b6QSG;lLqV&0L*aln+}CF30vmIrJ=b<=nmBbXQ%80?rB zKBx*Xv^O$X2(maZF~mAESTV%#FeSL_v(MitGl$=STb}tn!wGBEH>ai;Om@m<El3G^ z@y?I?e_m9@O=)L2t=)zxJPpx@848Lu81nep4=@QbOo>-|7JN`O$u3%H**Vum$37P5 zJYRV4hEm{Hec|#cKYAWM{XE%*VawKlC);_~E7={*)fM^4HmytcXycrtGhP3F*|ht@ y`-uOa*Z<gFS@Y&Z(8f&>TgZ)Rc<MOyh5fGKn?%i+`x!uQGkCiCxvX<aXaWGq#ra?W diff --git a/addons/skin.estuary/media/overlays/extras.png b/addons/skin.estuary/media/overlays/extras.png index c4572e3757c5187366efa5431a11aedb14a4c293..9813d1e77a523db7c3ce17c310fb7cf13435a784 100644 GIT binary patch delta 516 zcmV+f0{i{h29pGk8Gi%-007x@vVQ;o0oqAKK~zY`#g)HGV^I*sznd&E$`xTNK|2Kz zcl&LOU?FDl1)`0Z%2yF>#7EfR-C9cpA%(k}#y)|VYOO4cg$nU!k#ql?bMIBy1olid zbLRV)nKLuTL!+I-h<JU<p<UIMzG+dPCEcr|UpiM)uNl6$Qh%gQhpf|~PSN#u;Y+jt z=yJd&^OPtt&n5?S0cf%G9zm4{pht~DDzXAKdcXtKJc24Ozz=+sBR&n_B}tJCz7HJX zS&%8FEZyS>=*Nk~rE3AF4qRMv;aEIQfmT?~&|C+4OgqlA3>Yx$Sf}X$dr1P1Hc(6D zybr*B%3cH7!+&Uph6>P4zcCj8sU$>^TcCn4t-K}arv^@sEoe=Ww5A2;W}v{JOax6} z!?2(6-Z?W~8$dHo;SQKHS35lB!XMc-UUR_TnBW;GMHw!0lL^0JIr5nRp7IDpnUfU! zn2CgkLy$}%ls^{yw{sh%Fnoo!q!p{qzP?!cSNWE{B7Lb7Z=`EU>z2cJO<MY-s+PWP zP`Qm1n5|HuVm3jL-ALK3T&52M%pk-J0nn$M-^$@I`O(~8v1=7=m#gSDW0=BmZluOJ zEgp@maSj*C;Rh7C3uO!|Z466w3~T)#J{FG=zc%l`|ITk7JJP17g@u6t0000<MNUMn GLSTZ`0_<=A delta 845 zcmV-T1G4;+1lb0V8Gi-<0047(dh`GQ10YF6K~z{r#aBIxQ$ZBXJ|$6Eu}C9mry`=( zYU6^H>p$2|wDMmhoouuc|A9*uQrfBrTZpFFCRn@;n@y3GBoNq;kniU?<Go>$Op-y7 zci_CabMLu#=FOYDdD)1fC=n+DX=8);1hUy|FdB`vuwpsz0Dn9M`$F3E<*1=n($)h> zD_JNM&W*?8my^ll2z@V5%N*vS>#VDEI+gu?|2B#hVH?0c26u6nPrKdj^-`&nBx%#f z7&RiNzQ`Ido%KnllSIL76gY#;LMOPRolGVZ*cun9p_V!5Gxv<OY>iE)QwATvW`Pg& zdVSN5`;a-v-G7748iQq%Sa8U)w_!8!?FL}SZPseFOQb7CGM6=AvsT;nIA%KBG!U9+ zjyAt)a1xr&;3rp1DU-;<X3gUK#+(sz=c1+Xq}gm<wqx_`bUIg|8<VtM8YOG7ChQ1f zys%?aLuMMnrf5g~%LvUYv<6~3bxNtpn+rLX`lo{7aDVt1Ed|FuzhCIM^af%(bxL`> z71)uCW&BeCg1cy0?6vxqvDfR}fNluefHr+FoqDCzrk-ONpZ&^w#0!F7OiuvOM>aN7 zsniix@jPq;+VsJk(Mj$UR&6sDVNc)|FN49LWO@P=*#|q)^#*gmymSq$+Bc;&LPN}s zhf2g(tAF~G`2F5N{1CPmsAUe!nL1$@rqD9pQ=iwVl}sjoR;$&ANO&)<rGVyLxm<q0 z9C59N&IGgig*L*$a!q|MLiYo$6Nr6B<IE$aHg!C{jn7_zkQdDSuIV|@Xf!s_-22mW z;=X8{uxfL|8rT!~#!C<cPfX8&bUOVEJN}|{7k|JQG~3c$fK}U!WuQyp^CCiwzX${B zm_yPQH~rKs<gR@YVfHIu#g$6s0^)i~?zdX4D;lFM12fj9R%6PwuVNCS>0vbg4It<H zS7@Hwew&O@GoR~q7?H9}dK~ZBGyPk#25Z4)&DF;-Irl;G=3gcugZqDwC}aI3Q_3Xb z`9>&J%HxRVvs}qdgFA}nkBLn#EnXm7D4qikz*Dd<q)lIr8lOj{G&?*_cpb<84~fKY X)G;L<@fb4u00000NkvXXu0mjfuS1@1 diff --git a/addons/skin.estuary/media/overlays/shadow.png b/addons/skin.estuary/media/overlays/shadow.png index d66bc61da90de643d5335f968964877baf05563c..e77cd1aecd3ed1cdaf3ab7bf08afce68f0525c68 100644 GIT binary patch delta 336 zcmbQt{F-TkO1+V%i(^Q}y|*_v<~19LxIIixRu?f1KEBWU{(o1ik{2Q@EIlVLoO`<H z-m1{A^KTl(Z<yN1?WMVNN|4vzhfRf{3PNXI@6Tg4(~_ReZy=C)!esu5_srVb+}fq{ zXZRd&InAuwd5m#K&<u&05j73G>2j5&`(n=LguJ@9DdUD#(W6K8b-(rJ==k?9Q$APn z?m%D8&WjgIj?CO_{lRD3ofjFmbY{JKwQ<tho7<H6j>#7-@%Ui%DBjnBkyRuBgZOV= zu-VsyFYV6NZ*1B}Wh1`r{}A0c_0S7GncO2OOy_v^d~k6uTcFFc=ku{QtT8(;PL9l& ztLEz|9A1^TrY?WO@*>rpHE;Dr(%5cGZ{ZgfK3O;;LStLvqFR~XhFu?|j$f1i^*IRa c@#DLg&+-SxtXs17Hv<rOy85}Sb4q9e0Mrke`~Uy| delta 376 zcmaFOG?{sVN_~{4i(^Q}y|=e^=N&fSX?+;KY045e<I*>~zx<6~;@s}XIj1{yrE}Fy zA-2R@ODtseNNlhRFv*?4*L%CKqk)M_V*!YGV9R{@<K>6q9B<>(KUhy}^eFJkWR{xh z_L-4QNp|Kt;Y!O5MutX#{=&Hjjx@20saUZ;a7jGfrn{KGenaxP<m#_$*M9I>9AtaT zC*|0sy}zcP(OK<&SuDM(Ji&ikNQp^qVcI*sIg@oob6+=KywP_qyWrxsEnB&N*{3vL z7ki!R=)lM-5`aOhXUo~vd2D9lo>jWdQy=l^EZ_ejx^e2EW3oFo3Qp$Sk<OuSb8&K^ zY=$!P`#l@KGx@!Vm@FA-;X7;FnuyKGpH4rRb*k&hr>*+~ZW!M%lIXkd@vxwyL}k)r zgN*_E+B)ARJxCNNlyI%+O@8e^`y)e~>ARXob)OF`sE>fU<%9oY=0oApa~B`3oyq_N Mp00i_>zopr0BEVHlK=n! diff --git a/addons/skin.estuary/media/overlays/versions.png b/addons/skin.estuary/media/overlays/versions.png index 3bb88f1290a99c51a373404b709e4a0f69b3cb56..2772f316e5cdbe3a2b26b43c025739753c2d0d96 100644 GIT binary patch delta 219 zcmV<103`qC0_*{h8Gi%-007x@vVQ;o0JBL%K~zY`?Nvz*gD?!7^cCR&@El%BE<CF3 z35iIl1gbnelxBgnY6T~XeQ@NAXCf<c6tT`u*gL^k(a?gyX=%nXg7AY6m2}LQbVK#R z<jaio1R)J6tt;YPS4c}r^OnG?f{>ncdX8|Kd<rB4_gc;i)l`XS;Zfcv<&shTf<Q5J z6HrmaY>lMzKEMB)co6&>0k2cEz)k<au-S87Jy13qX&VgH#c6C(Y{d*^K&azy!~<VF Vt`@Hd(0Kp=002ovPDHLkV1j2dVW0p2 delta 343 zcmV-d0jU1$0p|jc8Gi-<0047(dh`GQ0WL{IK~z{r?N&<;f-n>Wcicb^Ajk0<-pMoQ z#;x~JS0<W30wLf`(?@*L;y2bsCmH(Y^|hT6Qkr&MmxRQ`w!sh$d}N@m>m1+E2E0J> zV;YcstU>J@dI)sB#j2|Ewrx*{p8{(+2BDfq2_NlZ8}KYS#D8nFoOoLl#Z2<|WFKn~ zvzB92zY;Dwc53p-(M*R0rjql~*3dw|0ihCP(=-bd?nPVIuAK_{u@5Lvc_m0JbU;gp z_U%+~n;WA*<y|MCBvRYnYk@ECgR?#!MA1Mr5Di2F|7QUIhNBG)pn2O#%CelA#FsHJ zfWO}oiyo{bc2Im|S+;_SnaO+;{2Pw(=NA$egI>vebiA2Su$E&Gs(F-nl{asOS8pfS p#~RemAqR7$e1t?ihyoi(k}JHf=59=&2mk;8002ovPDHLkV1nS)nWO*! diff --git a/addons/skin.estuary/media/pointer_arrow.png b/addons/skin.estuary/media/pointer_arrow.png index 3db6c8e042b44c34b06e7a222c7df17df47c949d..d4dff69ee070522051c5b66f54891b6048b86bc0 100644 GIT binary patch delta 1537 zcmV+c2LAcU50VU!BYy?iNkl<ZSi_~4d2AF_9LGO1v$Nanu@nj@LXpEtfso))0+@pS zp+tm=q-s+^p&W^!B@jYWFbD`p>RAke5#$hslxjpl5dp7+LkL%tvlfG3igJ~*-JRW; zdE+0Qo!#vPTi*L)r#tU`<~!f_JGL?a5(+9wq~QI1{LvTaOn-*dg^r|?N-`c?$jC?} zFk~AnA)_EZ>fN*I!O9EE@mdNgcu2yHiVFn^DG{^`NPvWl3+3K>k#)LEtu7zk6_qr+ zB;m$|f}9vywm%yct5;nrHPq6l@PoHT-iJaOUc7iv6Gg{cq54&o0DU=3-%)Fp=l8=! zI>{vA#*Hfxbbq{Jm!=9#U?{(mmHXb(2?LPvk%Bi-uOo`>$k5ctjgeH5IcVv^1tU=D zKnh+ww%d1vjw+UrP;@1F>jf&gKf7ek>mzX4=mb>9f@Ks#iM?6CO1g9}Syu5hZhWNL z#~4Ak8QHRPB_SajiWPUegcWpl70p;R1~+M>kxUXQu7BGfq#ScCAz959&Z3O8P7|kY zdLA#Sq~gUJ=e5)}xN(6b<mOAtnTtCkf8y4c@RAajG;N^cL9IOf9SSfy=8gYu8a|R8 zw2G^Bbi5T<t)Rc6kor!e#{5!@k7Rsy(zM~V%pH#HjNyVWH}_EKBbg-G=r&UQj=^w< zvD9?T$$!~2XJ}76j%u}K)d_&%Bw3v3&_C<b`Qr!Raq_W6LlOc54CdEPS-sw$TQCqe zK9ZXxjnr;1jAUne&#v#3%^ZSC2M4XhquT+7(QN6|z2p2@rNfX(BiSiWF^04Y424u= z+|z6B>=mQ1>@-*7`(iu6Fq@@h^q5rq@iXXnaDSsXiti2#3z$i2m%<r)rvi!-8358< zfng;jc)I3J-ap;$KzHwPWUL~eTHoN8kI%r2HR3M7PcVf=Y@h%QCB65A(jFLgi)#k9 zK(gA#Jx&!T_>Y_HWGxGMg#yv-T;1_2U+%hsgw;rChvK)%r>7xbx_su?!LPqrzovX~ zS%2}oc_qaUEItvyLf{CCfUU--wX8VDx!R9l^MTHl{VIoT8Mn1)TiLGi{U4s(dI=Q^ z6Jc|jSs`T6w6M<PBKvvL{%aON@-n-d)Ds|#KtaMn5W>KW$ZM+DLMS30`wc7bS&CKU zEUThNuaaIg5F$u`1_A^L5yn6u5Ruc8V}JExy38na3g;Qi>FQT2&s2|bzFmu-ya#a+ zLMQA@0}~U`{P3(suxdo&<rCJ@jUBtkZQXoi$H`PG?1lT-OZMQ>zGw&x40Lo13=G@h zIIv|aHaxmakmoo{5w+(lD#mU)_c#8}k6x`A?`YnD#fy*PMqt^mADu0DEHDYDi+|8R znQSDBW8c2AXx{huxz5cKHHS}UGT&ahjpGc=Ey=<}v0qO(YMUt*m>7t&7ftsAL8E%d z8xJqpT}_Z{T;~RLpZydNlN`<K@lZIzE|k$kjtf;e)(ET>7i(8vShwueyp1%_zzyoC zqmIDlQzw7OVw$~aC8rrQXnHon?tjv9>+RpdYSf%v-f!N!r?Bu-Pd&G&r=A9GZ2wtj zva_v<l<xUC$T3y8#V&0DCME_tNq8~w(-1TL7)uUT|M)0R+1nO!m4|X3%0RUCtFuYC zVW1H}BZTJ6AOSS$cJEPQG_R&}_G`n@?0=7>+k*`Z!i3S$V^Sc9SoPcGBY)2_%-&Fk zNzRDgDHyakqE=u>rD+FzB<vd>6e4`C_Q+l@ML;=!P>$xe8sxab)0*PwjK*c;Ey8H% z>-JnbQ@}M!>2u-F?{+Mny0pfRuyd{zFfqvZOcDqTbTrIu)n~sDB79)`oAb;1FDUqW z&%YRi?Q+>FxaARv7o9Kx%zDSxjUF<*IB$K$5i}&H{8-KHxP1q)z;-^2MrhyVzpeoS zKMe#4qdSVNM8OfK!-UWXqj3vAK{N*|ns2+4o)<;yG4UNDgl^a2c3DA__|ORvB4kIp nfe~NT5&<_wM`xqj79;t8E}^5Xmn)(C00000NkvXXu0mjfx?Axl delta 1980 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 O0000<MNUMnLSTZSz_+ge diff --git a/addons/skin.estuary/media/pointer_click.png b/addons/skin.estuary/media/pointer_click.png index c7da4ec721c49e9236d73e94ac220ef5243a5eb1..e708cf313aad993f993adbdcc8b68b656be60151 100644 GIT binary patch delta 2215 zcmV;Y2w3-#6tWSJBYy~aNkl<ZXx_z|c~F#P9LHT)mt}#4MHUE=6$C{DIRwE(F+A`< z1p);TMLZC(JaR0m@kniCG>_EGBP+|hG`uh!JG7~$n(Ue()5aS8rzR`^>7T})e!sWx zqYv*av<u71%y(elUHARGzvp*8kKMZ6|1pq!N!>|iQUJ-nVt?m_lBwI9r_kSFq#h(Y z$wmqx1(GZzKa!E;0~xw`;l~Fz^^>q87CxUe?|94I+7nmB%-28NL)W57k@y{fhmhcA zJgl!bpbfYQ-_V~F%n;*O?9HFwbWO}X_MPD0(_guCASQFfKzc8h6i@1f2MF~xv;;Q6 zx(r~;U%X+=f`2nVi779h5moy>67^?)rr(#(MI~nsrT6=hk^tWm?h@oJXz9OxxGDQ- zcy8^=2@G7a@of?6=qJjyoD>Vs-4fIHet4-*?)XXc8MbyhDM5j@VwJiSS^}Hl4!q1! zLuNfyRCnq}QPg-u&}(6_*+t2Qw>0SLH$MBO?;}O!bboyi)EB-Tra+sz3fh2xGb4!D zT8_cfmzGIjgO9HUZ3?i8u}|;RpeYbrvMLrWq3b!MEVxN8tWYpANS8oMV1J}6wp84p zsSC?h3rltP4-f?nJ2dFohrha&HEr>7x;}(7n3PD0>1t>RY(c>B4HGiT>MHL68w{<W z`StL+HGls>ANl%k^7rTHdM==o@Ezf8KzABk1?IqU@badW%$ren^15?i61xe3YS6XE zzrUSVzji&Kvq=L;y^*hjbp@u=*46{_jS|y}=gw>oSc+CcLjN{u*_N$zZ3Li`@m-OK zjQ~$UOW*)RIp3szYSHXjt^l)WH3YlQ{HE)_Q-4tIQJ_<ht0NH~9)WfSoa7ELD|W~j zwz8mqb9>^N{RimUXh6&A_z<9FRqQad9ynG5o?KJo4lt{C_9)IATovz7&TaxUr@d5C zA3%4ylzRpkp9xPGC}zC&u~^uA^Y^l?r!Iu`NiKt>(*YgT*;4K?3(L>h6H>TY;$Zx< za)0;xpGNdcFN3Ai(KxC7hufvxbHEC;H8Mt&zw|Cc-=_ZKO5DQ*HGtNaa<`#93#>o~ z+N19EA6&}ghfkc1s+bArULEN_JP)kMFDOJ%*3h75z4`gIfs?8i0b1XGxD4HO!1#CE zG>uulO@ppE_~ng^(z>Mx#7scD<sMyXVSiP!lvDq4b6D-F74T;nh%)5`=;p)+2h87- zUG)T~JVEo~ua7KhT!9ik06mP|$q?`WfuWSbncB!j1MV+H>kn@Ma2%{0qU-PsdT6g& zSPlcp6RHG{U<*#)xKq06ovkP;GCv2l=N4YK@cmLgn$sOm<$qAl-h?K}u2ezq1AqJY z8imo%Uj#?Ril~$v(Z8Tv(D)@rKCwxRd1?m_wOXggK26yp6AhA8Dck=G?0&cA<o&4R zY%%tkm#NyF*BT-M;!aM>5{m9phj+E+(3LAy`!4V7(L2$BOyQP=JJepP@->;Y{R{@0 zd+fWv=N$d^CRwtjbaT@u<5%o$8h^QD<8GSDS5fm{ms~ibBz{=oP{kc=|CidFO&tAG zi|16k0?a)FH#_>zP@dH?!SDrZFVX9bq*bKHNtLAWsCKC+91eHNblWqXvmsRZo60Qj zQE1T~otE3`ecpWGeK~snh9xw-k*;xdn?vHxZ7k-$G^M8qS4JtbE)N^p8h`WePtB!u z)18g^(y9|y?$i8`eDfbJ1ROy6&(YFrpP}VHLa%E{Rir}Fa8x)wY@p7%?G_H*zC?Y6 zej|m&QLZ)U1VX*;4}C{WK19E-@_58nRjnMEl&OtP^!^okT~Fez+XA$i>SV2lHM-JE z$ekaa2pn0JOSGMz;>M%*wtuSfVKEvuMd9jK&r_;eOYbdvxaR5Oe4Tp*z1vWbX{DG} zjPvXyj7_rX>FH!-(!&~D*<0(GGjn+n`zE)BJYaIZ)LO&?U;LO;{p4mW?>y3cN=MI9 zLvx+|nR{TlZy|?M4!$Yd&T{aTl18HGNob=KtDpu~cQ5UE^=l_l(0|;Tw(Fv{tZb#J z+nY~Qa8+Shivdu<q6a(04@9oi%8yN!Acoa)aLUB$25bQghW|=nb9>!cdmGvo_;C-a zbm?4u=th50F8zUmY&Ck=90Uz39}z!}e0eFA*t@(EW~;50YjCr}4?}slS(&60;Sx6Z zt^{`L$iNIMvtNZ(?tl4UH34j=6gLvxR5CV4Qn25Uft%+sI8DjT43;#uq7|@zNEinm zr@347-ea(E3Nlg;q_Xbr0!z2BAbE%4?;tE)ETY;0$)kfL1W|TY#iA#v`x4ZBY1K}J z=Kg^nX7cj~_v0bc@EvvpovE!f)}9|56=-ScaL5jYLE(512Y;5A11WFXuJ<l7bgztI zt$>3fqIuF_wS>hDn1B_ELs|>!2(XWG^yLS?vf$~hP!L|rYc_z#Ag`uUYHQ%E(5jH= ztUT`7UF#5Z@c_2so^AoF&_;lp;Mc1BAbFk_f>am>zs#rU_zOOHib%}32W<`x6=}tD zwG>A^@MW@ix_?qQ+y+*y?4yADDi2_Ea?KCc4n@RsG3ZYjrI9U3Gn=r*Bv5H?5S$)4 zujAbB9F_Nh%Eot4M}WKO61I;El9|N{|7L%Vr(!XhTFMsA-*b+Ai|TmoC(0CO3L17S zi%uPyixr7hCS_AcfIE7=XMt-($ZYcH3n$j_Novi(FMmHKi?5^px*TaR0aA|@+MSX; zedOz_9QoP+%^g@QH9vy@84t)L1e?RjDXPnr&J$;R@=!ex&~`LYJ*AsqDvrUmaAg$I zH|t(GSl9ng09knKu(*^Rl`RCY&JJt3{HLuP9ssCdMOLgr06ycflcml;8x?SK8+qKM p1ew=i+aSy1Mkn%ha>(cs?7teD@W9)5>GA*o002ovPDHLkV1i5LRmK1S delta 2569 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 diff --git a/addons/skin.estuary/media/progress/texturebg_alt_white.png b/addons/skin.estuary/media/progress/texturebg_alt_white.png index 0850e1a66fda00c4256c238db3dad9935eed661a..d387ec2e9286229eeca65eafd8945dc8aeecda53 100644 GIT binary patch delta 64 zcmbQv7&k%1O4ZZFF~s6@@}Kh#b_q!mS8Y~RR%E3m{;+@a=Zr;^L{FC7)gDd;SrcX} T`A*@*3_#%N>gTe~DWM4fFjyCP delta 121 zcma!>&NxA(m?_EI-GzZ+Rj;xU0|NtRfk$L90|R$E2s2K0Xb%PovX^-Jy0Smul4s`8 zdsQ!V0Vt&5>Eak-aXL9cf^~7jw1$uNtg~iHuz4ACAO64aMb0uoiO|IfA{WFM7<BlV Vg&tUTssZ&dc)I$ztaD0e0sy^mB8LC~ diff --git a/addons/skin.estuary/media/progress/texturebg_white.png b/addons/skin.estuary/media/progress/texturebg_white.png index bad503e6e1ef3c09cb590ae4526c5ae0e346e9af..e3ece2c989ca317ed5b8e031f00ed5320027fd0f 100644 GIT binary patch delta 71 zcmaz_o1o&P>*?YcVsSe8&v^&Cgd~ZpHmhbbBqSyPfr9!>!@jm}E7FoKoy?FlTQzgp ZEJpd~%+~pKbJsEefv2mV%Q~loCIJ6G8?pcZ delta 59 zcmYd`nxJB)=;`7ZV&R{hAi=u0Vc7$Fj!Dc7zuZhGGr6sL7HGhg!NuSgq5EFkEhLWt O2s~Z=T-G@yGywoi0}~1W diff --git a/addons/skin.estuary/media/windows/pvr/archive.png b/addons/skin.estuary/media/windows/pvr/archive.png index a832a88821aa444b463fd28376eb29b7ee4cf1ce..7e0f878d61a4223f054ba4159a0e2aa2c06ef777 100644 GIT binary patch delta 370 zcmV-&0ge8l1B?TZBYy!?Nkl<ZSi|j?p>6^}5QaY}sR30kA(*N&JOGXw>ZnHZ08RP| z&{qI_9{>%Q7E>IeFL0ucTyjX<iJECbDw^HBS$Ylo+uQl?%kJ#VY^AY@DYL4STmdiu z2EYJ#>3}sQpBB&r>f)ILN8kXYe+d`^ec%D;0_~G;dte7FfqxB<xcs0}pcfB;C!lAr z_8pi3i*xPZBQOCX!}WJSM^a(#`;rVT;l33CV~O_$7|XFnjyoSoeC!LbZ<uqxsR!Kq z0Bp%^)zCovQ~{e(N;K3JKUKi`k2+UGZShkD)ap7`el~!)GGML^c$D^~p}qL20v@Ed zYG@~Zs(>l5QfmTSn@%~lh58NH7KGaZuf94-0!)F-5iXPX#1|kkGEE#HGvL{F<hr^Z z-MA+Crxe^<!($x-FXx=77r=)!{y`Zh>VNSjIW0jc9u(fi0Wbgtz)J`G0+a_omPHzd QQUCw|07*qoM6N<$f_0XleE<Le delta 392 zcmV;30eAk41EB+uBYyw^b5ch_0Itp)=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi z!vFvd!vV){sAK>D02y>eSaefwW^{L9a%BK;VQFr3E^cLXAT%y8E;2FkAZe8V009w6 zL_t(oN9~tE4uUWgL^*(aF2IUC3lD-v5EH$e7jU6>apNKMO@H!92q_6*3tjw|OiYo^ zG`0P$h2uDm<LuAjQI)7h4LaJV+NHg+4-B9}XLLtz^c_fTJ=gb@b=eL=Tl7N1K~3q2 zTI-q~!mj9J;d7<;Z)TAWLNs$r2iHse(#<61T61%M)+wofnl*{B7JMjlsXDWa2c{|| z+LfWerIJtF5`ToD>e{%Jtd)FfvyKCXD!!qxwQ9^d4jAf$2nDUx(X8WuVfHha{Tj@B z7Yj_gLC8P4m}Oir&if=0nCE*I_YjirZKj8?CHFzSce1YAL8#WGHjZ}XxCE)K=lk5K mhcJLV|C2b5<2b1)iXTELZ}-6(48H&X002ovPDHLkU;%>v8mi|2 diff --git a/addons/skin.estuary/media/windows/pvr/record.png b/addons/skin.estuary/media/windows/pvr/record.png index c1638cd64fb5148cdf373eb682f963e94fc209f0..bfb70e0866ab97f0a7478c3e92103b9cc31e1942 100644 GIT binary patch delta 368 zcmV-$0gwKK0*V8WBYy!=Nkl<ZSi|j?p>6^}5Qab62E#cnA(*N&eE=La;98CD1CaC; zpsxV<J^&gpEv7hL;Y1y|<dC=%m?0q*&F<bTy@vhm?fmy;cXnpBwvHmItQ#t>02lxR zU;w;!z&j<M7SIG5;#mL(U=L(}377yq;2G!u?Xzz?U<)jOHGh!0{Ge8%7Y~3}plh)9 z3z!1)OYPtg7z454`bXeFQeo!%k_;^2PKtq%#Crow<XB_JosT6x@dem3%(>sx1^PY! zTXI`9^dx?&fK4eS8tRCjDq!PBohzfZ_^ASFbsZ}|8^A&tu+RoPNc+;zPW)5>_tIN6 zv=u*9zzkR_0coyGrySct{RwPJ!fk*LU!5ccCP3~8mrH!=3lJHZ#tx7<@Mb%5-M-Z8 zu1Wqa1^3?YSVzG7B?s2ibkmpKTDgh(U%W}qOHhUfg?DiP41fXf+5wMhvp&BxiEFk1 O0000<MNUMnLSTaP)}wd; delta 233 zcmV<F02cp>1B3#QBYyzPNkl<ZSi|j?K?;K~5Jf$JdoSS1eHJ;0J%TB;m&*lQG<V6y zL)aOZfD{U?OiaZ8;UQ`Wk1!@<(;x_fAV~4dZQBt8VF)W%zVAwl^~6MYA`<I{D9`zQ zUqWsq!iUs1UOfItxd*ZQD3{>z&!blEU%9R1n7ih}PPs(LR$`M<@T&qY*H+?{1ikte zB>P%a66ARXZ9$_XmoVCbR$0(qZUw8a!MeX<k#tIe_iLUc)t|+u=w^2IgFNn5s-t#Q jcGRc%CqWPdLGtGfE~`&Jz&s#_00000NkvXXu0mjffMje# diff --git a/addons/skin.estuary/themes/curial/overlays/shadow.png b/addons/skin.estuary/themes/curial/overlays/shadow.png index d8fc81cca61a5366f464015599a1b216405965d9..9b72c825af16b18dc4faed964ddbe517cdd48d4a 100644 GIT binary patch delta 1286 zcmV+h1^N1o2$>3yBYy<oNkl<ZXx`PDTb83b3`LIw)4B5>$em7G>JQ6C@&ic4q|(V+ zq(}vObMydY0bl;24;`ydPC|=LXauY80MhgqcfQk`I_fznoqSK-ujqwS)f*aW_(pMC z3mT|6)01BIK5dXZrd|(EK`&|$X+e-^gQr1LJqL_H`EPdH@_&2&fr1jg5x3P2cryHf zSJdLV#{*4Y0c`b>x+hi90pdQv^KkLqhR`d32Y`a?Q^Ae8<aPJMbBD*E`sopAp#4>N zsMD#u1%TAg@;~nmPQg~a{1+QwL5+ZCO6nja)R|Q)gn*%7h>ej|)ZWBiOOM8eO7+u& z&JvJzDJ5^k41X*ju!9u0hu#ONPFs{JkRH&ZV(5`0qaJW-YDx9MJY)V=E218LxDWFp zo0+*9cQdff`5{fstV6#mp9u^BBSC7qN>xvazS~DJSKljm8`u!H&dhW}L2b;-NMIxY zgk*@>FR6N>_9ph*h_@Ns1{flrCY@PgDS!npZ!@BI1%L8kvNsOS?)q`yZHBu!Pb8No zdlg&t#32^-q?(s4rYQstL1UUZ5)C^;7r@QTOomCp8^=+sP399MC>R7ms!2u7xUkR( zcG!n^fJLRP#&Xn=Iz!KW&>J_$seq8|7gE94Vz&vCy}{#<qN%F4;s3=C8~*`NYyi}f zOmUm!5r6qqK_A1bUq^0;*|o6-5AK?a(J~%P#nmr%c_F5)0Lg>v@DTu;^TYLk)MQs` zFsV1Ozg3NQFwX$`@h8EyPoFV-zS|!0L8;ebpZmJ=7(M%v(~+x>tS%X_%ec`49T87} z(fQ+gz&6zniTL6`JPwF+A$a(J9*1}`ibq(1hkq{((3)VSg6JN{09=`PoOGTt@#Rw? zPhe<R_*h-|>u2ep`L+o@6~!-^`0m5`d$mcrYhny*TJ)%i>9ledt|T-q!9A_u>HPfS zKp(G=^Dm*@o`ru=zgwd-zWj9!sE^LC?*ZyZMACOntiPw<am%mWq53$YPUmy`d~#^4 zOn*Irr%iqT$3FP1f<N@Zw(zqaPeWnz2H;HeCry0mQ2c_YJfYy_sq>`)E==$iU^~wQ zP$%ic*Y;#Npt!1`PSTH#6pYH@@gTu@FmiWZza7Og2GpVZDx{iXJ)qR_N1-@f3ezw^ zDSfZMElWtD_CHD5JWVXptxK*C5^aj?Q-471Oj{PM)Xwv%C{8wuHnUeM9+=iIT%~G^ z^?=DnFDCY0*e!*jQBpv*iji!v^*$)3padgh)Uyx^ZO7WuZrg3yLQclY>Ln!nL4w#e z)xdEHyj75bf_cgBSP9=Cwz~VyQkWW!I6s^!xQ77`JlF!?v1p~Xa8IuE{j<eH34c0D zHL;BPK6a;Ec3rM*4P+je_cq(ESdPtsr)Ja)2Pck;h`li`S^SpCGGb{R+3JegI}6%J z`jUz#rXbn}fV~-zYFEM09*}H!t_lj?YY8YROd33O?LHV^>avCsnr+yYp~V8`y{3x8 z6V>4zcGLM|Csps`ngg{il*U$)cYoVbA|TAX(%;|glPu4+P*wKW{E({gS2XryG&#H8 z?_Car^F($%Raw7JwMOY#mGd*rW$|gG#nbVbe_?yxKi1$F%h_MqBHC8mzp%*`gXH^V wZOXlRcaHjSqxbALU)3tV^4QN*yMN>L7p~ij-kz*yg8%>k07*qoM6N<$f+Na`F8}}l delta 1016 zcmV<U0|)$>3XKSmBYy+bNkl<ZXx`o03y+jA5C?Et;QTZu#uyVH@%g!ZM$g0;W8$ZA zfsWA5OlP3%v3uRKc;tZH+sE(z({@>?sS5uwcl2Di<eQjG|4<4Gw*i{fM{KzdHs%P& zq2|c_X7MO<!oLa|G6Z!GRGYIfi4@oJG$*${n2{tCZhm>A@_#ZjqRG%nV8#QLDI5oC zP}sL(O^>%{B<R#VPOM=8UU~V*qqHpFvWZMj0nYPse9Lj738GoyNt|BhUFZ~G1n4Dl z7ex8;cEPjb9&IJjB(lG$dw|G^7UyZSl|;9EVZ#M9f@Bd$R1G|qBqX{7h_X(s0CR#1 z+z8+p&?=hc`+sOFp=$#WEQXY@jvPQ_WV&^r=1s5~krF7WRkB5<idu;`b-b3qX@B5? zE8-j|3tBUexW;dFV@Q=5lI@PR1M&tgv=jRQnFQ+c642nb7CgI3T<;dLyaVzaVjbR+ zWu5`8%CF}2gsKr&kAJOIdrS{H>jR5JUN!y(<bcFMFn=jSCmT2wIOf+dtL9?Yq9qzJ zPA*fNrzg8c%xhrcQ1v21QDn0yFhOcuH@s&tzgmrpx<}86dgKcw4dopW*z=w&G#6O| zsY66uB+4AkqR{JYP!<;!*#%xi4zqTsfU-l-($E@yXa25nA9=JUp5tDg09xk7>{{O* z8~BwO_<vx8P3^8zISyiEoB^qO==3ID>l6H$8Tin{Izf~vQikG)&+^hRu=VTdb^|{$ z10Q)*@7EmzdrlA!AeKPNF62kR7QSZ&KE~t%a6qms@(QrQ3|q?1@3rtPGw_Met>YX! z%&$)ajT)dMEG*+5TxSM8MP84*o>PpSBmtDYw}0gvC*ar2z-J8Xe34~uJ_?Nem(%mW zFPVYQ5!i8taa^1M<L8JTU?{!N20v#8zCa$Os0SPVELj6K0_gYQQ)b{x#6@6#4iI@G z@MC7+E05~9&>MiY7^GW~;nnmDA2I`9SBDXF5czdtupVv(#smZKeP-Yr22z4&04c$# z#DDpL6DcNm5-_yEE0y58%)qxQ!GmanNuZsLgSy01g86}zU@Wm72i;cSJwFa!0@zo7 zZU+o)@Y2B82G0UwTf7kPAoBZPfkl_N_)b#LFjM?My|a(HT^uYFc(_&k0qd>~`HT9} zRQ%V@U43yc_S8r5Cy#b?`mM+ln8lw0-f_iA^c`G=DE>lD;g`aSzL;42<=7ou(B8$A zfTAZ=6hAFx2NQdWpBlA;$z{b)&#S89Cpb!Cp;JUPvB*ibRbuchY?|^iy)DnW<B8sD mk11-Ze9q^>UA)OZ`S}Y$qU4;xt-nzK0000<MNUMnLSTX`5$JFL From ccf1d1b7b94213ee97b5a679318ffab819709c96 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Tue, 7 May 2024 18:39:37 +0200 Subject: [PATCH 638/651] [filesystem][addons devkit] Update file read flags documentation --- xbmc/addons/interfaces/Filesystem.cpp | 3 +- .../include/kodi/c-api/filesystem.h | 14 ++++++++-- .../kodi-dev-kit/include/kodi/versions.h | 2 +- xbmc/filesystem/IFileTypes.h | 28 ++++++++++--------- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/xbmc/addons/interfaces/Filesystem.cpp b/xbmc/addons/interfaces/Filesystem.cpp index eb35cb7b5411e..d8ae5a2887822 100644 --- a/xbmc/addons/interfaces/Filesystem.cpp +++ b/xbmc/addons/interfaces/Filesystem.cpp @@ -153,8 +153,7 @@ unsigned int Interface_Filesystem::TranslateFileReadBitsToKodi(unsigned int addo kodiFlags |= READ_AFTER_WRITE; if (addonFlags & ADDON_READ_REOPEN) kodiFlags |= READ_REOPEN; - //! @todo Add ADDON_READ_NO_BUFFER to filesystem.h in the binary addon devkit - if (addonFlags & READ_NO_BUFFER) + if (addonFlags & ADDON_READ_NO_BUFFER) kodiFlags |= READ_NO_BUFFER; return kodiFlags; diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/filesystem.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/filesystem.h index 41810ca93c616..0d155d88ae4d8 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/filesystem.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/filesystem.h @@ -63,6 +63,9 @@ extern "C" /// @brief **0000 0000 0010** :\n /// Indicate that that caller support read in the minimum defined /// chunk size, this disables internal cache then. + /// This flag is deprecated, instead use ADDON_READ_NO_CACHE to disable FileCache and + /// ADDON_READ_NO_BUFFER to disable StreamBuffer. On the contrary to explicitly indicate that + /// the file has audio/video content (suitable for caching), use the ADDON_READ_AUDIO_VIDEO flag. ADDON_READ_CHUNKED = 0x02, /// @brief **0000 0000 0100** :\n @@ -83,8 +86,9 @@ extern "C" ADDON_READ_MULTI_STREAM = 0x20, /// @brief **0000 0100 0000** :\n - /// indicate to the caller file is audio and/or video (and e.g. may - /// grow). + /// Indicate to the caller file is audio and/or video and is suitable for caching with FileCache or StreamBuffer. + /// The final method used will depend on the user's settings and file location, e.g. user can disable FileCache. + /// This flag ensures that at least the buffer size necessary to read with the appropriate chunk size will be used. ADDON_READ_AUDIO_VIDEO = 0x40, /// @brief **0000 1000 0000** :\n @@ -93,7 +97,11 @@ extern "C" /// @brief **0001 0000 0000** :\n /// Indicate that caller want to reopen a file if its already open. - ADDON_READ_REOPEN = 0x100 + ADDON_READ_REOPEN = 0x100, + + /// @brief **0010 0000 0000** :\n + /// Indicate that caller want open a file without intermediate buffer regardless to file type. + ADDON_READ_NO_BUFFER = 0x200, } OpenFileFlags; ///@} //---------------------------------------------------------------------------- diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h index f0c7a81cf0468..9a2964e8a5c47 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h @@ -62,7 +62,7 @@ #define ADDON_GLOBAL_VERSION_AUDIOENGINE_DEPENDS "AudioEngine.h" \ "c-api/audio_engine.h" -#define ADDON_GLOBAL_VERSION_FILESYSTEM "1.1.8" +#define ADDON_GLOBAL_VERSION_FILESYSTEM "1.1.9" #define ADDON_GLOBAL_VERSION_FILESYSTEM_MIN "1.1.7" #define ADDON_GLOBAL_VERSION_FILESYSTEM_XML_ID "kodi.binary.global.filesystem" #define ADDON_GLOBAL_VERSION_FILESYSTEM_DEPENDS "Filesystem.h" \ diff --git a/xbmc/filesystem/IFileTypes.h b/xbmc/filesystem/IFileTypes.h index 1f89350cc9d0c..ce8d994986af4 100644 --- a/xbmc/filesystem/IFileTypes.h +++ b/xbmc/filesystem/IFileTypes.h @@ -14,39 +14,41 @@ namespace XFILE { /* indicate that caller can handle truncated reads, where function returns before entire buffer has been filled */ - static const unsigned int READ_TRUNCATED = 0x01; +static const unsigned int READ_TRUNCATED = 0x01; /* indicate that that caller support read in the minimum defined chunk size, this disables internal cache then */ - static const unsigned int READ_CHUNKED = 0x02; +static const unsigned int READ_CHUNKED = 0x02; /* use cache to access this file */ - static const unsigned int READ_CACHED = 0x04; +static const unsigned int READ_CACHED = 0x04; /* open without caching. regardless to file type. */ - static const unsigned int READ_NO_CACHE = 0x08; +static const unsigned int READ_NO_CACHE = 0x08; /* calculate bitrate for file while reading */ - static const unsigned int READ_BITRATE = 0x10; +static const unsigned int READ_BITRATE = 0x10; /* indicate to the caller we will seek between multiple streams in the file frequently */ - static const unsigned int READ_MULTI_STREAM = 0x20; +static const unsigned int READ_MULTI_STREAM = 0x20; -/* indicate to the caller file is audio and/or video (and e.g. may grow) */ - static const unsigned int READ_AUDIO_VIDEO = 0x40; +// Indicate to the caller file is audio and/or video and is suitable for caching with FileCache or StreamBuffer. +// The final method used will depend on the user's settings and file location, e.g. user can disable FileCache. +// This flag ensures that at least the buffer size necessary to read with the appropriate chunk size will be used. +static const unsigned int READ_AUDIO_VIDEO = 0x40; /* indicate that caller will do write operations before reading */ - static const unsigned int READ_AFTER_WRITE = 0x80; +static const unsigned int READ_AFTER_WRITE = 0x80; /* indicate that caller want to reopen a file if its already open */ - static const unsigned int READ_REOPEN = 0x100; +static const unsigned int READ_REOPEN = 0x100; /* indicate that caller want open a file without intermediate buffer regardless to file type */ - static const unsigned int READ_NO_BUFFER = 0x200; +static const unsigned int READ_NO_BUFFER = 0x200; struct SNativeIoControl { - unsigned long int request; - void* param; + unsigned long int request; + void* param; }; struct SCacheStatus From 9819090f1fc5d5a0d20ca1c516a1eb0a57587d70 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 25 Oct 2024 20:38:12 +0200 Subject: [PATCH 639/651] [PVR][cores][video] Allow stream details extraction for PVR recordings, if multiple streams is supported by the providing add-on. --- xbmc/Util.cpp | 2 +- xbmc/cores/VideoPlayer/DVDFileInfo.cpp | 13 ++++++------- xbmc/pvr/PVRThumbLoader.cpp | 2 +- xbmc/pvr/PVRThumbLoader.h | 2 +- xbmc/pvr/recordings/PVRRecording.cpp | 1 + xbmc/pvr/recordings/PVRRecording.h | 2 +- xbmc/video/VideoDatabase.cpp | 12 ++++++++++++ xbmc/video/VideoDatabase.h | 2 ++ 8 files changed, 25 insertions(+), 11 deletions(-) diff --git a/xbmc/Util.cpp b/xbmc/Util.cpp index 916effb4e8d1e..f4de8b5ce9150 100644 --- a/xbmc/Util.cpp +++ b/xbmc/Util.cpp @@ -2060,7 +2060,7 @@ void CUtil::ScanForExternalSubtitles(const std::string& strMovie, std::vector<st CFileItem item(strMovie, false); if ((NETWORK::IsInternetStream(item) && !URIUtils::IsOnLAN(item.GetDynPath())) || - PLAYLIST::IsPlayList(item) || item.IsLiveTV() || !VIDEO::IsVideo(item)) + PLAYLIST::IsPlayList(item) || item.IsPVR() || !VIDEO::IsVideo(item)) return; CLog::Log(LOGDEBUG, "{}: Searching for subtitles...", __FUNCTION__); diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp index a6b09baf0f7b5..aca8846718ce6 100644 --- a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp +++ b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp @@ -259,7 +259,8 @@ bool CDVDFileInfo::CanExtract(const CFileItem& fileItem) if (fileItem.m_bIsFolder) return false; - if ((URIUtils::IsPVR(fileItem.GetPath()) && !PVR::ProvidesStreamForThumbExtraction(fileItem)) || + if ((URIUtils::IsPVR(fileItem.GetPath()) && + !PVR::ProvidesStreamForMetaDataExtraction(fileItem)) || // plugin path not fully resolved URIUtils::IsPlugin(fileItem.GetDynPath()) || URIUtils::IsUPnP(fileItem.GetPath()) || NETWORK::IsInternetStream(fileItem) || VIDEO::IsDiscStub(fileItem) || @@ -308,11 +309,6 @@ bool CDVDFileInfo::GetFileStreamDetails(CFileItem *pItem) if (!pInputStream) return false; - if (pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER)) - { - return false; - } - if (pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) || !pInputStream->Open()) { return false; @@ -322,7 +318,10 @@ bool CDVDFileInfo::GetFileStreamDetails(CFileItem *pItem) if (pDemuxer) { bool retVal = DemuxerToStreamDetails(pInputStream, pDemuxer, pItem->GetVideoInfoTag()->m_streamDetails, strFileNameAndPath); - ProcessExternalSubtitles(pItem); + + if (!pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER)) + ProcessExternalSubtitles(pItem); + delete pDemuxer; return retVal; } diff --git a/xbmc/pvr/PVRThumbLoader.cpp b/xbmc/pvr/PVRThumbLoader.cpp index 1ab7a07e59a62..2a4e03070fa61 100644 --- a/xbmc/pvr/PVRThumbLoader.cpp +++ b/xbmc/pvr/PVRThumbLoader.cpp @@ -27,7 +27,7 @@ namespace PVR { -bool ProvidesStreamForThumbExtraction(const CFileItem& item) +bool ProvidesStreamForMetaDataExtraction(const CFileItem& item) { // Note: We must not rely on presence of a recording info tag here, but path is always set. if (URIUtils::IsPVRRecording(item.GetPath())) diff --git a/xbmc/pvr/PVRThumbLoader.h b/xbmc/pvr/PVRThumbLoader.h index 2480447ef6511..af00949942945 100644 --- a/xbmc/pvr/PVRThumbLoader.h +++ b/xbmc/pvr/PVRThumbLoader.h @@ -14,7 +14,7 @@ namespace PVR { -bool ProvidesStreamForThumbExtraction(const CFileItem& item); +bool ProvidesStreamForMetaDataExtraction(const CFileItem& item); class CPVRThumbLoader : public CThumbLoader { diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp index 9994a0a621f71..fc7538dd03be7 100644 --- a/xbmc/pvr/recordings/PVRRecording.cpp +++ b/xbmc/pvr/recordings/PVRRecording.cpp @@ -423,6 +423,7 @@ void CPVRRecording::UpdateMetadata(CVideoDatabase& db, const CPVRClient& client) } m_lastPlayed = db.GetLastPlayed(m_strFileNameAndPath); + db.GetStreamDetails(m_strFileNameAndPath, m_streamDetails); m_bGotMetaData = true; } diff --git a/xbmc/pvr/recordings/PVRRecording.h b/xbmc/pvr/recordings/PVRRecording.h index 49c18abfb4383..9c9d9441ade1f 100644 --- a/xbmc/pvr/recordings/PVRRecording.h +++ b/xbmc/pvr/recordings/PVRRecording.h @@ -163,7 +163,7 @@ class CPVRRecording final : public CVideoInfoTag std::vector<EDL::Edit> GetEdl() const; /*! - * @brief Get the resume point and play count from the database if the + * @brief Get metadata like the resume point and play count from the database if the * client doesn't handle it itself. * @param db The database to read the data from. * @param client The client this recording belongs to. diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index c5500a4b277ef..fb3dfd9f69cd3 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -4220,6 +4220,18 @@ CVideoInfoTag CVideoDatabase::GetDetailsByTypeAndId(VideoDbContentType type, int return {}; } +bool CVideoDatabase::GetStreamDetails(const std::string& filenameAndPath, CStreamDetails& details) +{ + CVideoInfoTag tag; + tag.m_iFileId = GetFileId(filenameAndPath); + if (GetStreamDetails(tag)) + { + details = tag.m_streamDetails; + return true; + } + return false; +} + bool CVideoDatabase::GetStreamDetails(CFileItem& item) { // Note that this function (possibly) creates VideoInfoTags for items that don't have one yet! diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index 2c8ea6357de4f..96f07ec665a16 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -22,6 +22,7 @@ class CFileItem; class CFileItemList; +class CStreamDetails; class CVideoSettings; class CGUIDialogProgress; class CGUIDialogProgressBarHandle; @@ -701,6 +702,7 @@ class CVideoDatabase : public CDatabase bool GetResumePoint(CVideoInfoTag& tag); bool GetStreamDetails(CFileItem& item); bool GetStreamDetails(CVideoInfoTag& tag); + bool GetStreamDetails(const std::string& filenameAndPath, CStreamDetails& details); bool GetDetailsByTypeAndId(CFileItem& item, VideoDbContentType type, int id); CVideoInfoTag GetDetailsByTypeAndId(VideoDbContentType type, int id); From 418840f0ddc12fd0e2ca40655ee3e5d51a39f0f3 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:50:28 +0100 Subject: [PATCH 640/651] [PVR][cores] Move ProvidesStreamForMetaDataExtraction to CInputStreamPVRBase. --- xbmc/cores/VideoPlayer/DVDFileInfo.cpp | 17 ++++++++--------- .../DVDInputStreams/InputStreamPVRBase.cpp | 15 +++++++++++++++ .../DVDInputStreams/InputStreamPVRBase.h | 2 ++ xbmc/pvr/PVRThumbLoader.cpp | 19 ------------------- xbmc/pvr/PVRThumbLoader.h | 4 +--- 5 files changed, 26 insertions(+), 31 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp index aca8846718ce6..4c84510ebfabf 100644 --- a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp +++ b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp @@ -18,7 +18,6 @@ #include "network/NetworkFileItemClassify.h" #include "pictures/Picture.h" #include "playlists/PlayListFileItemClassify.h" -#include "pvr/PVRThumbLoader.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" #include "utils/MemUtils.h" @@ -29,20 +28,20 @@ #ifdef HAVE_LIBBLURAY #include "DVDInputStreams/DVDInputStreamBluray.h" #endif -#include "DVDInputStreams/DVDFactoryInputStream.h" -#include "DVDDemuxers/DVDDemux.h" -#include "DVDDemuxers/DVDDemuxUtils.h" -#include "DVDDemuxers/DVDFactoryDemuxer.h" #include "DVDCodecs/DVDFactoryCodec.h" #include "DVDCodecs/Video/DVDVideoCodec.h" #include "DVDCodecs/Video/DVDVideoCodecFFmpeg.h" +#include "DVDDemuxers/DVDDemux.h" +#include "DVDDemuxers/DVDDemuxUtils.h" #include "DVDDemuxers/DVDDemuxVobsub.h" +#include "DVDDemuxers/DVDFactoryDemuxer.h" +#include "DVDInputStreams/DVDFactoryInputStream.h" +#include "DVDInputStreams/InputStreamPVRBase.h" #include "Process/ProcessInfo.h" - -#include "filesystem/File.h" -#include "cores/FFmpeg.h" #include "TextureCache.h" #include "Util.h" +#include "cores/FFmpeg.h" +#include "filesystem/File.h" #include "utils/LangCodeExpander.h" #include <cstdlib> @@ -260,7 +259,7 @@ bool CDVDFileInfo::CanExtract(const CFileItem& fileItem) return false; if ((URIUtils::IsPVR(fileItem.GetPath()) && - !PVR::ProvidesStreamForMetaDataExtraction(fileItem)) || + !CInputStreamPVRBase::ProvidesStreamForMetaDataExtraction(fileItem)) || // plugin path not fully resolved URIUtils::IsPlugin(fileItem.GetDynPath()) || URIUtils::IsUPnP(fileItem.GetPath()) || NETWORK::IsInternetStream(fileItem) || VIDEO::IsDiscStub(fileItem) || diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp index 7ec9ab307c4fd..62e4e53ee68ca 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp @@ -15,6 +15,7 @@ #include "pvr/addons/PVRClient.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" +#include "utils/URIUtils.h" #include "utils/log.h" CInputStreamPVRBase::CInputStreamPVRBase(IVideoPlayer* pPlayer, const CFileItem& fileitem) @@ -352,3 +353,17 @@ void CInputStreamPVRBase::UpdateStreamMap() m_streamMap = newStreamMap; } + +bool CInputStreamPVRBase::ProvidesStreamForMetaDataExtraction(const CFileItem& item) +{ + // Note: We must not rely on presence of a recording info tag here, but path is always set. + if (URIUtils::IsPVRRecording(item.GetPath())) + { + const std::shared_ptr<const PVR::CPVRClient> client{ + CServiceBroker::GetPVRManager().GetClient(item)}; + if (client) + return client->GetClientCapabilities().SupportsMultipleRecordedStreams(); + } + + return false; +} diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h index c060453959df3..d14fd95e8bd46 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h @@ -30,6 +30,8 @@ class CInputStreamPVRBase , public CDVDInputStream::IDemux { public: + static bool ProvidesStreamForMetaDataExtraction(const CFileItem& item); + CInputStreamPVRBase(IVideoPlayer* pPlayer, const CFileItem& fileitem); ~CInputStreamPVRBase() override; bool Open() override; diff --git a/xbmc/pvr/PVRThumbLoader.cpp b/xbmc/pvr/PVRThumbLoader.cpp index 2a4e03070fa61..99cbefd276406 100644 --- a/xbmc/pvr/PVRThumbLoader.cpp +++ b/xbmc/pvr/PVRThumbLoader.cpp @@ -14,32 +14,13 @@ #include "TextureCache.h" #include "imagefiles/ImageFileURL.h" #include "pvr/PVRManager.h" -#include "pvr/addons/PVRClient.h" -#include "pvr/filesystem/PVRGUIDirectory.h" -#include "settings/AdvancedSettings.h" -#include "settings/Settings.h" -#include "settings/SettingsComponent.h" #include "utils/StringUtils.h" -#include "utils/URIUtils.h" #include "utils/log.h" #include <ctime> namespace PVR { -bool ProvidesStreamForMetaDataExtraction(const CFileItem& item) -{ - // Note: We must not rely on presence of a recording info tag here, but path is always set. - if (URIUtils::IsPVRRecording(item.GetPath())) - { - const std::shared_ptr<const CPVRClient> client{CServiceBroker::GetPVRManager().GetClient(item)}; - if (client) - return client->GetClientCapabilities().SupportsMultipleRecordedStreams(); - } - - return false; -} - bool CPVRThumbLoader::LoadItem(CFileItem* item) { bool result = LoadItemCached(item); diff --git a/xbmc/pvr/PVRThumbLoader.h b/xbmc/pvr/PVRThumbLoader.h index af00949942945..a031b1efcfec7 100644 --- a/xbmc/pvr/PVRThumbLoader.h +++ b/xbmc/pvr/PVRThumbLoader.h @@ -14,8 +14,6 @@ namespace PVR { -bool ProvidesStreamForMetaDataExtraction(const CFileItem& item); - class CPVRThumbLoader : public CThumbLoader { public: @@ -39,4 +37,4 @@ class CPVRThumbLoader : public CThumbLoader bool m_bInvalidated = false; }; -} +} // namespace PVR From ea7e441dfb9c4c1ad7aaaffac92a092505a25d46 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 30 Oct 2024 20:19:49 +0100 Subject: [PATCH 641/651] [Estuary] Show media flags in PVR recordings window (instead of backend disk usage, which does not work well for multiclient setups and is also available in system info). --- addons/skin.estuary/xml/Includes.xml | 2 +- addons/skin.estuary/xml/MyPVRRecordings.xml | 27 +++------------------ 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/addons/skin.estuary/xml/Includes.xml b/addons/skin.estuary/xml/Includes.xml index b649528a36b84..36cd212cad77a 100644 --- a/addons/skin.estuary/xml/Includes.xml +++ b/addons/skin.estuary/xml/Includes.xml @@ -366,7 +366,7 @@ <param name="visible">true</param> <definition> <control type="grouplist"> - <visible>Window.IsActive(Home) | Window.IsActive(10025)</visible> + <visible>Window.IsActive(Home) | Window.IsActive(10025) | Window.IsActive(TVRecordings) | Window.IsActive(RadioRecordings)</visible> <orientation>horizontal</orientation> <right>20</right> <bottom>0</bottom> diff --git a/addons/skin.estuary/xml/MyPVRRecordings.xml b/addons/skin.estuary/xml/MyPVRRecordings.xml index bc45ed139f326..16cf5a4cddabb 100644 --- a/addons/skin.estuary/xml/MyPVRRecordings.xml +++ b/addons/skin.estuary/xml/MyPVRRecordings.xml @@ -65,30 +65,11 @@ <param name="info_visible" value="true" /> </include> <control type="group"> - <depth>DepthBars</depth> - <right>20</right> - <width>850</width> - <include>OpenClose_Right</include> <bottom>0</bottom> - <height>60</height> - <control type="label"> - <right>220</right> - <width>610</width> - <height>20</height> - <label>$INFO[PVR.backenddiskspace]</label> - <shadowcolor>black</shadowcolor> - <align>right</align> - <font>font27</font> - <visible>!Integer.IsGreater(PVR.backenddiskspaceprogr,100)</visible> - </control> - <control type="progress"> - <right>0</right> - <top>17</top> - <width>200</width> - <height>12</height> - <info>PVR.backenddiskspaceprogr</info> - <visible>!Integer.IsGreater(PVR.backenddiskspaceprogr,100)</visible> - </control> + <height>70</height> + <animation effect="fade" start="0" end="100" time="300" delay="300">WindowOpen</animation> + <animation effect="fade" start="100" end="0" time="200">WindowClose</animation> + <include condition="!Skin.HasSetting(hide_mediaflags)">MediaFlags</include> </control> <control type="group"> <include>MediaMenuCommon</include> From 89c5e74714ba018e51f0608bab2d793e809842ff Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 2 Nov 2024 00:06:24 +0100 Subject: [PATCH 642/651] [PVR] Delete metadata from video db on delete of recordings. --- xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp | 7 ++-- xbmc/pvr/recordings/PVRRecording.cpp | 5 +++ xbmc/pvr/recordings/PVRRecording.h | 6 ++++ xbmc/pvr/recordings/PVRRecordings.cpp | 11 ++++++ xbmc/pvr/recordings/PVRRecordings.h | 7 ++++ xbmc/video/VideoDatabase.cpp | 38 +++++++++++++++++++++ xbmc/video/VideoDatabase.h | 6 ++++ 7 files changed, 78 insertions(+), 2 deletions(-) diff --git a/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp b/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp index 0366556764442..71c991ee9967c 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp @@ -126,11 +126,14 @@ class AsyncDeleteRecording : public AsyncRecordingAction items.Add(item); } + const auto recordings{CServiceBroker::GetPVRManager().Recordings()}; return std::accumulate( - items.cbegin(), items.cend(), true, [this](bool success, const auto& itemToDelete) { + items.cbegin(), items.cend(), true, + [this, &recordings](bool success, const auto& itemToDelete) + { return (itemToDelete->IsPVRRecording() && (!m_bWatchedOnly || itemToDelete->GetPVRRecordingInfoTag()->GetPlayCount() > 0) && - !itemToDelete->GetPVRRecordingInfoTag()->Delete()) + !recordings->DeleteRecording(itemToDelete->GetPVRRecordingInfoTag())) ? false : success; }); diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp index 9994a0a621f71..051d95847e6e7 100644 --- a/xbmc/pvr/recordings/PVRRecording.cpp +++ b/xbmc/pvr/recordings/PVRRecording.cpp @@ -427,6 +427,11 @@ void CPVRRecording::UpdateMetadata(CVideoDatabase& db, const CPVRClient& client) m_bGotMetaData = true; } +void CPVRRecording::DeleteMetadata(CVideoDatabase& db) +{ + db.EraseAllForFile(m_strFileNameAndPath); +} + std::vector<EDL::Edit> CPVRRecording::GetEdl() const { std::vector<EDL::Edit> edls; diff --git a/xbmc/pvr/recordings/PVRRecording.h b/xbmc/pvr/recordings/PVRRecording.h index 49c18abfb4383..e846de1dcbbb0 100644 --- a/xbmc/pvr/recordings/PVRRecording.h +++ b/xbmc/pvr/recordings/PVRRecording.h @@ -170,6 +170,12 @@ class CPVRRecording final : public CVideoInfoTag */ void UpdateMetadata(CVideoDatabase& db, const CPVRClient& client); + /*! + * @brief Delete metadata like the resume point and play count from the database. + * @param db The database to delete the data from. + */ + void DeleteMetadata(CVideoDatabase& db); + /*! * @brief Update this tag with the contents of the given tag. * @param tag The new tag info. diff --git a/xbmc/pvr/recordings/PVRRecordings.cpp b/xbmc/pvr/recordings/PVRRecordings.cpp index 363196b1d0bb1..e0e30b0409eea 100644 --- a/xbmc/pvr/recordings/PVRRecordings.cpp +++ b/xbmc/pvr/recordings/PVRRecordings.cpp @@ -356,6 +356,17 @@ bool CPVRRecordings::ResetResumePoint(const std::shared_ptr<CPVRRecording>& reco return bResult; } +bool CPVRRecordings::DeleteRecording(const std::shared_ptr<CPVRRecording>& recording) +{ + CVideoDatabase& db = GetVideoDatabase(); + if (db.IsOpen() && recording->Delete()) + { + recording->DeleteMetadata(db); + return true; + } + return false; +} + CVideoDatabase& CPVRRecordings::GetVideoDatabase() { if (!m_database) diff --git a/xbmc/pvr/recordings/PVRRecordings.h b/xbmc/pvr/recordings/PVRRecordings.h index 4857da36189f9..3958f36060213 100644 --- a/xbmc/pvr/recordings/PVRRecordings.h +++ b/xbmc/pvr/recordings/PVRRecordings.h @@ -82,6 +82,13 @@ class CPVRRecordings */ bool ResetResumePoint(const std::shared_ptr<CPVRRecording>& recording); + /*! + * @brief Delete a recording in the backend, cleanup db enries. + * @param recording The recording + * @return True on success, false otherwise + */ + bool DeleteRecording(const std::shared_ptr<CPVRRecording>& recording); + /*! * @brief Get a list of all recordings * @return the list of all recordings diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index c5500a4b277ef..2d999092bdad1 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -12000,6 +12000,44 @@ void CVideoDatabase::EraseAllForPath(const std::string& path) } } +void CVideoDatabase::EraseAllForFile(const std::string& fileNameAndPath) +{ + try + { + const int fileId{GetFileId(fileNameAndPath)}; + if (fileId != -1) + { + std::string sql = PrepareSQL("DELETE FROM settings WHERE idFile = %i", fileId); + m_pDS->exec(sql); + + sql = PrepareSQL("DELETE FROM bookmark WHERE idFile = %i", fileId); + m_pDS->exec(sql); + + sql = PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", fileId); + m_pDS->exec(sql); + + sql = PrepareSQL("DELETE FROM files WHERE idFile = %i", fileId); + m_pDS->exec(sql); + + std::string path; + std::string fileName; + SplitPath(fileNameAndPath, path, fileName); + const int pathId{GetPathId(path)}; + if (pathId != -1) + { + sql = PrepareSQL("DELETE FROM path WHERE idPath = %i " + "AND NOT EXISTS (SELECT 1 FROM files WHERE files.idPath = %i)", + pathId, pathId); + m_pDS->exec(sql); + } + } + } + catch (...) + { + CLog::Log(LOGERROR, "{} failed", __FUNCTION__); + } +} + std::string CVideoDatabase::GetVideoItemTitle(VideoDbContentType itemType, int dbId) { switch (itemType) diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index 2c8ea6357de4f..e504243a3a17a 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -685,6 +685,12 @@ class CVideoDatabase : public CDatabase */ void EraseAllForPath(const std::string& path); + /** + * Erases all entries for the given file, including path entry if no longer used + * @param fileNameAndPath The name and path of the file to erase db entries for + */ + void EraseAllForFile(const std::string& fileNameAndPath); + bool GetStackTimes(const std::string &filePath, std::vector<uint64_t> ×); void SetStackTimes(const std::string &filePath, const std::vector<uint64_t> ×); From 5f9b296ce81c63156599a65915978607d1cf9df9 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 3 Nov 2024 11:33:01 +0100 Subject: [PATCH 643/651] [PVR] Add support for automatic texture cache cleanup for parantal rating icon for recordings and EPG tags. --- xbmc/pvr/addons/PVRClient.cpp | 4 +-- xbmc/pvr/epg/Epg.cpp | 4 ++- xbmc/pvr/epg/EpgDatabase.cpp | 43 +++++++++++++++++++++------ xbmc/pvr/epg/EpgDatabase.h | 13 ++++++-- xbmc/pvr/epg/EpgInfoTag.cpp | 16 ++++++---- xbmc/pvr/epg/EpgInfoTag.h | 14 +++++++-- xbmc/pvr/recordings/PVRRecording.cpp | 14 ++++----- xbmc/pvr/recordings/PVRRecording.h | 11 ++++++- xbmc/pvr/recordings/PVRRecordings.cpp | 4 ++- 9 files changed, 91 insertions(+), 32 deletions(-) diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index d80af88cd0567..05f87afbe108c 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -134,7 +134,7 @@ class CAddonRecording : public PVR_RECORDING m_firstAired(recording.FirstAired().IsValid() ? recording.FirstAired().GetAsW3CDate() : ""), m_providerName(recording.ProviderName()), m_parentalRatingCode(recording.GetParentalRatingCode()), - m_parentalRatingIcon(recording.GetParentalRatingIcon()), + m_parentalRatingIcon(recording.ClientParentalRatingIconPath()), m_parentalRatingSource(recording.GetParentalRatingSource()) { // zero-init base struct members @@ -309,7 +309,7 @@ class CAddonEpgTag : public EPG_TAG m_genreDescription(tag.GenreDescription()), m_firstAired(GetFirstAired(tag)), m_parentalRatingCode(tag.ParentalRatingCode()), - m_parentalRatingIcon(tag.ParentalRatingIcon()), + m_parentalRatingIcon(tag.ClientParentalRatingIconPath()), m_parentalRatingSource(tag.ParentalRatingSource()) { // zero-init base struct members diff --git a/xbmc/pvr/epg/Epg.cpp b/xbmc/pvr/epg/Epg.cpp index 8bc6045d20683..cb76937c3e011 100644 --- a/xbmc/pvr/epg/Epg.cpp +++ b/xbmc/pvr/epg/Epg.cpp @@ -547,7 +547,9 @@ void CPVREpg::RemovedFromContainer() int CPVREpg::CleanupCachedImages(const std::shared_ptr<const CPVREpgDatabase>& database) { - const std::vector<std::string> urlsToCheck = database->GetAllIconPaths(EpgID()); + std::vector<std::string> urlsToCheck; + database->GetAllIconPaths(EpgID(), urlsToCheck); + database->GetAllParentalRatingIconPaths(EpgID(), urlsToCheck); const std::string owner = StringUtils::Format(CPVREpgInfoTag::IMAGE_OWNER_PATTERN, EpgID()); return CPVRCachedImages::Cleanup({{owner, ""}}, urlsToCheck); diff --git a/xbmc/pvr/epg/EpgDatabase.cpp b/xbmc/pvr/epg/EpgDatabase.cpp index 32150e434762a..785693d698fbd 100644 --- a/xbmc/pvr/epg/EpgDatabase.cpp +++ b/xbmc/pvr/epg/EpgDatabase.cpp @@ -443,7 +443,8 @@ std::shared_ptr<CPVREpgInfoTag> CPVREpgDatabase::CreateEpgTag( if (!pDS->eof()) { std::shared_ptr<CPVREpgInfoTag> newTag( - new CPVREpgInfoTag(m_pDS->fv("idEpg").get_asInt(), m_pDS->fv("sIconPath").get_asString())); + new CPVREpgInfoTag(m_pDS->fv("idEpg").get_asInt(), m_pDS->fv("sIconPath").get_asString(), + m_pDS->fv("sParentalRatingIcon").get_asString())); time_t iStartTime; iStartTime = static_cast<time_t>(m_pDS->fv("iStartTime").get_asInt()); @@ -481,7 +482,6 @@ std::shared_ptr<CPVREpgInfoTag> CPVREpgDatabase::CreateEpgTag( newTag->m_iFlags = m_pDS->fv("iFlags").get_asInt(); newTag->m_strSeriesLink = m_pDS->fv("sSeriesLink").get_asString(); newTag->m_parentalRatingCode = m_pDS->fv("sParentalRatingCode").get_asString(); - newTag->m_parentalRatingIcon = m_pDS->fv("sParentalRatingIcon").get_asString(); newTag->m_parentalRatingSource = m_pDS->fv("sParentalRatingSource").get_asString(); newTag->m_iGenreType = m_pDS->fv("iGenreType").get_asInt(); newTag->m_iGenreSubType = m_pDS->fv("iGenreSubType").get_asInt(); @@ -1121,7 +1121,7 @@ std::vector<std::shared_ptr<CPVREpgInfoTag>> CPVREpgDatabase::GetAllEpgTags(int return {}; } -std::vector<std::string> CPVREpgDatabase::GetAllIconPaths(int iEpgID) const +bool CPVREpgDatabase::GetAllIconPaths(int iEpgID, std::vector<std::string>& paths) const { std::unique_lock<CCriticalSection> lock(m_critSection); const std::string strQuery = @@ -1130,21 +1130,46 @@ std::vector<std::string> CPVREpgDatabase::GetAllIconPaths(int iEpgID) const { try { - std::vector<std::string> paths; while (!m_pDS->eof()) { paths.emplace_back(m_pDS->fv("sIconPath").get_asString()); m_pDS->next(); } m_pDS->close(); - return paths; + return true; } catch (...) { - CLog::LogF(LOGERROR, "Could not load tags for EPG ({})", iEpgID); + CLog::LogF(LOGERROR, "Could not load icon paths for EPG ({})", iEpgID); } } - return {}; + return false; +} + +bool CPVREpgDatabase::GetAllParentalRatingIconPaths(int iEpgID, + std::vector<std::string>& paths) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + const std::string strQuery = + PrepareSQL("SELECT sParentalRatingIcon FROM epgtags WHERE idEpg = %u;", iEpgID); + if (ResultQuery(strQuery)) + { + try + { + while (!m_pDS->eof()) + { + paths.emplace_back(m_pDS->fv("sParentalRatingIcon").get_asString()); + m_pDS->next(); + } + m_pDS->close(); + return true; + } + catch (...) + { + CLog::LogF(LOGERROR, "Could not load parental rating icon paths for EPG ({})", iEpgID); + } + } + return false; } bool CPVREpgDatabase::GetLastEpgScanTime(int iEpgId, CDateTime* lastScan) const @@ -1301,7 +1326,7 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag) tag.GenreDescription().c_str(), sFirstAired.c_str(), tag.ParentalRating(), tag.StarRating(), tag.SeriesNumber(), tag.EpisodeNumber(), tag.EpisodePart(), tag.EpisodeName().c_str(), tag.Flags(), tag.SeriesLink().c_str(), tag.ParentalRatingCode().c_str(), - tag.UniqueBroadcastID(), tag.ParentalRatingIcon().c_str(), + tag.UniqueBroadcastID(), tag.ClientParentalRatingIconPath().c_str(), tag.ParentalRatingSource().c_str(), tag.TitleExtraInfo().c_str()); } else @@ -1324,7 +1349,7 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag) tag.GenreDescription().c_str(), sFirstAired.c_str(), tag.ParentalRating(), tag.StarRating(), tag.SeriesNumber(), tag.EpisodeNumber(), tag.EpisodePart(), tag.EpisodeName().c_str(), tag.Flags(), tag.SeriesLink().c_str(), tag.ParentalRatingCode().c_str(), - tag.UniqueBroadcastID(), iBroadcastId, tag.ParentalRatingIcon().c_str(), + tag.UniqueBroadcastID(), iBroadcastId, tag.ClientParentalRatingIconPath().c_str(), tag.ParentalRatingSource().c_str(), tag.TitleExtraInfo().c_str()); } diff --git a/xbmc/pvr/epg/EpgDatabase.h b/xbmc/pvr/epg/EpgDatabase.h index 077a7fbb37fff..33c2e7d8fd79d 100644 --- a/xbmc/pvr/epg/EpgDatabase.h +++ b/xbmc/pvr/epg/EpgDatabase.h @@ -113,9 +113,18 @@ namespace PVR /*! * @brief Get all icon paths for a given EPG id. * @param iEpgID The ID of the EPG. - * @return The entries. + * @param path The paths returned. + * @return True on success, false otherwise. + */ + bool GetAllIconPaths(int iEpgID, std::vector<std::string>& paths) const; + + /*! + * @brief Get all parental rating icon paths for a given EPG id. + * @param iEpgID The ID of the EPG. + * @param path The paths returned. + * @return True on success, false otherwise. */ - std::vector<std::string> GetAllIconPaths(int iEpgID) const; + bool GetAllParentalRatingIconPaths(int iEpgID, std::vector<std::string>& paths) const; /*! * @brief Check whether this EPG has any tags. diff --git a/xbmc/pvr/epg/EpgInfoTag.cpp b/xbmc/pvr/epg/EpgInfoTag.cpp index cafb7aceb4aab..648789327afc5 100644 --- a/xbmc/pvr/epg/EpgInfoTag.cpp +++ b/xbmc/pvr/epg/EpgInfoTag.cpp @@ -32,8 +32,11 @@ using namespace PVR; const std::string CPVREpgInfoTag::IMAGE_OWNER_PATTERN = "epgtag_{}"; -CPVREpgInfoTag::CPVREpgInfoTag(int iEpgID, const std::string& iconPath) - : m_iUniqueBroadcastID(EPG_TAG_INVALID_UID), +CPVREpgInfoTag::CPVREpgInfoTag(int iEpgID, + const std::string& iconPath, + const std::string& parentalRatingIconPath) + : m_parentalRatingIcon(parentalRatingIconPath, StringUtils::Format(IMAGE_OWNER_PATTERN, iEpgID)), + m_iUniqueBroadcastID(EPG_TAG_INVALID_UID), m_iconPath(iconPath, StringUtils::Format(IMAGE_OWNER_PATTERN, iEpgID)), m_iFlags(EPG_TAG_FLAG_UNDEFINED), m_channelData(new CPVREpgChannelData), @@ -46,7 +49,8 @@ CPVREpgInfoTag::CPVREpgInfoTag(const std::shared_ptr<CPVREpgChannelData>& channe const CDateTime& start, const CDateTime& end, bool bIsGapTag) - : m_iUniqueBroadcastID(EPG_TAG_INVALID_UID), + : m_parentalRatingIcon(StringUtils::Format(IMAGE_OWNER_PATTERN, iEpgID)), + m_iUniqueBroadcastID(EPG_TAG_INVALID_UID), m_iconPath(StringUtils::Format(IMAGE_OWNER_PATTERN, iEpgID)), m_iFlags(EPG_TAG_FLAG_UNDEFINED), m_bIsGapTag(bIsGapTag), @@ -70,6 +74,8 @@ CPVREpgInfoTag::CPVREpgInfoTag(const EPG_TAG& data, : m_iGenreType(data.iGenreType), m_iGenreSubType(data.iGenreSubType), m_parentalRating(data.iParentalRating), + m_parentalRatingIcon(data.strParentalRatingIcon ? data.strParentalRatingIcon : "", + StringUtils::Format(IMAGE_OWNER_PATTERN, iEpgID)), m_iStarRating(data.iStarRating), m_iSeriesNumber(data.iSeriesNumber), m_iEpisodeNumber(data.iEpisodeNumber), @@ -134,8 +140,6 @@ CPVREpgInfoTag::CPVREpgInfoTag(const EPG_TAG& data, m_strSeriesLink = data.strSeriesLink; if (data.strParentalRatingCode) m_parentalRatingCode = data.strParentalRatingCode; - if (data.strParentalRatingIcon) - m_parentalRatingIcon = data.strParentalRatingIcon; if (data.strParentalRatingSource) m_parentalRatingSource = data.strParentalRatingSource; } @@ -176,7 +180,7 @@ void CPVREpgInfoTag::Serialize(CVariant& value) const value["channeluid"] = m_channelData->UniqueClientChannelId(); value["parentalrating"] = m_parentalRating; value["parentalratingcode"] = m_parentalRatingCode; - value["parentalratingicon"] = m_parentalRatingIcon; + value["parentalratingicon"] = ClientParentalRatingIconPath(); value["parentalratingsource"] = m_parentalRatingSource; value["rating"] = m_iStarRating; value["title"] = m_strTitle; diff --git a/xbmc/pvr/epg/EpgInfoTag.h b/xbmc/pvr/epg/EpgInfoTag.h index 65858dca2bfd9..654049fb1e985 100644 --- a/xbmc/pvr/epg/EpgInfoTag.h +++ b/xbmc/pvr/epg/EpgInfoTag.h @@ -319,7 +319,13 @@ class CPVREpgInfoTag final : public ISerializable, * @brief Get the parental rating icon path of this event. * @return Path to the parental rating icon. */ - const std::string& ParentalRatingIcon() const { return m_parentalRatingIcon; } + const std::string& ParentalRatingIcon() const { return m_parentalRatingIcon.GetLocalImage(); } + + /*! + * @brief Get the parental rating icon path of this event as given by the client. + * @return The path to the icon + */ + std::string ClientParentalRatingIconPath() const { return m_parentalRatingIcon.GetClientImage(); } /*! * @brief Get the parental rating source of this event. @@ -485,7 +491,9 @@ class CPVREpgInfoTag final : public ISerializable, static const std::string DeTokenize(const std::vector<std::string>& tokens); private: - CPVREpgInfoTag(int iEpgID, const std::string& iconPath); + CPVREpgInfoTag(int iEpgID, + const std::string& iconPath, + const std::string& parentalRatingIconPath); CPVREpgInfoTag() = delete; CPVREpgInfoTag(const CPVREpgInfoTag& tag) = delete; @@ -503,7 +511,7 @@ class CPVREpgInfoTag final : public ISerializable, std::string m_strGenreDescription; /*!< genre description */ unsigned int m_parentalRating = 0; /*!< parental rating */ std::string m_parentalRatingCode; /*!< Parental rating code */ - std::string m_parentalRatingIcon; /*!< parental rating icon path */ + CPVRCachedImage m_parentalRatingIcon; /*!< parental rating icon path */ std::string m_parentalRatingSource; /*!< parental rating source */ int m_iStarRating = 0; /*!< star rating */ int m_iSeriesNumber = -1; /*!< series number */ diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp index 9994a0a621f71..683a8095b86bc 100644 --- a/xbmc/pvr/recordings/PVRRecording.cpp +++ b/xbmc/pvr/recordings/PVRRecording.cpp @@ -69,7 +69,8 @@ const std::string CPVRRecording::IMAGE_OWNER_PATTERN = "pvrrecording"; CPVRRecording::CPVRRecording() : m_iconPath(IMAGE_OWNER_PATTERN), m_thumbnailPath(IMAGE_OWNER_PATTERN), - m_fanartPath(IMAGE_OWNER_PATTERN) + m_fanartPath(IMAGE_OWNER_PATTERN), + m_parentalRatingIcon(IMAGE_OWNER_PATTERN) { Reset(); } @@ -78,7 +79,9 @@ CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClien : m_iconPath(recording.strIconPath ? recording.strIconPath : "", IMAGE_OWNER_PATTERN), m_thumbnailPath(recording.strThumbnailPath ? recording.strThumbnailPath : "", IMAGE_OWNER_PATTERN), - m_fanartPath(recording.strFanartPath ? recording.strFanartPath : "", IMAGE_OWNER_PATTERN) + m_fanartPath(recording.strFanartPath ? recording.strFanartPath : "", IMAGE_OWNER_PATTERN), + m_parentalRatingIcon(recording.strParentalRatingIcon ? recording.strParentalRatingIcon : "", + IMAGE_OWNER_PATTERN) { Reset(); @@ -132,8 +135,6 @@ CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClien m_parentalRating = recording.iParentalRating; if (recording.strParentalRatingCode) m_parentalRatingCode = recording.strParentalRatingCode; - if (recording.strParentalRatingIcon) - m_parentalRatingIcon = recording.strParentalRatingIcon; if (recording.strParentalRatingSource) m_parentalRatingSource = recording.strParentalRatingSource; @@ -222,7 +223,7 @@ void CPVRRecording::Serialize(CVariant& value) const value["genre"] = m_genre; value["parentalrating"] = m_parentalRating; value["parentalratingcode"] = m_parentalRatingCode; - value["parentalratingicon"] = m_parentalRatingIcon; + value["parentalratingicon"] = ClientParentalRatingIconPath(); value["parentalratingsource"] = m_parentalRatingSource; value["episodepart"] = m_episodePartNumber; value["titleextrainfo"] = m_titleExtraInfo; @@ -279,7 +280,6 @@ void CPVRRecording::Reset() m_parentalRating = 0; m_parentalRatingCode.clear(); - m_parentalRatingIcon.clear(); m_parentalRatingSource.clear(); m_titleExtraInfo.clear(); @@ -749,7 +749,7 @@ const std::string& CPVRRecording::GetParentalRatingCode() const const std::string& CPVRRecording::GetParentalRatingIcon() const { std::unique_lock<CCriticalSection> lock(m_critSection); - return m_parentalRatingIcon; + return m_parentalRatingIcon.GetLocalImage(); } const std::string& CPVRRecording::GetParentalRatingSource() const diff --git a/xbmc/pvr/recordings/PVRRecording.h b/xbmc/pvr/recordings/PVRRecording.h index 49c18abfb4383..dc5b61f9cb6c9 100644 --- a/xbmc/pvr/recordings/PVRRecording.h +++ b/xbmc/pvr/recordings/PVRRecording.h @@ -327,6 +327,15 @@ class CPVRRecording final : public CVideoInfoTag */ const std::string& ClientFanartPath() const { return m_fanartPath.GetClientImage(); } + /*! + * @brief Return the parental rating icon path as given by the client. + * @return The path. + */ + const std::string& ClientParentalRatingIconPath() const + { + return m_parentalRatingIcon.GetClientImage(); + } + /*! * @brief Return the icon path used by Kodi. * @return The path. @@ -572,7 +581,7 @@ class CPVRRecording final : public CVideoInfoTag PVR_PROVIDER_INVALID_UID; /*!< provider uid associated with this recording on the client */ unsigned int m_parentalRating{0}; /*!< parental rating */ std::string m_parentalRatingCode; /*!< Parental rating code */ - std::string m_parentalRatingIcon; /*!< parental rating icon path */ + CPVRCachedImage m_parentalRatingIcon; /*!< parental rating icon path */ std::string m_parentalRatingSource; /*!< parental rating source */ int m_episodePartNumber{PVR_RECORDING_INVALID_SERIES_EPISODE}; /*!< episode part number */ std::string m_titleExtraInfo; /*!< title extra info */ diff --git a/xbmc/pvr/recordings/PVRRecordings.cpp b/xbmc/pvr/recordings/PVRRecordings.cpp index 363196b1d0bb1..aa4f5f0ae09d1 100644 --- a/xbmc/pvr/recordings/PVRRecordings.cpp +++ b/xbmc/pvr/recordings/PVRRecordings.cpp @@ -380,12 +380,14 @@ int CPVRRecordings::CleanupCachedImages() urlsToCheck.emplace_back(recording.second->ClientIconPath()); urlsToCheck.emplace_back(recording.second->ClientThumbnailPath()); urlsToCheck.emplace_back(recording.second->ClientFanartPath()); + urlsToCheck.emplace_back(recording.second->ClientParentalRatingIconPath()); urlsToCheck.emplace_back(recording.second->m_strFileNameAndPath); } } static const std::vector<PVRImagePattern> urlPatterns = { - {CPVRRecording::IMAGE_OWNER_PATTERN, ""}, // client-supplied icon, thumbnail, fanart + {CPVRRecording::IMAGE_OWNER_PATTERN, + ""}, // client-supplied icon, thumbnail, fanart, parental rating icon {"video", "pvr://recordings/"}, // kodi-generated video thumbnail }; return CPVRCachedImages::Cleanup(urlPatterns, urlsToCheck); From c39d63117ba907b77a6af28f00f93db832e6bcc6 Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Sun, 3 Nov 2024 18:11:24 -0500 Subject: [PATCH 644/651] Fix Stream Details Save After Playback - no need to save stream details for PVR live streams - they're bound to change many times - restore logic calculating the file to save the stream details --- xbmc/utils/SaveFileStateJob.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xbmc/utils/SaveFileStateJob.cpp b/xbmc/utils/SaveFileStateJob.cpp index 27653ce1c14dc..be2b6c7c883cd 100644 --- a/xbmc/utils/SaveFileStateJob.cpp +++ b/xbmc/utils/SaveFileStateJob.cpp @@ -171,7 +171,8 @@ void CSaveFileState::DoWork(CFileItem& item, } } - if (item.HasVideoInfoTag() && item.GetVideoInfoTag()->HasStreamDetails()) + if (item.HasVideoInfoTag() && item.GetVideoInfoTag()->HasStreamDetails() && + !item.IsLiveTV()) { CFileItem dbItem(item); @@ -180,7 +181,7 @@ void CSaveFileState::DoWork(CFileItem& item, dbItem.GetVideoInfoTag()->m_streamDetails != item.GetVideoInfoTag()->m_streamDetails) { const int idFile = videodatabase.SetStreamDetailsForFile( - item.GetVideoInfoTag()->m_streamDetails, item.GetDynPath()); + item.GetVideoInfoTag()->m_streamDetails, progressTrackingFile); if (item.GetVideoContentType() == VideoDbContentType::MOVIES) videodatabase.SetFileForMovie(item.GetDynPath(), item.GetVideoInfoTag()->m_iDbId, idFile); From 8a159dae154f3238a326e80de06cea54910f3494 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:53:48 +0100 Subject: [PATCH 645/651] [guiinfo][PVR] New GUI info labels: PVR.ClientName, PVR.InstanceName. --- xbmc/GUIInfoManager.cpp | 23 +++++++++++++++++++++-- xbmc/guilib/guiinfo/GUIInfoLabels.h | 2 ++ xbmc/pvr/addons/PVRClients.cpp | 2 ++ xbmc/pvr/addons/PVRClients.h | 2 ++ xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 24 ++++++++++++++++++++++++ xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h | 4 ++++ 6 files changed, 55 insertions(+), 2 deletions(-) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 6ade6e27938dc..d2ddd18b6a093 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -7087,7 +7087,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \anchor ListItem_PVRClientName /// _string_, /// @return If selected item is of type PVR (recording\, timer\, EPG)\, the name of the PVR client -/// add-on\, as specified by the add-on developer. Empty if theitem is not of type PVR. +/// add-on\, as specified by the add-on developer. Empty if the item is not of type PVR. /// <p><hr> /// @skinning_v22 **[New Infolabel]** \link ListItem_PVRClientName `ListItem.PVRClientName`\endlink /// <p> @@ -8384,6 +8384,23 @@ const infomap playlist[] = {{ "length", PLAYLIST_LENGTH }, /// @skinning_v22 **[New Integer Value]** \link PVR_ClientCount `PVR.ClientCount`\endlink /// <p> /// } +/// \table_row3{ <b>`PVR.ClientName`</b>, +/// \anchor PVR_ClientName +/// _string_, +/// @return The name of the PVR client add-on\, as specified by the add-on developer. +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link PVR_ClientName `PVR.ClientName`\endlink +/// <p> +/// } +/// \table_row3{ <b>`PVR.InstanceName`</b>, +/// \anchor PVR_InstanceName +/// _string_, +/// @return The name of the instance of the PVR client add-on\, as specified by the user in +/// the add-on settings. Empty if the PVR client add-on does not support multiple instances. +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link PVR_InstanceName `PVR.InstanceName`\endlink +/// <p> +/// } /// \table_end /// /// ----------------------------------------------------------------------------- @@ -8467,7 +8484,9 @@ const infomap pvr[] = {{ "isrecording", PVR_IS_RECORDING { "timeshiftprogressbufferstart", PVR_TIMESHIFT_PROGRESS_BUFFER_START }, { "timeshiftprogressbufferend", PVR_TIMESHIFT_PROGRESS_BUFFER_END }, { "epgeventicon", PVR_EPG_EVENT_ICON }, - { "clientcount", PVR_CLIENT_COUNT }}; + { "clientcount", PVR_CLIENT_COUNT }, + { "clientname", PVR_CLIENT_NAME }, + { "instancename", PVR_INSTANCE_NAME }}; // clang-format on /// \page modules__infolabels_boolean_conditions diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index 4a688123f282b..98f9d4a681c3b 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -684,6 +684,8 @@ static constexpr unsigned int SYSTEM_LOCALE = 1012; #define PVR_TIMESHIFT_SEEKBAR (PVR_STRINGS_START + 73) #define PVR_BACKEND_PROVIDERS (PVR_STRINGS_START + 74) #define PVR_BACKEND_CHANNEL_GROUPS (PVR_STRINGS_START + 75) +#define PVR_CLIENT_NAME (PVR_STRINGS_START + 76) +#define PVR_INSTANCE_NAME (PVR_STRINGS_START + 77) #define PVR_INTS_START 1300 #define PVR_CLIENT_COUNT (PVR_INTS_START) diff --git a/xbmc/pvr/addons/PVRClients.cpp b/xbmc/pvr/addons/PVRClients.cpp index a67fe724ee772..32f290dcee752 100644 --- a/xbmc/pvr/addons/PVRClients.cpp +++ b/xbmc/pvr/addons/PVRClients.cpp @@ -600,6 +600,8 @@ std::vector<SBackend> CPVRClients::GetBackendProperties() const properties.numRecordings = iAmount; if (client->GetRecordingsAmount(true, iAmount) == PVR_ERROR_NO_ERROR) properties.numDeletedRecordings = iAmount; + properties.clientname = client->GetClientName(); + properties.instancename = client->GetInstanceName(); properties.name = client->GetBackendName(); properties.version = client->GetBackendVersion(); properties.host = client->GetConnectionString(); diff --git a/xbmc/pvr/addons/PVRClients.h b/xbmc/pvr/addons/PVRClients.h index a7724b9640df2..a041ed46fe67b 100644 --- a/xbmc/pvr/addons/PVRClients.h +++ b/xbmc/pvr/addons/PVRClients.h @@ -46,6 +46,8 @@ typedef std::map<int, std::shared_ptr<CPVRClient>> CPVRClientMap; */ struct SBackend { + std::string clientname; + std::string instancename; std::string name; std::string version; std::string host; diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index dd9396a0ff2c2..fbf4cf3344832 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -76,6 +76,8 @@ void CPVRGUIInfo::ResetProperties() m_bHasRadioRecordings = false; m_iCurrentActiveClient = 0; m_strPlayingClientName.clear(); + m_strClientName.clear(); + m_strInstanceName.clear(); m_strBackendName.clear(); m_strBackendVersion.clear(); m_strBackendHost.clear(); @@ -1172,6 +1174,12 @@ bool CPVRGUIInfo::GetPVRLabel(const CFileItem* item, case PVR_ACTUAL_STREAM_PROVIDER: CharInfoProvider(strValue); return true; + case PVR_CLIENT_NAME: + CharInfoClientName(strValue); + return true; + case PVR_INSTANCE_NAME: + CharInfoInstanceName(strValue); + return true; case PVR_BACKEND_NAME: CharInfoBackendName(strValue); return true; @@ -1960,6 +1968,18 @@ void CPVRGUIInfo::CharInfoFrontendStatus(std::string& strValue) const strValue = g_localizeStrings.Get(13205); } +void CPVRGUIInfo::CharInfoClientName(std::string& strValue) const +{ + m_updateBackendCacheRequested = true; + strValue = m_strClientName; +} + +void CPVRGUIInfo::CharInfoInstanceName(std::string& strValue) const +{ + m_updateBackendCacheRequested = true; + strValue = m_strInstanceName; +} + void CPVRGUIInfo::CharInfoBackendName(std::string& strValue) const { m_updateBackendCacheRequested = true; @@ -2101,6 +2121,8 @@ void CPVRGUIInfo::UpdateBackendCache() } // Store some defaults + m_strClientName = g_localizeStrings.Get(13205); + m_strInstanceName = g_localizeStrings.Get(13205); m_strBackendName = g_localizeStrings.Get(13205); m_strBackendVersion = g_localizeStrings.Get(13205); m_strBackendHost = g_localizeStrings.Get(13205); @@ -2118,6 +2140,8 @@ void CPVRGUIInfo::UpdateBackendCache() { const auto& backend = m_backendProperties[m_iCurrentActiveClient]; + m_strClientName = backend.clientname; + m_strInstanceName = backend.instancename; m_strBackendName = backend.name; m_strBackendVersion = backend.version; m_strBackendHost = backend.host; diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h index 50f01d6ec3062..0c0d0794a9d25 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h @@ -142,6 +142,8 @@ class CPVRGUIInfo : public KODI::GUILIB::GUIINFO::CGUIInfoProvider, private CThr void CharInfoUNC(std::string& strValue) const; void CharInfoFrontendName(std::string& strValue) const; void CharInfoFrontendStatus(std::string& strValue) const; + void CharInfoClientName(std::string& strValue) const; + void CharInfoInstanceName(std::string& strValue) const; void CharInfoBackendName(std::string& strValue) const; void CharInfoBackendVersion(std::string& strValue) const; void CharInfoBackendHost(std::string& strValue) const; @@ -170,6 +172,8 @@ class CPVRGUIInfo : public KODI::GUILIB::GUIINFO::CGUIInfoProvider, private CThr bool m_bHasRadioRecordings; unsigned int m_iCurrentActiveClient; std::string m_strPlayingClientName; + std::string m_strClientName; + std::string m_strInstanceName; std::string m_strBackendName; std::string m_strBackendVersion; std::string m_strBackendHost; From ef900e3061073dada1ea7104533b8790c67eb4aa Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 27 Oct 2024 00:10:45 +0200 Subject: [PATCH 646/651] [Estuary] Re-introduce and improve backend disk usage display in PVR recordings window. --- addons/skin.estuary/xml/Includes_PVR.xml | 290 +++++++++++--------- addons/skin.estuary/xml/MyPVRRecordings.xml | 16 +- 2 files changed, 177 insertions(+), 129 deletions(-) diff --git a/addons/skin.estuary/xml/Includes_PVR.xml b/addons/skin.estuary/xml/Includes_PVR.xml index 674f0ba835641..70b5c5f6352e9 100644 --- a/addons/skin.estuary/xml/Includes_PVR.xml +++ b/addons/skin.estuary/xml/Includes_PVR.xml @@ -366,144 +366,147 @@ </control> </include> <include name="PVRInfoPanel"> - <control type="group"> - <visible>!ListItem.IsFolder</visible> - <control type="image"> - <top>135</top> - <left>630</left> - <width>200</width> - <height>200</height> - <aspectratio align="center" aligny="center">keep</aspectratio> - <texture fallback="DefaultTVShows.png">$VAR[ChannelListEPGIconVar]</texture> - <visible>!String.IsEmpty(ListItem.ChannelName)</visible> - <fadetime>200</fadetime> - </control> + <param name="bottom">list_bottom_offset</param> + <definition> <control type="group"> - <top>120</top> - <left>0</left> - <width>600</width> + <visible>!ListItem.IsFolder</visible> + <control type="image"> + <top>135</top> + <left>630</left> + <width>200</width> + <height>200</height> + <aspectratio align="center" aligny="center">keep</aspectratio> + <texture fallback="DefaultTVShows.png">$VAR[ChannelListEPGIconVar]</texture> + <visible>!String.IsEmpty(ListItem.ChannelName)</visible> + <fadetime>200</fadetime> + </control> + <control type="group"> + <top>120</top> + <left>0</left> + <width>600</width> + <control type="label"> + <height>262</height> + <font>font45</font> + <label>$INFO[ListItem.ChannelName]</label> + </control> + <control type="label"> + <top>60</top> + <height>200</height> + <label>$VAR[PVRInfoPanelDateDurationLabel]</label> + </control> + <control type="progress"> + <top>200</top> + <height>12</height> + <colordiffuse>88FFFFFF</colordiffuse> + <info>ListItem.Progress</info> + <visible>Integer.IsGreater(ListItem.Progress,0)</visible> + </control> + <control type="progress"> + <top>200</top> + <height>12</height> + <colordiffuse>88FFFFFF</colordiffuse> + <info>ListItem.PercentPlayed</info> + <visible>Integer.IsGreater(ListItem.PercentPlayed,0)</visible> + </control> + </control> <control type="label"> + <top>365</top> + <width>830</width> <height>262</height> - <font>font45</font> - <label>$INFO[ListItem.ChannelName]</label> + <font>font36_title</font> + <label>$INFO[ListItem.Title] $INFO[ListItem.Year,([COLOR grey],[/COLOR])]</label> + <scroll>true</scroll> + <visible>!ListItem.HasEpg</visible> </control> <control type="label"> - <top>60</top> - <height>200</height> - <label>$VAR[PVRInfoPanelDateDurationLabel]</label> + <top>365</top> + <width>830</width> + <height>262</height> + <font>font36_title</font> + <label>$INFO[ListItem.EpgEventTitle] $INFO[ListItem.Year,([COLOR grey],[/COLOR])]</label> + <scroll>true</scroll> + <visible>ListItem.HasEpg</visible> </control> - <control type="progress"> - <top>200</top> - <height>12</height> - <colordiffuse>88FFFFFF</colordiffuse> - <info>ListItem.Progress</info> - <visible>Integer.IsGreater(ListItem.Progress,0)</visible> + <control type="label"> + <top>410</top> + <width>830</width> + <height>70</height> + <scroll>true</scroll> + <label>[I]$VAR[SeasonEpisodeAndNameLabel][/I]</label> </control> - <control type="progress"> - <top>200</top> - <height>12</height> - <colordiffuse>88FFFFFF</colordiffuse> - <info>ListItem.PercentPlayed</info> - <visible>Integer.IsGreater(ListItem.PercentPlayed,0)</visible> + <control type="textbox"> + <top>465</top> + <width>830</width> + <bottom>$PARAM[bottom]</bottom> + <label>$VAR[PVRInstanceName,,[CR]]$INFO[ListItem.MediaProviders,[COLOR grey]$LOCALIZE[19334]:[/COLOR] ,[CR]]$VAR[FlagLabel,,[CR]]$INFO[ListItem.Genre,[COLOR grey]$LOCALIZE[515]:[/COLOR] ,[CR]]$INFO[ListItem.ParentalRatingCode,[COLOR grey]$LOCALIZE[31017]: [/COLOR],[CR]]$INFO[ListItem.TimerType,[COLOR grey]$LOCALIZE[803]:[/COLOR] ,[CR]]$VAR[RecordingSizeLabel]$VAR[ExpirationDateTimeLabel]$INFO[ListItem.Plot,[CR]]</label> + <autoscroll delay="10000" time="3000" repeat="10000">Skin.HasSetting(AutoScroll)</autoscroll> </control> </control> - <control type="label"> - <top>365</top> - <width>830</width> - <height>262</height> - <font>font36_title</font> - <label>$INFO[ListItem.Title] $INFO[ListItem.Year,([COLOR grey],[/COLOR])]</label> - <scroll>true</scroll> - <visible>!ListItem.HasEpg</visible> - </control> - <control type="label"> - <top>365</top> - <width>830</width> - <height>262</height> - <font>font36_title</font> - <label>$INFO[ListItem.EpgEventTitle] $INFO[ListItem.Year,([COLOR grey],[/COLOR])]</label> - <scroll>true</scroll> - <visible>ListItem.HasEpg</visible> - </control> - <control type="label"> - <top>410</top> - <width>830</width> - <height>70</height> - <scroll>true</scroll> - <label>[I]$VAR[SeasonEpisodeAndNameLabel][/I]</label> - </control> - <control type="textbox"> - <top>465</top> - <width>830</width> - <bottom>list_bottom_offset</bottom> - <label>$VAR[PVRInstanceName,,[CR]]$INFO[ListItem.MediaProviders,[COLOR grey]$LOCALIZE[19334]:[/COLOR] ,[CR]]$VAR[FlagLabel,,[CR]]$INFO[ListItem.Genre,[COLOR grey]$LOCALIZE[515]:[/COLOR] ,[CR]]$INFO[ListItem.ParentalRatingCode,[COLOR grey]$LOCALIZE[31017]: [/COLOR],[CR]]$INFO[ListItem.TimerType,[COLOR grey]$LOCALIZE[803]:[/COLOR] ,[CR]]$VAR[RecordingSizeLabel]$VAR[ExpirationDateTimeLabel]$INFO[ListItem.Plot,[CR]]</label> - <autoscroll delay="10000" time="3000" repeat="10000">Skin.HasSetting(AutoScroll)</autoscroll> - </control> - </control> - <control type="group"> - <visible>ListItem.IsFolder</visible> - <top>list_top_offset</top> - <control type="label"> - <top>10</top> - <width>830</width> - <height>262</height> - <label>$LOCALIZE[19076] ($INFO[Container(5000).NumItems,[B],[/B] $LOCALIZE[31036]]) $INFO[ListItem.Property(recordingsize),- $LOCALIZE[20161]: [B],[/B]]</label> - <font>font37</font> - <visible>!ListItem.IsParentFolder</visible> - </control> <control type="group"> - <left>-10</left> - <top>60</top> - <visible>!ListItem.IsParentFolder</visible> - <control type="panel" id="5000"> - <top>20</top> + <visible>ListItem.IsFolder</visible> + <top>list_top_offset</top> + <control type="label"> + <top>10</top> <width>830</width> - <bottom>100</bottom> - <orientation>vertical</orientation> - <focusedlayout height="100" width="780"> - <control type="label"> - <visible>!String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible> - <left>10</left> - <height>90</height> - <width>830</width> - <aligny>center</aligny> - <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$VAR[SeasonEpisodeAndNameLabel, (,)]$INFO[ListItem.Property(totalcount), (, $LOCALIZE[31036])]</label> - <shadowcolor>text_shadow</shadowcolor> - </control> - <control type="label"> - <visible>String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible> - <left>10</left> - <height>90</height> - <width>830</width> - <aligny>center</aligny> - <label>[COLOR grey]$INFO[ListItem.ChannelName,,[CR]][/COLOR]$INFO[ListItem.EpgEventTitle]$VAR[SeasonEpisodeAndNameLabel, (,)]</label> - <shadowcolor>text_shadow</shadowcolor> - </control> - </focusedlayout> - <itemlayout height="100" width="780"> - <control type="label"> - <visible>!String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible> - <left>10</left> - <height>90</height> - <width>830</width> - <aligny>center</aligny> - <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$VAR[SeasonEpisodeAndNameLabel, (,)]$INFO[ListItem.Property(totalcount), (, $LOCALIZE[31036])]</label> - <shadowcolor>text_shadow</shadowcolor> - </control> - <control type="label"> - <visible>String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible> - <left>10</left> - <height>90</height> - <width>830</width> - <aligny>center</aligny> - <label>[COLOR grey]$INFO[ListItem.ChannelName,,[CR]][/COLOR]$INFO[ListItem.EpgEventTitle]$VAR[SeasonEpisodeAndNameLabel, (,)]</label> - <shadowcolor>text_shadow</shadowcolor> - </control> - </itemlayout> - <content sortby="$PARAM[folder_sortby]" sortorder="$PARAM[folder_sortorder]">$INFO[ListItem.FilenameAndPath]</content> + <height>262</height> + <label>$LOCALIZE[19076] ($INFO[Container(5000).NumItems,[B],[/B] $LOCALIZE[31036]]) $INFO[ListItem.Property(recordingsize),- $LOCALIZE[20161]: [B],[/B]]</label> + <font>font37</font> + <visible>!ListItem.IsParentFolder</visible> + </control> + <control type="group"> + <left>-10</left> + <top>60</top> + <visible>!ListItem.IsParentFolder</visible> + <control type="panel" id="5000"> + <top>20</top> + <width>830</width> + <bottom>$PARAM[bottom]</bottom> + <orientation>vertical</orientation> + <focusedlayout height="100" width="780"> + <control type="label"> + <visible>!String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible> + <left>10</left> + <height>90</height> + <width>830</width> + <aligny>center</aligny> + <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$VAR[SeasonEpisodeAndNameLabel, (,)]$INFO[ListItem.Property(totalcount), (, $LOCALIZE[31036])]</label> + <shadowcolor>text_shadow</shadowcolor> + </control> + <control type="label"> + <visible>String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible> + <left>10</left> + <height>90</height> + <width>830</width> + <aligny>center</aligny> + <label>[COLOR grey]$INFO[ListItem.ChannelName,,[CR]][/COLOR]$INFO[ListItem.EpgEventTitle]$VAR[SeasonEpisodeAndNameLabel, (,)]</label> + <shadowcolor>text_shadow</shadowcolor> + </control> + </focusedlayout> + <itemlayout height="100" width="780"> + <control type="label"> + <visible>!String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible> + <left>10</left> + <height>90</height> + <width>830</width> + <aligny>center</aligny> + <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$VAR[SeasonEpisodeAndNameLabel, (,)]$INFO[ListItem.Property(totalcount), (, $LOCALIZE[31036])]</label> + <shadowcolor>text_shadow</shadowcolor> + </control> + <control type="label"> + <visible>String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible> + <left>10</left> + <height>90</height> + <width>830</width> + <aligny>center</aligny> + <label>[COLOR grey]$INFO[ListItem.ChannelName,,[CR]][/COLOR]$INFO[ListItem.EpgEventTitle]$VAR[SeasonEpisodeAndNameLabel, (,)]</label> + <shadowcolor>text_shadow</shadowcolor> + </control> + </itemlayout> + <content sortby="$PARAM[folder_sortby]" sortorder="$PARAM[folder_sortorder]">$INFO[ListItem.FilenameAndPath]</content> + </control> </control> </control> - </control> + </definition> </include> <include name="RDSInfoLine"> <control type="grouplist"> @@ -767,4 +770,41 @@ </control> </definition> </include> + <include name="PVRBackendDiskspace"> + <control type="label"> + <left>0</left> + <bottom>70</bottom> + <width>830</width> + <height>20</height> + <font>font37</font> + <label>$INFO[PVR.ClientName]$INFO[PVR.InstanceName, (,)]</label> + </control> + <control type="group"> + <visible>!Integer.IsGreater(PVR.BackendDiskspaceProgr,100)</visible> + <control type="label"> + <left>0</left> + <bottom>20</bottom> + <width>610</width> + <height>20</height> + <label>[COLOR grey]$LOCALIZE[19116]:[/COLOR] $INFO[PVR.BackendDiskspace]</label> + </control> + <control type="progress"> + <right>25</right> + <bottom>10</bottom> + <width>200</width> + <height>12</height> + <info>PVR.BackendDiskspaceProgr</info> + </control> + </control> + <control type="group"> + <visible>Integer.IsGreater(PVR.BackendDiskspaceProgr,100)</visible> + <control type="label"> + <left>0</left> + <bottom>20</bottom> + <width>610</width> + <height>20</height> + <label>[COLOR grey]$LOCALIZE[19116]:[/COLOR] $LOCALIZE[13205]</label> + </control> + </control> + </include> </includes> diff --git a/addons/skin.estuary/xml/MyPVRRecordings.xml b/addons/skin.estuary/xml/MyPVRRecordings.xml index 16cf5a4cddabb..9d615f3f335ed 100644 --- a/addons/skin.estuary/xml/MyPVRRecordings.xml +++ b/addons/skin.estuary/xml/MyPVRRecordings.xml @@ -53,10 +53,18 @@ <orientation>vertical</orientation> <animation effect="zoom" start="100,100" end="50,100" center="-50,0" time="300" tween="sine" easing="inout" condition="!Control.HasFocus(73)">conditional</animation> </control> - <include content="PVRInfoPanel"> - <param name="folder_sortby" value="date" /> - <param name="folder_sortorder" value="descending" /> - </include> + <control type="group"> + <include content="PVRInfoPanel"> + <param name="folder_sortby" value="date" /> + <param name="folder_sortorder" value="descending" /> + <param name="bottom" value="200" /> + </include> + <control type="group"> + <bottom>list_bottom_offset</bottom> + <height>60</height> + <include content="PVRBackendDiskspace"/> + </control> + </control> </control> <include content="TopBar"> <param name="breadcrumbs_label" value="$VAR[BreadcrumbsPVRRecordingsVar]" /> From 8eb1caefaa02cea5462469ab460e84aeff82551e Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Fri, 1 Nov 2024 18:23:35 -0400 Subject: [PATCH 647/651] [videodb] Remove nested transaction in video version type population --- xbmc/video/VideoDatabase.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index c5500a4b277ef..747b21a8f2507 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -208,6 +208,7 @@ void CVideoDatabase::CreateTables() CLog::Log(LOGINFO, "create videoversiontype table"); m_pDS->exec("CREATE TABLE videoversiontype (id INTEGER PRIMARY KEY, name TEXT, owner INTEGER, " "itemType INTEGER)"); + CLog::Log(LOGINFO, "populate videoversiontype table"); InitializeVideoVersionTypeTable(GetSchemaVersion()); CLog::Log(LOGINFO, "create videoversion table"); @@ -12013,10 +12014,10 @@ std::string CVideoDatabase::GetVideoItemTitle(VideoDbContentType itemType, int d void CVideoDatabase::InitializeVideoVersionTypeTable(int schemaVersion) { + assert(m_pDB->in_transaction()); + try { - BeginTransaction(); - for (int id = VIDEO_VERSION_ID_BEGIN; id <= VIDEO_VERSION_ID_END; ++id) { // Exclude removed pre-populated "quality" values @@ -12037,13 +12038,11 @@ void CVideoDatabase::InitializeVideoVersionTypeTable(int schemaVersion) type.c_str(), VideoAssetTypeOwner::SYSTEM, VideoAssetType::VERSION)); } } - - CommitTransaction(); } catch (...) { - CLog::Log(LOGERROR, "{} failed", __FUNCTION__); - RollbackTransaction(); + CLog::LogF(LOGERROR, "failed"); + throw; } } From c060e207c8a2cbc5251a257571e18e182a73804d Mon Sep 17 00:00:00 2001 From: CrystalP <crystalp@kodi.tv> Date: Sun, 3 Nov 2024 15:23:12 -0500 Subject: [PATCH 648/651] [videodb] Remove nested transaction when saving state after stopping PVR playback More work is needed to fix CSaveFileState::DoWork() error/transaction handling and restore data integrity. Only the immediate area of the issue was addressed in this commit. --- xbmc/utils/SaveFileStateJob.cpp | 31 +++++++++++++++++++++++-------- xbmc/video/VideoDatabase.cpp | 22 ++++++++++++---------- xbmc/video/VideoDatabase.h | 3 ++- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/xbmc/utils/SaveFileStateJob.cpp b/xbmc/utils/SaveFileStateJob.cpp index be2b6c7c883cd..a771896e6539a 100644 --- a/xbmc/utils/SaveFileStateJob.cpp +++ b/xbmc/utils/SaveFileStateJob.cpp @@ -86,7 +86,9 @@ void CSaveFileState::DoWork(CFileItem& item, } else { + //! @todo check possible failure of BeginTransaction videodatabase.BeginTransaction(); + bool videoDbSuccess{true}; if (URIUtils::IsPlugin(progressTrackingFile) && !(item.HasVideoInfoTag() && item.GetVideoInfoTag()->m_iDbId >= 0)) { @@ -105,6 +107,7 @@ void CSaveFileState::DoWork(CFileItem& item, // No resume & watched status for livetv if (!item.IsLiveTV()) { + //! @todo handle db failures to maintain data integrity if (updatePlayCount) { // no watched for not yet finished pvr recordings @@ -182,17 +185,29 @@ void CSaveFileState::DoWork(CFileItem& item, { const int idFile = videodatabase.SetStreamDetailsForFile( item.GetVideoInfoTag()->m_streamDetails, progressTrackingFile); - if (item.GetVideoContentType() == VideoDbContentType::MOVIES) - videodatabase.SetFileForMovie(item.GetDynPath(), item.GetVideoInfoTag()->m_iDbId, - idFile); - else if (item.GetVideoContentType() == VideoDbContentType::EPISODES) - videodatabase.SetFileForEpisode(item.GetDynPath(), item.GetVideoInfoTag()->m_iDbId, - idFile); - updateListing = true; + + if (idFile == -2) + { + videoDbSuccess = false; + } + else if (idFile > 0) + { + updateListing = true; + + if (item.GetVideoContentType() == VideoDbContentType::MOVIES) + videoDbSuccess = videodatabase.SetFileForMovie( + item.GetDynPath(), item.GetVideoInfoTag()->m_iDbId, idFile); + else if (item.GetVideoContentType() == VideoDbContentType::EPISODES) + videoDbSuccess = videodatabase.SetFileForEpisode( + item.GetDynPath(), item.GetVideoInfoTag()->m_iDbId, idFile); + } } } - videodatabase.CommitTransaction(); + if (videoDbSuccess) + videodatabase.CommitTransaction(); + else + videodatabase.RollbackTransaction(); if (updateListing) { diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 747b21a8f2507..892322416343a 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -3021,11 +3021,9 @@ bool CVideoDatabase::SetFileForEpisode(const std::string& fileAndPath, int idEpi { try { - BeginTransaction(); std::string sql = PrepareSQL("UPDATE episode SET c18='%s', idFile=%i WHERE idEpisode=%i", fileAndPath.c_str(), idFile, idEpisode); m_pDS->exec(sql); - CommitTransaction(); return true; } @@ -3033,7 +3031,6 @@ bool CVideoDatabase::SetFileForEpisode(const std::string& fileAndPath, int idEpi { CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idEpisode); } - RollbackTransaction(); return false; } @@ -3041,14 +3038,14 @@ bool CVideoDatabase::SetFileForMovie(const std::string& fileAndPath, int idMovie { try { - BeginTransaction(); + assert(m_pDB->in_transaction()); + std::string sql = PrepareSQL("UPDATE movie SET c22='%s', idFile=%i WHERE idMovie=%i", fileAndPath.c_str(), idFile, idMovie); m_pDS->exec(sql); sql = PrepareSQL("UPDATE videoversion SET idFile=%i WHERE idMedia=%i AND media_type='movie'", idFile, idMovie); m_pDS->exec(sql); - CommitTransaction(); return true; } @@ -3056,7 +3053,6 @@ bool CVideoDatabase::SetFileForMovie(const std::string& fileAndPath, int idMovie { CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idMovie); } - RollbackTransaction(); return false; } @@ -3256,14 +3252,18 @@ int CVideoDatabase::SetStreamDetailsForFile(const CStreamDetails& details, int idFile = AddFile(strFileNameAndPath); if (idFile < 0) return -1; - SetStreamDetailsForFileId(details, idFile); - return idFile; + + //! @todo ugly error return mechanism, fixme + if (SetStreamDetailsForFileId(details, idFile)) + return idFile; + else + return -2; } -void CVideoDatabase::SetStreamDetailsForFileId(const CStreamDetails& details, int idFile) +bool CVideoDatabase::SetStreamDetailsForFileId(const CStreamDetails& details, int idFile) { if (idFile < 0) - return; + return false; try { @@ -3313,11 +3313,13 @@ void CVideoDatabase::SetStreamDetailsForFileId(const CStreamDetails& details, in m_pDS->exec(sql); } } + return true; } catch (...) { CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idFile); } + return false; } //******************************************************************************************************************************** diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index 2c8ea6357de4f..526f89cd46a66 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -601,8 +601,9 @@ class CVideoDatabase : public CDatabase * \brief Clear any existing stream details and add the new provided details to a file. * \param[in] details New stream details * \param[in] idFile Identifier of the file + * \return operation success. true for success, false for failure */ - void SetStreamDetailsForFileId(const CStreamDetails& details, int idFile); + bool SetStreamDetailsForFileId(const CStreamDetails& details, int idFile); bool SetSingleValue(VideoDbContentType type, int dbId, int dbField, const std::string& strValue); bool SetSingleValue(VideoDbContentType type, From 396eed4003c30169066b2023f37643d3770d09b5 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 5 Nov 2024 23:09:33 +0100 Subject: [PATCH 649/651] [PVR] CPVRTimerInfoTag::UpdateSummary: Add support for timer types not supporting start/end time. --- xbmc/pvr/timers/PVRTimerInfoTag.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.cpp b/xbmc/pvr/timers/PVRTimerInfoTag.cpp index 50cc5662d420b..7be5541754444 100644 --- a/xbmc/pvr/timers/PVRTimerInfoTag.cpp +++ b/xbmc/pvr/timers/PVRTimerInfoTag.cpp @@ -343,7 +343,11 @@ void CPVRTimerInfoTag::UpdateSummary() const std::string startDate(StartAsLocalTime().GetAsLocalizedDate()); const std::string endDate(EndAsLocalTime().GetAsLocalizedDate()); - if (m_bEndAnyTime) + if (!m_timerType->SupportsStartTime() || !m_timerType->SupportsEndTime()) + { + m_strSummary = GetWeekdaysString(); + } + else if (m_bEndAnyTime) { m_strSummary = StringUtils::Format( "{} {} {}", From 524cbadceb0ce2ce5fc60ebb9aed7f147bece0f5 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 6 Nov 2024 12:36:24 +0100 Subject: [PATCH 650/651] [PVR] CPVRTimerInfoTag::UpdateSummary: Fixup: Add support for timer types not supporting start/end time. --- xbmc/pvr/timers/PVRTimerInfoTag.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.cpp b/xbmc/pvr/timers/PVRTimerInfoTag.cpp index 7be5541754444..824e62a21bc61 100644 --- a/xbmc/pvr/timers/PVRTimerInfoTag.cpp +++ b/xbmc/pvr/timers/PVRTimerInfoTag.cpp @@ -343,12 +343,14 @@ void CPVRTimerInfoTag::UpdateSummary() const std::string startDate(StartAsLocalTime().GetAsLocalizedDate()); const std::string endDate(EndAsLocalTime().GetAsLocalizedDate()); - if (!m_timerType->SupportsStartTime() || !m_timerType->SupportsEndTime()) + if (!m_timerType->SupportsStartTime() && !m_timerType->SupportsEndTime()) { + // neither start time nor end time supported m_strSummary = GetWeekdaysString(); } - else if (m_bEndAnyTime) + else if (m_bEndAnyTime || (m_timerType->SupportsStartTime() && !m_timerType->SupportsEndTime())) { + // any end time or no end time supported m_strSummary = StringUtils::Format( "{} {} {}", m_iWeekdays != PVR_WEEKDAY_NONE ? GetWeekdaysString() From 7ba339a36859a44655748172c9cb14b773c4b87a Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:42:00 +0100 Subject: [PATCH 651/651] [PVR] Timer settings dialog: Ensure that the order of timer custom settings matches the order defined by the add-on. --- xbmc/pvr/settings/PVRCustomTimerSettings.cpp | 5 +++-- xbmc/pvr/settings/PVRCustomTimerSettings.h | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/xbmc/pvr/settings/PVRCustomTimerSettings.cpp b/xbmc/pvr/settings/PVRCustomTimerSettings.cpp index c62266896b588..00106a0a776c6 100644 --- a/xbmc/pvr/settings/PVRCustomTimerSettings.cpp +++ b/xbmc/pvr/settings/PVRCustomTimerSettings.cpp @@ -55,7 +55,7 @@ CPVRCustomTimerSettings::CPVRCustomTimerSettings( } const std::string settingId{StringUtils::Format("{}-{}", settingIdPrefix, idx)}; - m_customSettingDefs.insert({settingId, settingDef}); + m_customSettingDefs.emplace_back(std::make_pair(settingId, settingDef)); ++idx; } } @@ -247,7 +247,8 @@ bool CPVRCustomTimerSettings::IsSettingSupportedForTimerType(const std::string& std::shared_ptr<const CPVRTimerSettingDefinition> CPVRCustomTimerSettings::GetSettingDefintion( const std::string& settingId) const { - const auto it{m_customSettingDefs.find(settingId)}; + const auto it{std::find_if(m_customSettingDefs.cbegin(), m_customSettingDefs.cend(), + [&settingId](const auto& entry) { return entry.first == settingId; })}; if (it == m_customSettingDefs.cend()) return {}; diff --git a/xbmc/pvr/settings/PVRCustomTimerSettings.h b/xbmc/pvr/settings/PVRCustomTimerSettings.h index 864269c2125b7..7a396cf5f21c4 100644 --- a/xbmc/pvr/settings/PVRCustomTimerSettings.h +++ b/xbmc/pvr/settings/PVRCustomTimerSettings.h @@ -11,9 +11,9 @@ #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h" // PVR_TIMER_STATE #include "pvr/timers/PVRTimerInfoTag.h" -#include <map> #include <memory> #include <string> +#include <utility> #include <vector> class CSetting; @@ -67,11 +67,11 @@ class CPVRCustomTimerSettings std::shared_ptr<const CPVRTimerSettingDefinition> GetSettingDefintion( const std::string& settingId) const; - using CustomSettingDefinitionsMap = - std::map<std::string, - std::shared_ptr<const CPVRTimerSettingDefinition>>; // setting id, setting def + using CustomSettingDefinitionsVector = std::vector< + std::pair<std::string, + std::shared_ptr<const CPVRTimerSettingDefinition>>>; // setting id, setting def - CustomSettingDefinitionsMap m_customSettingDefs; + CustomSettingDefinitionsVector m_customSettingDefs; CPVRTimerInfoTag::CustomPropsMap m_customProps; }; } // namespace PVR