diff --git a/CMakeLists.txt b/CMakeLists.txt index 97e3b903..594657ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,8 @@ set(HTS_SOURCES_TVHEADEND src/tvheadend/AutoRecordings.h src/tvheadend/ChannelTuningPredictor.h src/tvheadend/ChannelTuningPredictor.cpp + src/tvheadend/CustomTimerProperties.h + src/tvheadend/CustomTimerProperties.cpp src/tvheadend/HTSPConnection.h src/tvheadend/HTSPConnection.cpp src/tvheadend/HTSPDemuxer.h diff --git a/pvr.hts/addon.xml.in b/pvr.hts/addon.xml.in index 81fb54ee..6949f098 100644 --- a/pvr.hts/addon.xml.in +++ b/pvr.hts/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ diff --git a/pvr.hts/changelog.txt b/pvr.hts/changelog.txt index 081821cd..354c9411 100644 --- a/pvr.hts/changelog.txt +++ b/pvr.hts/changelog.txt @@ -1,3 +1,8 @@ +v22.4.0 +- PVR Add-on API v9.1.0 +- Add support for Autorec property "Broadcast type" (HTSPv39+) +- Add support for DVR entry property "DVR configuration" (HTSPv40+) + v22.3.0 - Add support for PVR Providers (HTSPv38+) diff --git a/pvr.hts/resources/language/resource.language.en_gb/strings.po b/pvr.hts/resources/language/resource.language.en_gb/strings.po index f511615c..e9c17902 100644 --- a/pvr.hts/resources/language/resource.language.en_gb/strings.po +++ b/pvr.hts/resources/language/resource.language.en_gb/strings.po @@ -298,7 +298,13 @@ msgctxt "#30372" msgid "Record if unique episode according EPG/XMLTV" msgstr "" -#empty strings from id 30373 to 30374 +#. Prevent duplicate episodes representation +#: src/Tvheadend.cpp +msgctxt "#30373" +msgid "Use DVR configuration" +msgstr "" + +#empty string with id 30374 #. Recording lifetime representation #: src/Tvheadend.cpp @@ -441,7 +447,11 @@ msgctxt "#30456" msgid "Subscription error" msgstr "" -#empty strings from id 30457 to 30499 +msgctxt "#30457" +msgid "DVR configuration" +msgstr "" + +#empty strings from id 30458 to 30499 msgctxt "#30500" msgid "Streaming profile" @@ -451,8 +461,6 @@ msgctxt "#30501" msgid "Profile to use (empty = default)" msgstr "" -#. Check streaming profile validity during startup -#: src/client.cpp msgctxt "#30502" msgid "Streaming profile %s is not available" msgstr "" @@ -478,3 +486,29 @@ msgstr "" msgctxt "#30511" msgid "Server based play status" msgstr "" + +#empty strings from id 30512 to 30599 + +msgctxt "#30600" +msgid "Broadcast type" +msgstr "" + +msgctxt "#30601" +msgid "Any" +msgstr "" + +msgctxt "#30602" +msgid "New / premiere / unknown" +msgstr "" + +msgctxt "#30603" +msgid "Repeated" +msgstr "" + +msgctxt "#30604" +msgid "New / premiere" +msgstr "" + +msgctxt "#30605" +msgid "(Default profile)" +msgstr "" diff --git a/src/Tvheadend.cpp b/src/Tvheadend.cpp index b71f7191..f5e01855 100644 --- a/src/Tvheadend.cpp +++ b/src/Tvheadend.cpp @@ -7,6 +7,7 @@ #include "Tvheadend.h" +#include "tvheadend/CustomTimerProperties.h" #include "tvheadend/HTSPConnection.h" #include "tvheadend/HTSPDemuxer.h" #include "tvheadend/HTSPMessage.h" @@ -22,6 +23,7 @@ #include #include #include +#include #include using namespace tvheadend; @@ -36,8 +38,8 @@ CTvheadend::CTvheadend(const kodi::addon::IInstanceInfo& instance) m_vfs(new HTSPVFS(m_settings, *m_conn)), m_queue(static_cast(-1)), m_asyncState(m_settings->GetResponseTimeout()), - m_timeRecordings(*m_conn), - m_autoRecordings(m_settings, *m_conn), + m_timeRecordings(*m_conn, m_dvrConfigs), + m_autoRecordings(m_settings, *m_conn, m_dvrConfigs), m_epgMaxDays(EpgMaxFutureDays()), m_playingLiveStream(false), m_playingRecording(nullptr) @@ -167,6 +169,56 @@ std::string CTvheadend::GetImageURL(const char* str) } } +void CTvheadend::QueryAvailableDvrConfigurations(std::unique_lock& lock) +{ + /* Build message */ + htsmsg_t* m = htsmsg_create_map(); + + /* Send */ + m = m_conn->SendAndWait0(lock, "getDvrConfigs", m); + + /* Validate */ + if (!m) + return; + + htsmsg_t* l = htsmsg_get_list(m, "dvrconfigs"); + + if (!l) + { + Logger::Log(LogLevel::LEVEL_ERROR, "malformed getDvrConfigs: 'dvrconfigs' missing"); + htsmsg_destroy(m); + return; + } + + /* Process */ + Logger::Log(LogLevel::LEVEL_INFO, " Available DVR configurations:"); + + htsmsg_field_t* f = nullptr; + HTSMSG_FOREACH(f, l) + { + Profile profile; + + const char* str = htsmsg_get_str(&f->hmf_msg, "uuid"); + if (str) + profile.SetUuid(str); + + str = htsmsg_get_str(&f->hmf_msg, "name"); + if (str) + profile.SetName(str); + + str = htsmsg_get_str(&f->hmf_msg, "comment"); + if (str) + profile.SetComment(str); + + Logger::Log(LogLevel::LEVEL_INFO, " Name: %s, Comment: %s", profile.GetName().c_str(), + profile.GetComment().c_str()); + + m_dvrConfigs.emplace_back(std::move(profile)); + } + + htsmsg_destroy(m); +} + void CTvheadend::QueryAvailableProfiles(std::unique_lock& lock) { /* Build message */ @@ -840,16 +892,16 @@ struct TimerType : kodi::addon::PVRTimerType unsigned int id, unsigned int attributes, const std::string& description, - const std::vector& priorityValues = - std::vector(), - const std::vector& lifetimeValues = - std::vector(), + const std::vector& customIntSettingDefs, + const std::vector& priorityValues, + const std::vector& lifetimeValues, const std::vector& dupEpisodesValues = std::vector()) { SetId(id); SetAttributes(attributes); SetDescription(description); + SetCustomIntSettingDefinitions(customIntSettingDefs); SetPriorities(priorityValues, settings->GetDvrPriority()); SetLifetimes(lifetimeValues, LifetimeMapper::TvhToKodi(settings->GetDvrLifetime())); SetPreventDuplicateEpisodes(dupEpisodesValues, settings->GetDvrDupdetect()); @@ -880,6 +932,40 @@ void CTvheadend::GetLivetimeValues(std::vector& li }; } +const std::vector CTvheadend:: + GetCustomIntSettingDefinitions() const +{ + std::vector ret; + + //! @todo CustomTimerProperties??? + if (m_dvrConfigs.size() > 1 && m_conn->GetProtocol() >= 40) + { + // DVR configuration + int defaultConfigId{0}; + std::vector dvrConfigValues; + for (const auto& entry : m_dvrConfigs) + { + std::string name{entry.GetName()}; + if (name.empty()) + { + name = kodi::addon::GetLocalizedString(30605); // (default profile) + defaultConfigId = entry.GetId(); + } + + dvrConfigValues.emplace_back(entry.GetId(), name); + } + + kodi::addon::PVRIntSettingDefinition settingDef; + settingDef.SetId(CUSTOM_PROP_ID_DVR_CONFIGURATION); + settingDef.SetName(kodi::addon::GetLocalizedString(30457)); // DVR configuration + settingDef.SetValues(dvrConfigValues); + settingDef.SetDefaultValue(defaultConfigId); + ret.emplace_back(std::move(settingDef)); + } + + return ret; +} + PVR_ERROR CTvheadend::GetTimerTypes(std::vector& types) { /* PVR_Timer.iPriority values and presentation.*/ @@ -898,6 +984,7 @@ PVR_ERROR CTvheadend::GetTimerTypes(std::vector& type /* PVR_Timer.iPreventDuplicateEpisodes values and presentation.*/ std::vector deDupValues = { + {DVR_AUTOREC_RECORD_DVR_PROFILE, kodi::addon::GetLocalizedString(30373)}, {DVR_AUTOREC_RECORD_ALL, kodi::addon::GetLocalizedString(30356)}, {DVR_AUTOREC_RECORD_DIFFERENT_EPISODE_NUMBER, kodi::addon::GetLocalizedString(30357)}, {DVR_AUTOREC_RECORD_DIFFERENT_SUBTITLE, kodi::addon::GetLocalizedString(30358)}, @@ -951,6 +1038,10 @@ PVR_ERROR CTvheadend::GetTimerTypes(std::vector& type /* Timer types definition. */ + /* Custom integer setting definitions */ + const std::vector customIntSettingDefs{ + GetCustomIntSettingDefinitions()}; + /* One-shot manual (time and channel based) */ types.emplace_back(TimerType( /* Settings */ @@ -961,6 +1052,8 @@ PVR_ERROR CTvheadend::GetTimerTypes(std::vector& type TIMER_ONCE_MANUAL_ATTRIBS, /* Let Kodi generate the description. */ "", + /* Custom int settings definitions. */ + customIntSettingDefs, /* Values definitions for priorities. */ priorityValues, /* Values definitions for lifetime. */ @@ -976,6 +1069,8 @@ PVR_ERROR CTvheadend::GetTimerTypes(std::vector& type TIMER_ONCE_EPG_ATTRIBS, /* Let Kodi generate the description. */ "", + /* Custom int settings definitions. */ + customIntSettingDefs, /* Values definitions for priorities. */ priorityValues, /* Values definitions for lifetime. */ @@ -991,6 +1086,8 @@ PVR_ERROR CTvheadend::GetTimerTypes(std::vector& type TIMER_ONCE_MANUAL_ATTRIBS | PVR_TIMER_TYPE_IS_READONLY | PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES, /* Description. */ kodi::addon::GetLocalizedString(30350), // "One Time (Scheduled by timer rule)" + /* Custom int settings definitions. */ + customIntSettingDefs, /* Values definitions for priorities. */ priorityValues, /* Values definitions for lifetime. */ @@ -1006,6 +1103,8 @@ PVR_ERROR CTvheadend::GetTimerTypes(std::vector& type TIMER_ONCE_EPG_ATTRIBS | PVR_TIMER_TYPE_IS_READONLY | PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES, /* Description. */ kodi::addon::GetLocalizedString(30350), // "One Time (Scheduled by timer rule)" + /* Custom int settings definitions. */ + customIntSettingDefs, /* Values definitions for priorities. */ priorityValues, /* Values definitions for lifetime. */ @@ -1025,11 +1124,19 @@ PVR_ERROR CTvheadend::GetTimerTypes(std::vector& type PVR_TIMER_TYPE_SUPPORTS_LIFETIME | PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS, /* Let Kodi generate the description. */ "", + /* Custom int settings definitions. */ + customIntSettingDefs, /* Values definitions for priorities. */ priorityValues, /* Values definitions for lifetime. */ lifetimeValues)); + /* Custom Autorec integer setting definitions */ + std::vector customAutorecIntSettingDefs{ + m_autoRecordings.GetCustomAutorecIntSettingDefinitions()}; + std::copy(customIntSettingDefs.cbegin(), customIntSettingDefs.cend(), + std::back_inserter(customAutorecIntSettingDefs)); + if (m_conn->GetProtocol() >= 29) { unsigned int TIMER_REPEATING_SERIESLINK_ATTRIBS = @@ -1057,6 +1164,8 @@ PVR_ERROR CTvheadend::GetTimerTypes(std::vector& type TIMER_REPEATING_SERIESLINK_ATTRIBS, /* Description. */ kodi::addon::GetLocalizedString(30369), // "Timer rule (series link)" + /* Custom int settings definitions. */ + customAutorecIntSettingDefs, /* Values definitions for priorities. */ priorityValues, /* Values definitions for lifetime. */ @@ -1089,6 +1198,8 @@ PVR_ERROR CTvheadend::GetTimerTypes(std::vector& type TIMER_REPEATING_EPG_ATTRIBS, /* Let Kodi generate the description. */ "", + /* Custom int settings definitions. */ + customAutorecIntSettingDefs, /* Values definitions for priorities. */ priorityValues, /* Values definitions for lifetime. */ @@ -1148,6 +1259,13 @@ bool CTvheadend::CreateTimer(const Recording& tvhTmr, kodi::addon::PVRTimer& tmr : tmr.GetTimerType() == TIMER_ONCE_CREATED_BY_AUTOREC ? m_autoRecordings.GetTimerIntIdFromStringId(tvhTmr.GetAutorecId()) : 0); + + /* Custom integer props. Currently only DVR configuration */ + const CustomTimerProperties props(m_conn->GetProtocol(), m_dvrConfigs); + const std::vector customProps{ + props.GetProperties(tvhTmr, {CUSTOM_PROP_ID_DVR_CONFIGURATION})}; + tmr.SetCustomIntProperties(std::move(customProps)); + return true; } @@ -1231,6 +1349,10 @@ PVR_ERROR CTvheadend::AddTimer(const kodi::addon::PVRTimer& timer) LifetimeMapper::KodiToTvh(timer.GetLifetime())); // remove from disk htsmsg_add_u32(m, "priority", timer.GetPriority()); + /* Custom integer props. */ + const CustomTimerProperties props(m_conn->GetProtocol(), m_dvrConfigs); + props.AppendPropertiesToHTSPMessage(timer.GetCustomIntProperties(), m); + /* Send and Wait */ { std::unique_lock lock(m_conn->Mutex()); @@ -1346,6 +1468,10 @@ PVR_ERROR CTvheadend::UpdateTimer(const kodi::addon::PVRTimer& timer) LifetimeMapper::KodiToTvh(timer.GetLifetime())); // remove from disk htsmsg_add_u32(m, "priority", timer.GetPriority()); + /* Custom integer props. */ + const CustomTimerProperties props(m_conn->GetProtocol(), m_dvrConfigs); + props.AppendPropertiesToHTSPMessage(timer.GetCustomIntProperties(), m); + return SendDvrUpdate(m); } else if (timer.GetTimerType() == TIMER_REPEATING_MANUAL) @@ -1539,6 +1665,9 @@ bool CTvheadend::Connected(std::unique_lock& lock) /* Query the server for available streaming profiles */ QueryAvailableProfiles(lock); + /* Query the server for available DVR configurations */ + QueryAvailableDvrConfigurations(lock); + /* Show a notification if the profile is not available */ const std::string streamingProfile = m_settings->GetStreamingProfile(); @@ -2686,6 +2815,10 @@ void CTvheadend::ParseRecordingAddOrUpdate(htsmsg_t* msg, bool bAdd) rec.SetRatingSource(str); } + str = htsmsg_get_str(msg, "configId"); + if (str) + rec.SetConfigUuid(str); + if (m_conn->GetProtocol() >= 32) { if (rec.GetDescription().empty() && !rec.GetSubtitle().empty()) diff --git a/src/Tvheadend.h b/src/Tvheadend.h index db8224c3..1cbf0925 100644 --- a/src/Tvheadend.h +++ b/src/Tvheadend.h @@ -140,11 +140,22 @@ class ATTR_DLL_LOCAL CTvheadend : public kodi::addon::CInstancePVRClient, std::string GetImageURL(const char* str); /** - * Queries the server for available streaming profiles and populates - * m_profiles + * Queries the server for available DVR configurations and populates m_dvrConfigs + */ + void QueryAvailableDvrConfigurations(std::unique_lock& lock); + + /** + * Queries the server for available streaming profiles and populates m_profiles */ void QueryAvailableProfiles(std::unique_lock& lock); + /** + * Get custom int setting defininitions common for all timer types that can be used to by users + * to create timers (time-based, epg-based) and timer rules (Aurorecs, Timerecs). + */ + const std::vector GetCustomIntSettingDefinitions() + const; + /** * @param streamingProfile the streaming profile to check for * @return whether the server supports the specified streaming profile @@ -262,6 +273,11 @@ class ATTR_DLL_LOCAL CTvheadend : public kodi::addon::CInstancePVRClient, */ PVR_ERROR GetStreamTimes(kodi::addon::PVRStreamTimes& times) override; + /** + * The DVR configurations available on the server + */ + tvheadend::Profiles m_dvrConfigs; + /** * The streaming profiles available on the server */ diff --git a/src/tvheadend/AutoRecordings.cpp b/src/tvheadend/AutoRecordings.cpp index dfb9990a..6e0ae5c7 100644 --- a/src/tvheadend/AutoRecordings.cpp +++ b/src/tvheadend/AutoRecordings.cpp @@ -7,6 +7,7 @@ #include "AutoRecordings.h" +#include "CustomTimerProperties.h" #include "HTSPConnection.h" #include "InstanceSettings.h" #include "entity/Recording.h" @@ -23,8 +24,9 @@ using namespace tvheadend::entity; using namespace tvheadend::utilities; AutoRecordings::AutoRecordings(const std::shared_ptr& settings, - HTSPConnection& conn) - : m_settings(settings), m_conn(conn) + HTSPConnection& conn, + Profiles& dvrConfigs) + : m_settings(settings), m_conn(conn), m_dvrConfigs(dvrConfigs) { } @@ -105,6 +107,12 @@ void AutoRecordings::GetAutorecTimers(std::vector& timers tmr.SetFullTextEpgSearch(rec.second.GetFulltext()); tmr.SetParentClientIndex(0); + /* Custom integer props. Currently DVR configuration and Broadcast type */ + const CustomTimerProperties props(m_conn.GetProtocol(), m_dvrConfigs); + const std::vector customProps{props.GetProperties( + rec.second, {CUSTOM_PROP_ID_DVR_CONFIGURATION, CUSTOM_PROP_ID_AUTOREC_BROADCASTTYPE})}; + tmr.SetCustomIntProperties(std::move(customProps)); + timers.emplace_back(std::move(tmr)); } } @@ -133,6 +141,28 @@ const std::string AutoRecordings::GetTimerStringIdFromIntId(unsigned int intId) return ""; } +const std::vector AutoRecordings:: + GetCustomAutorecIntSettingDefinitions() const +{ + std::vector ret; + if (m_conn.GetProtocol() >= 39) + { + static std::vector broadcastTypeValues = { + {DVR_AUTOREC_BTYPE_ALL, kodi::addon::GetLocalizedString(30601)}, + {DVR_AUTOREC_BTYPE_NEW_OR_UNKNOWN, kodi::addon::GetLocalizedString(30602)}, + {DVR_AUTOREC_BTYPE_REPEAT, kodi::addon::GetLocalizedString(30603)}, + {DVR_AUTOREC_BTYPE_NEW, kodi::addon::GetLocalizedString(30604)}}; + + kodi::addon::PVRIntSettingDefinition settingDef; + settingDef.SetId(CUSTOM_PROP_ID_AUTOREC_BROADCASTTYPE); + settingDef.SetName(kodi::addon::GetLocalizedString(30600)); + settingDef.SetValues(broadcastTypeValues); + settingDef.SetDefaultValue(DVR_AUTOREC_BTYPE_ALL); + ret.emplace_back(std::move(settingDef)); + } + return ret; +} + PVR_ERROR AutoRecordings::SendAutorecAdd(const kodi::addon::PVRTimer& timer) { return SendAutorecAddOrUpdate(timer, false); @@ -259,6 +289,10 @@ PVR_ERROR AutoRecordings::SendAutorecAddOrUpdate(const kodi::addon::PVRTimer& ti if (timer.GetTimerType() == TIMER_REPEATING_SERIESLINK) htsmsg_add_str(m, "serieslinkUri", timer.GetSeriesLink().c_str()); + /* Custom integer props. */ + const CustomTimerProperties props(m_conn.GetProtocol(), m_dvrConfigs); + props.AppendPropertiesToHTSPMessage(timer.GetCustomIntProperties(), m); + /* Send and Wait */ { std::unique_lock lock(m_conn.Mutex()); @@ -443,21 +477,24 @@ bool AutoRecordings::ParseAutorecAddOrUpdate(htsmsg_t* msg, bool bAdd) rec.SetCreator(str); if (!htsmsg_get_u32(msg, "channel", &u32)) - { rec.SetChannel(u32); - } else rec.SetChannel(PVR_TIMER_ANY_CHANNEL); // an empty channel field = any channel if (!htsmsg_get_u32(msg, "fulltext", &u32)) - { rec.SetFulltext(u32); - } str = htsmsg_get_str(msg, "serieslinkUri"); if (str) rec.SetSeriesLink(str); + if (!htsmsg_get_u32(msg, "broadcastType", &u32)) + rec.SetBroadcastType(u32); + + str = htsmsg_get_str(msg, "configId"); + if (str) + rec.SetConfigUuid(str); + return true; } diff --git a/src/tvheadend/AutoRecordings.h b/src/tvheadend/AutoRecordings.h index ccd66725..721adf4f 100644 --- a/src/tvheadend/AutoRecordings.h +++ b/src/tvheadend/AutoRecordings.h @@ -17,6 +17,7 @@ extern "C" #include } +#include "Profile.h" #include "entity/AutoRecording.h" #include "kodi/addon-instance/pvr/Timers.h" @@ -30,7 +31,9 @@ class InstanceSettings; class AutoRecordings { public: - AutoRecordings(const std::shared_ptr& settings, HTSPConnection& conn); + AutoRecordings(const std::shared_ptr& settings, + HTSPConnection& conn, + Profiles& dvrConfigs); ~AutoRecordings(); /* state updates */ @@ -41,6 +44,8 @@ class AutoRecordings int GetAutorecTimerCount() const; void GetAutorecTimers(std::vector& timers); const unsigned int GetTimerIntIdFromStringId(const std::string& strId) const; + const std::vector GetCustomAutorecIntSettingDefinitions() + const; /* client to server messages */ PVR_ERROR SendAutorecAdd(const kodi::addon::PVRTimer& timer); @@ -56,6 +61,7 @@ class AutoRecordings PVR_ERROR SendAutorecAddOrUpdate(const kodi::addon::PVRTimer& timer, bool update); HTSPConnection& m_conn; + tvheadend::Profiles& m_dvrConfigs; tvheadend::entity::AutoRecordingsMap m_autoRecordings; std::shared_ptr m_settings; }; diff --git a/src/tvheadend/CustomTimerProperties.cpp b/src/tvheadend/CustomTimerProperties.cpp new file mode 100644 index 00000000..27ca63f5 --- /dev/null +++ b/src/tvheadend/CustomTimerProperties.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2005-2024 Team Kodi (https://kodi.tv) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "CustomTimerProperties.h" + +#include "entity/AutoRecording.h" +#include "entity/Recording.h" +#include "entity/TimeRecording.h" +#include "utilities/Logger.h" + +#include + +using namespace tvheadend; +using namespace tvheadend::entity; +using namespace tvheadend::utilities; + +CustomTimerProperties::CustomTimerProperties(unsigned int htspVersion, const Profiles& dvrConfigs) + : m_htspVersion(htspVersion), m_dvrConfigs(dvrConfigs) +{ +} + +std::vector CustomTimerProperties::GetProperties( + const tvheadend::entity::Recording& rec, const std::vector& propIds) const +{ + std::vector customProps; + for (unsigned int propId : propIds) + { + switch (propId) + { + case CUSTOM_PROP_ID_DVR_CONFIGURATION: + { + /* DVR configuration */ + const int configId{GetDvrConfigurationId(rec.GetConfigUuid())}; + if (configId != -1) + customProps.emplace_back(CUSTOM_PROP_ID_DVR_CONFIGURATION, configId); + break; + } + default: + Logger::Log(LogLevel::LEVEL_ERROR, "Unknown property %u", propId); + break; + } + } + return customProps; +} + +std::vector CustomTimerProperties::GetProperties( + const tvheadend::entity::AutoRecording& autorec, const std::vector& propIds) const +{ + std::vector customProps; + for (unsigned int propId : propIds) + { + switch (propId) + { + case CUSTOM_PROP_ID_AUTOREC_BROADCASTTYPE: + /* Broadcast type */ + if (m_htspVersion >= 39) + customProps.emplace_back(CUSTOM_PROP_ID_AUTOREC_BROADCASTTYPE, + autorec.GetBroadcastType()); + break; + case CUSTOM_PROP_ID_DVR_CONFIGURATION: + { + /* DVR configuration */ + const int configId{GetDvrConfigurationId(autorec.GetConfigUuid())}; + if (configId != -1) + customProps.emplace_back(CUSTOM_PROP_ID_DVR_CONFIGURATION, configId); + break; + } + default: + Logger::Log(LogLevel::LEVEL_ERROR, "Unknown property %u", propId); + break; + } + } + return customProps; +} + +std::vector CustomTimerProperties::GetProperties( + const tvheadend::entity::TimeRecording& timerec, const std::vector& propIds) const +{ + std::vector customProps; + for (unsigned int propId : propIds) + { + switch (propId) + { + case CUSTOM_PROP_ID_DVR_CONFIGURATION: + { + /* DVR configuration */ + const int configId{GetDvrConfigurationId(timerec.GetConfigUuid())}; + if (configId != -1) + customProps.emplace_back(CUSTOM_PROP_ID_DVR_CONFIGURATION, configId); + break; + } + default: + Logger::Log(LogLevel::LEVEL_ERROR, "Unknown property %u", propId); + break; + } + } + return customProps; +} + +void CustomTimerProperties::AppendPropertiesToHTSPMessage( + const std::vector& props, htsmsg_t* msg) const +{ + for (const auto& prop : props) + { + switch (prop.GetKey()) + { + case CUSTOM_PROP_ID_AUTOREC_BROADCASTTYPE: + /* Broadcast type */ + htsmsg_add_u32(msg, "broadcastType", prop.GetValue()); + break; + case CUSTOM_PROP_ID_DVR_CONFIGURATION: + /* DVR configuration */ + for (const auto& config : m_dvrConfigs) + { + if (config.GetId() == prop.GetValue()) + { + htsmsg_add_str(msg, "configName", config.GetUuid().c_str()); + break; + } + } + break; + default: + Logger::Log(LogLevel::LEVEL_ERROR, "Unknown property %u", prop.GetKey()); + break; + } + } +} + +int CustomTimerProperties::GetDvrConfigurationId(const std::string& uuid) const +{ + if (m_htspVersion >= 40 && m_dvrConfigs.size() > 1) + { + for (const auto& cfg : m_dvrConfigs) + { + if (cfg.GetUuid() == uuid) + return cfg.GetId(); + } + } + return -1; +} diff --git a/src/tvheadend/CustomTimerProperties.h b/src/tvheadend/CustomTimerProperties.h new file mode 100644 index 00000000..2321816f --- /dev/null +++ b/src/tvheadend/CustomTimerProperties.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005-2024 Team Kodi (https://kodi.tv) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "Profile.h" + +#include + +extern "C" +{ +#include "libhts/htsmsg.h" +} + +namespace kodi::addon +{ +class PVRIntKeyValuePair; +} + +namespace tvheadend +{ + +namespace entity +{ +class AutoRecording; +class Recording; +class TimeRecording; +} // namespace entity + +class CustomTimerProperties +{ +public: + CustomTimerProperties(unsigned int htspVersion, const tvheadend::Profiles& dvrConfigs); + virtual ~CustomTimerProperties() = default; + + // Custom props for all one-shot timers + std::vector GetProperties( + const tvheadend::entity::Recording& rec, const std::vector& propIds) const; + + // Custom props for Autorecs + std::vector GetProperties( + const tvheadend::entity::AutoRecording& autorec, + const std::vector& propIds) const; + + // Custom props for Timerecs + std::vector GetProperties( + const tvheadend::entity::TimeRecording& timerec, + const std::vector& propIds) const; + + void AppendPropertiesToHTSPMessage(const std::vector& props, + htsmsg_t* msg) const; + +private: + int GetDvrConfigurationId(const std::string& uuid) const; + + const unsigned int m_htspVersion{0}; + const tvheadend::Profiles& m_dvrConfigs; +}; + +} // namespace tvheadend diff --git a/src/tvheadend/HTSPTypes.h b/src/tvheadend/HTSPTypes.h index de04e7cd..16ad1e6e 100644 --- a/src/tvheadend/HTSPTypes.h +++ b/src/tvheadend/HTSPTypes.h @@ -37,7 +37,6 @@ typedef enum DVR_ACTION_TYPE_MUTE, DVR_ACTION_TYPE_SCENE, DVR_ACTION_TYPE_COMBREAK, - } dvr_action_type_t; typedef enum @@ -50,6 +49,7 @@ typedef enum DVR_AUTOREC_RECORD_ONCE_PER_MONTH = 12, DVR_AUTOREC_RECORD_ONCE_PER_WEEK = 4, DVR_AUTOREC_RECORD_ONCE_PER_DAY = 5, + DVR_AUTOREC_RECORD_DVR_PROFILE = 15, // Use DVR configuration DVR_AUTOREC_LRECORD_DIFFERENT_EPISODE_NUMBER = 6, DVR_AUTOREC_LRECORD_DIFFERENT_TITLE = 7, DVR_AUTOREC_LRECORD_DIFFERENT_SUBTITLE = 8, @@ -80,11 +80,10 @@ typedef enum DVR_RET_1YEAR = 366, DVR_RET_2YEARS = 731, DVR_RET_3YEARS = 1096, - DVR_RET_SPACE = - INT32_MAX - - 1, // the server may delete this recording if space for a new recording is needed (removal only) - DVR_RET_FOREVER = - INT32_MAX // the server should never delete this recording or database entry, only the user can do this + // the server may delete this recording if space for a new recording is needed (removal only) + DVR_RET_SPACE = (INT32_MAX - 1), + // the server should never delete this recording or database entry, only the user can do this + DVR_RET_FOREVER = (INT32_MAX) } dvr_retention_t; typedef enum @@ -94,6 +93,15 @@ typedef enum CHANNEL_TYPE_RADIO = 2 } channel_type_t; +// Autorecs: Broadcast type +typedef enum +{ + DVR_AUTOREC_BTYPE_ALL = 0, // "Any" + DVR_AUTOREC_BTYPE_NEW_OR_UNKNOWN = 1, // "New / premiere / unknown" + DVR_AUTOREC_BTYPE_REPEAT = 2, // "Repeated" + DVR_AUTOREC_BTYPE_NEW = 3, // "New / premiere" +} dvr_autorec_btype_t; + typedef enum { HTSP_DVR_PLAYCOUNT_RESET = 0, @@ -137,4 +145,8 @@ struct SHTSPEvent typedef std::vector SHTSPEventList; +// custom property ids +constexpr unsigned int CUSTOM_PROP_ID_AUTOREC_BROADCASTTYPE{1}; +constexpr unsigned int CUSTOM_PROP_ID_DVR_CONFIGURATION{2}; + } // namespace tvheadend diff --git a/src/tvheadend/Profile.h b/src/tvheadend/Profile.h index 1846d7b4..f5481073 100644 --- a/src/tvheadend/Profile.h +++ b/src/tvheadend/Profile.h @@ -17,11 +17,16 @@ class Profile; typedef std::vector Profiles; /** - * Represents a single streaming profile + * Represents a single streaming profile or a single DVR configuration */ class Profile { public: + Profile() : m_id(GetNextIntId()) {} + + uint32_t GetId() const { return m_id; } + void SetId(uint32_t id) { m_id = id; } + std::string GetUuid() const { return m_uuid; } void SetUuid(const std::string& uuid) { m_uuid = uuid; } @@ -32,19 +37,15 @@ class Profile void SetComment(const std::string& comment) { m_comment = comment; } private: - /** - * The profile UUID - */ - std::string m_uuid; + static unsigned int GetNextIntId() + { + static unsigned int intId = 0; + return ++intId; + } - /** - * The profile name - */ + uint32_t m_id{0}; + std::string m_uuid; std::string m_name; - - /** - * The profile comment - */ std::string m_comment; }; diff --git a/src/tvheadend/TimeRecordings.cpp b/src/tvheadend/TimeRecordings.cpp index fbf52e32..aafb7e60 100644 --- a/src/tvheadend/TimeRecordings.cpp +++ b/src/tvheadend/TimeRecordings.cpp @@ -7,6 +7,7 @@ #include "TimeRecordings.h" +#include "CustomTimerProperties.h" #include "HTSPConnection.h" #include "entity/Recording.h" #include "utilities/LifetimeMapper.h" @@ -20,7 +21,8 @@ using namespace tvheadend; using namespace tvheadend::entity; using namespace tvheadend::utilities; -TimeRecordings::TimeRecordings(HTSPConnection& conn) : m_conn(conn) +TimeRecordings::TimeRecordings(HTSPConnection& conn, Profiles& dvrConfigs) + : m_conn(conn), m_dvrConfigs(dvrConfigs) { } @@ -81,6 +83,12 @@ void TimeRecordings::GetTimerecTimers(std::vector& timers tmr.SetFullTextEpgSearch(false); // n/a for manual timers tmr.SetParentClientIndex(0); + /* Custom integer props. Currently only DVR configuration */ + const CustomTimerProperties props(m_conn.GetProtocol(), m_dvrConfigs); + const std::vector customProps{ + props.GetProperties(rec.second, {CUSTOM_PROP_ID_DVR_CONFIGURATION})}; + tmr.SetCustomIntProperties(std::move(customProps)); + timers.emplace_back(std::move(tmr)); } } @@ -164,6 +172,10 @@ PVR_ERROR TimeRecordings::SendTimerecAddOrUpdate(const kodi::addon::PVRTimer& ti if (timer.GetDirectory() != "/") htsmsg_add_str(m, "directory", timer.GetDirectory().c_str()); + /* Custom integer props. */ + const CustomTimerProperties props(m_conn.GetProtocol(), m_dvrConfigs); + props.AppendPropertiesToHTSPMessage(timer.GetCustomIntProperties(), m); + /* Send and Wait */ { std::unique_lock lock(m_conn.Mutex()); @@ -328,6 +340,10 @@ bool TimeRecordings::ParseTimerecAddOrUpdate(htsmsg_t* msg, bool bAdd) rec.SetChannel(PVR_TIMER_ANY_CHANNEL); } + str = htsmsg_get_str(msg, "configId"); + if (str) + rec.SetConfigUuid(str); + return true; } diff --git a/src/tvheadend/TimeRecordings.h b/src/tvheadend/TimeRecordings.h index 353b5fac..8a907ffd 100644 --- a/src/tvheadend/TimeRecordings.h +++ b/src/tvheadend/TimeRecordings.h @@ -16,6 +16,7 @@ extern "C" #include } +#include "Profile.h" #include "entity/TimeRecording.h" #include "kodi/addon-instance/pvr/Timers.h" @@ -28,7 +29,7 @@ class HTSPConnection; class TimeRecordings { public: - TimeRecordings(HTSPConnection& conn); + TimeRecordings(HTSPConnection& conn, Profiles& dvrConfigs); ~TimeRecordings(); /* state updates */ @@ -54,6 +55,7 @@ class TimeRecordings PVR_ERROR SendTimerecAddOrUpdate(const kodi::addon::PVRTimer& timer, bool update); HTSPConnection& m_conn; + tvheadend::Profiles& m_dvrConfigs; tvheadend::entity::TimeRecordingsMap m_timeRecordings; }; diff --git a/src/tvheadend/entity/AutoRecording.cpp b/src/tvheadend/entity/AutoRecording.cpp index c0e7c815..3830e9aa 100644 --- a/src/tvheadend/entity/AutoRecording.cpp +++ b/src/tvheadend/entity/AutoRecording.cpp @@ -28,7 +28,8 @@ bool AutoRecording::operator==(const AutoRecording& right) return RecordingBase::operator==(right) && m_startWindowBegin == right.m_startWindowBegin && m_startWindowEnd == right.m_startWindowEnd && m_startExtra == right.m_startExtra && m_stopExtra == right.m_stopExtra && m_dupDetect == right.m_dupDetect && - m_fulltext == right.m_fulltext && m_seriesLink == right.m_seriesLink; + m_fulltext == right.m_fulltext && m_broadcastType == right.m_broadcastType && + m_seriesLink == right.m_seriesLink; } bool AutoRecording::operator!=(const AutoRecording& right) @@ -135,6 +136,15 @@ void AutoRecording::SetFulltext(uint32_t fulltext) m_fulltext = fulltext; } +uint32_t AutoRecording::GetBroadcastType() const +{ + return m_broadcastType; +} +void AutoRecording::SetBroadcastType(uint32_t broadcastType) +{ + m_broadcastType = broadcastType; +} + const std::string& AutoRecording::GetSeriesLink() const { return m_seriesLink; diff --git a/src/tvheadend/entity/AutoRecording.h b/src/tvheadend/entity/AutoRecording.h index dcfd8373..9dc50216 100644 --- a/src/tvheadend/entity/AutoRecording.h +++ b/src/tvheadend/entity/AutoRecording.h @@ -47,6 +47,9 @@ class AutoRecording : public RecordingBase bool GetFulltext() const; void SetFulltext(uint32_t fulltext); + uint32_t GetBroadcastType() const; + void SetBroadcastType(uint32_t broadcastType); + const std::string& GetSeriesLink() const; void SetSeriesLink(const std::string& seriesLink); @@ -59,6 +62,7 @@ class AutoRecording : public RecordingBase int64_t m_stopExtra; // Extra stop minutes (post-time). uint32_t m_dupDetect; // duplicate episode detect (numeric values: see dvr_autorec_dedup_t). uint32_t m_fulltext; // Fulltext epg search. + uint32_t m_broadcastType{0}; // Broadcast type (numeric values: see dvr_autorec_btype_t). std::string m_seriesLink; // Series link. }; diff --git a/src/tvheadend/entity/Recording.h b/src/tvheadend/entity/Recording.h index 7d011a2c..9b2d65d6 100644 --- a/src/tvheadend/entity/Recording.h +++ b/src/tvheadend/entity/Recording.h @@ -83,7 +83,8 @@ class Recording : public Entity m_playPosition == other.m_playPosition && m_contentType == other.m_contentType && m_season == other.m_season && m_episode == other.m_episode && m_part == other.m_part && m_ageRating == other.m_ageRating && m_ratingLabel == other.m_ratingLabel && - m_ratingIcon == other.m_ratingIcon && m_ratingSource == other.m_ratingSource; + m_ratingIcon == other.m_ratingIcon && m_ratingSource == other.m_ratingSource && + m_configUuid == other.m_configUuid; } bool operator!=(const Recording& other) const { return !(*this == other); } @@ -225,6 +226,9 @@ class Recording : public Entity const std::string& GetRatingSource() const { return m_ratingSource; } void SetRatingSource(const std::string& ratingSource) { m_ratingSource = ratingSource; } + const std::string& GetConfigUuid() const { return m_configUuid; } + void SetConfigUuid(const std::string& uuid) { m_configUuid = uuid; } + private: uint32_t m_enabled; uint32_t m_channel; @@ -260,6 +264,7 @@ class Recording : public Entity std::string m_ratingLabel; std::string m_ratingIcon; std::string m_ratingSource; + std::string m_configUuid; // DVR configuration UUID. }; } // namespace entity diff --git a/src/tvheadend/entity/RecordingBase.cpp b/src/tvheadend/entity/RecordingBase.cpp index 4d888e01..edd8b67f 100644 --- a/src/tvheadend/entity/RecordingBase.cpp +++ b/src/tvheadend/entity/RecordingBase.cpp @@ -25,7 +25,8 @@ bool RecordingBase::operator==(const RecordingBase& right) return m_id == right.m_id && m_enabled == right.m_enabled && m_daysOfWeek == right.m_daysOfWeek && m_lifetime == right.m_lifetime && m_priority == right.m_priority && m_title == right.m_title && m_name == right.m_name && m_directory == right.m_directory && - m_owner == right.m_owner && m_creator == right.m_creator && m_channel == right.m_channel; + m_owner == right.m_owner && m_creator == right.m_creator && m_channel == right.m_channel && + m_configUuid == right.m_configUuid; } bool RecordingBase::operator!=(const RecordingBase& right) @@ -133,6 +134,16 @@ void RecordingBase::SetChannel(uint32_t channel) m_channel = channel; } +const std::string& RecordingBase::GetConfigUuid() const +{ + return m_configUuid; +} + +void RecordingBase::SetConfigUuid(const std::string& uuid) +{ + m_configUuid = uuid; +} + // static time_t RecordingBase::LocaltimeToUTC(int32_t lctime) { diff --git a/src/tvheadend/entity/RecordingBase.h b/src/tvheadend/entity/RecordingBase.h index d9751015..f499bc57 100644 --- a/src/tvheadend/entity/RecordingBase.h +++ b/src/tvheadend/entity/RecordingBase.h @@ -56,6 +56,9 @@ class RecordingBase : public Entity uint32_t GetChannel() const; void SetChannel(uint32_t channel); + const std::string& GetConfigUuid() const; + void SetConfigUuid(const std::string& uuid); + protected: static time_t LocaltimeToUTC(int32_t lctime); @@ -74,6 +77,7 @@ class RecordingBase : public Entity std::string m_owner; // Owner. std::string m_creator; // Creator. uint32_t m_channel; // Channel ID. + std::string m_configUuid; // DVR configuration UUID. }; } // namespace entity