From 0f369937e5ee1dbd69e3a6fbf87bba47086c04bc Mon Sep 17 00:00:00 2001 From: emveepee Date: Sat, 7 Sep 2024 16:46:56 -0400 Subject: [PATCH] Changes for Piers (#269) * Timeshift change Start timeshift in real time mode. Low bit radio was taking to long to buffer. From PR 266 * Series and episode clean up Remove duplicated series information from recordings, timer and EPG. Parse episode display information from the description. From PR #266 * Cache channel changes NextPVR has no group API calls. Cache and compress channel list to avoid extra downloads. Compress to save on disk for large IPTV channel lists with zlib potential future use with VOD. Also update to same tinyxml2 version used by Kodi * Populate EpisodePart PVR 9 allows populating EpisodePart on recordings. Update package for release * Fix incorrect tag Since Matrix the wrong XML tag was being extracted so the genre colour and genre text have been incorrect. Will backport to Omega with changes from @ksooo improving the port from Omega in this PR. --- CMakeLists.txt | 8 +- depends/common/tinyxml2/CMakeLists.txt | 87 ------------------- depends/common/tinyxml2/flags.txt | 1 + depends/common/tinyxml2/tinyxml2.sha256 | 2 +- depends/common/tinyxml2/tinyxml2.txt | 2 +- depends/common/zlib/01-build-static.patch | 36 ++++++++ .../zlib/02-disable-example-binaries.patch | 28 ++++++ .../zlib/03-install-pkgconfig-in-lib.patch | 12 +++ depends/common/zlib/zlib.sha256 | 1 + depends/common/zlib/zlib.txt | 1 + pvr.nextpvr/addon.xml.in | 4 +- pvr.nextpvr/changelog.txt | 9 ++ src/BackendRequest.cpp | 54 +++++++----- src/BackendRequest.h | 2 +- src/Channels.cpp | 66 ++++++++++++-- src/Channels.h | 3 + src/EPG.cpp | 67 ++++++++++++-- src/EPG.h | 1 + src/Recordings.cpp | 53 +++++++---- src/buffers/ClientTimeshift.h | 4 + src/pvrclient-nextpvr.cpp | 1 + 21 files changed, 289 insertions(+), 153 deletions(-) delete mode 100644 depends/common/tinyxml2/CMakeLists.txt create mode 100644 depends/common/tinyxml2/flags.txt create mode 100644 depends/common/zlib/01-build-static.patch create mode 100644 depends/common/zlib/02-disable-example-binaries.patch create mode 100644 depends/common/zlib/03-install-pkgconfig-in-lib.patch create mode 100644 depends/common/zlib/zlib.sha256 create mode 100644 depends/common/zlib/zlib.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 8dd9170a..0cc65450 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,9 +4,11 @@ project(pvr.nextpvr) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}) find_package(Kodi REQUIRED) +find_package(ZLIB REQUIRED) find_package(TinyXML2 REQUIRED) include_directories(${TINYXML2_INCLUDE_DIR} + ${ZLIB_INCLUDE_DIRS} ${KODI_INCLUDE_DIR}/..) # Hack way with "/..", need bigger Kodi cmake rework to match right include ways set(NEXTPVR_SOURCES src/addon.cpp @@ -51,7 +53,11 @@ set(NEXTPVR_HEADERS src/addon.h src/utilities/SettingsMigration.h src/utilities/XMLUtils.h) -SET(DEPLIBS ${TINYXML2_LIBRARIES}) +SET(DEPLIBS ${TINYXML2_LIBRARIES} + ${ZLIB_LIBRARIES}) + +message(STATUS "ZLIB_LIBRARIES: ${ZLIB_LIBRARIES}") + if(WIN32) list(APPEND DEPLIBS ws2_32) add_definitions(-D_WINSOCKAPI_ -D_WINSOCK_DEPRECATED_NO_WARNINGS) diff --git a/depends/common/tinyxml2/CMakeLists.txt b/depends/common/tinyxml2/CMakeLists.txt deleted file mode 100644 index ed071d36..00000000 --- a/depends/common/tinyxml2/CMakeLists.txt +++ /dev/null @@ -1,87 +0,0 @@ -cmake_minimum_required(VERSION 2.6 FATAL_ERROR) -cmake_policy(VERSION 2.6) - -project(tinyxml2) -include(GNUInstallDirs) -#enable_testing() - -#CMAKE_BUILD_TOOL - -################################ -# set lib version here - -set(GENERIC_LIB_VERSION "1.0.12") -set(GENERIC_LIB_SOVERSION "1") - - -################################ -# Add common source - -include_directories("${CMAKE_CURRENT_SOURCE_DIR}/.") - -################################ -# Add custom target to copy all data - -set(TARGET_DATA_COPY DATA_COPY) -if(${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_BINARY_DIR}) - add_custom_target( - ${TARGET_DATA_COPY} - COMMAND ${CMAKE_COMMAND} -E echo "In source build") -else(${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_BINARY_DIR}) - make_directory(${CMAKE_CURRENT_BINARY_DIR}/resources/) - add_custom_target( - ${TARGET_DATA_COPY} - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/resources/dream.xml ${CMAKE_CURRENT_BINARY_DIR}/resources/ - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/resources/empty.xml ${CMAKE_CURRENT_BINARY_DIR}/resources/ - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/resources/utf8test.xml ${CMAKE_CURRENT_BINARY_DIR}/resources/ - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/resources/utf8testverify.xml ${CMAKE_CURRENT_BINARY_DIR}/resources/) -endif(${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_BINARY_DIR}) - -################################ -# Add definitions - -if(MSVC) - add_definitions(-D_CRT_SECURE_NO_WARNINGS) -endif(MSVC) - -################################ -# Add targets -set(BUILD_STATIC_LIBS ON CACHE BOOL "Set to ON to build static libraries") -if(BUILD_STATIC_LIBS) - add_library(tinyxml2 STATIC tinyxml2.cpp tinyxml2.h) -else(BUILD_STATIC_LIBS) - add_library(tinyxml2 SHARED tinyxml2.cpp tinyxml2.h) -endif(BUILD_STATIC_LIBS) -set_target_properties(tinyxml2 PROPERTIES - VERSION "${GENERIC_LIB_VERSION}" - SOVERSION "${GENERIC_LIB_SOVERSION}") - -add_executable(test xmltest.cpp) -add_dependencies(test tinyxml2) -add_dependencies(test ${TARGET_DATA_COPY}) -target_link_libraries(test tinyxml2) - - -if(BUILD_STATIC_LIBS) - install(TARGETS tinyxml2 - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) -else(BUILD_STATIC_LIBS) - install(TARGETS tinyxml2 - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) -endif(BUILD_STATIC_LIBS) -install(FILES tinyxml2.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) - -foreach(p LIB INCLUDE) - set(var CMAKE_INSTALL_${p}DIR) - if(NOT IS_ABSOLUTE "${${var}}") - set(${var} "${CMAKE_INSTALL_PREFIX}/${${var}}") - endif() -endforeach() - -configure_file(tinyxml2.pc.in tinyxml2.pc @ONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/tinyxml2.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) - -#add_test(test ${SAMPLE_NAME} COMMAND $) diff --git a/depends/common/tinyxml2/flags.txt b/depends/common/tinyxml2/flags.txt new file mode 100644 index 00000000..1f6c848c --- /dev/null +++ b/depends/common/tinyxml2/flags.txt @@ -0,0 +1 @@ +-Dtinyxml2_BUILD_TESTING=0 \ No newline at end of file diff --git a/depends/common/tinyxml2/tinyxml2.sha256 b/depends/common/tinyxml2/tinyxml2.sha256 index 30daced5..ac66b8f6 100644 --- a/depends/common/tinyxml2/tinyxml2.sha256 +++ b/depends/common/tinyxml2/tinyxml2.sha256 @@ -1 +1 @@ -68ebd396a4220d5a9b5a621c6e9c66349c5cfdf5efaea3f16e3bb92e45f4e2a3 +cc2f1417c308b1f6acc54f88eb70771a0bf65f76282ce5c40e54cfe52952702c \ No newline at end of file diff --git a/depends/common/tinyxml2/tinyxml2.txt b/depends/common/tinyxml2/tinyxml2.txt index 109367d3..c775015c 100644 --- a/depends/common/tinyxml2/tinyxml2.txt +++ b/depends/common/tinyxml2/tinyxml2.txt @@ -1 +1 @@ -tinyxml2 https://github.com/leethomason/tinyxml2/archive/7.1.0.tar.gz +tinyxml2 https://github.com/leethomason/tinyxml2/archive/refs/tags/9.0.0.tar.gz diff --git a/depends/common/zlib/01-build-static.patch b/depends/common/zlib/01-build-static.patch new file mode 100644 index 00000000..7ae95b94 --- /dev/null +++ b/depends/common/zlib/01-build-static.patch @@ -0,0 +1,36 @@ +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -147,10 +147,11 @@ + set(ZLIB_DLL_SRCS ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj) + endif(MINGW) + +-add_library(zlib SHARED ${ZLIB_SRCS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) +-add_library(zlibstatic STATIC ${ZLIB_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) +-set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL) +-set_target_properties(zlib PROPERTIES SOVERSION 1) ++add_library(zlib ${ZLIB_SRCS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) ++if(BUILD_SHARED_LIBS) ++ set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL) ++ set_target_properties(zlib PROPERTIES SOVERSION 1) ++endif() + + if(NOT CYGWIN) + # This property causes shared libraries on Linux to have the full version +@@ -165,7 +166,7 @@ + + if(UNIX) + # On unix-like platforms the library is almost always called libz +- set_target_properties(zlib zlibstatic PROPERTIES OUTPUT_NAME z) ++ set_target_properties(zlib PROPERTIES OUTPUT_NAME z) + if(NOT APPLE) + set_target_properties(zlib PROPERTIES LINK_FLAGS "-Wl,--version-script,\"${CMAKE_CURRENT_SOURCE_DIR}/zlib.map\"") + endif() +@@ -175,7 +176,7 @@ + endif() + + if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL ) +- install(TARGETS zlib zlibstatic ++ install(TARGETS zlib + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" + ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" + LIBRARY DESTINATION "${INSTALL_LIB_DIR}" ) diff --git a/depends/common/zlib/02-disable-example-binaries.patch b/depends/common/zlib/02-disable-example-binaries.patch new file mode 100644 index 00000000..e0619bcc --- /dev/null +++ b/depends/common/zlib/02-disable-example-binaries.patch @@ -0,0 +1,28 @@ +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -226,25 +226,3 @@ endif() + if(NOT SKIP_INSTALL_FILES AND NOT SKIP_INSTALL_ALL ) + install(FILES ${ZLIB_PC} DESTINATION "${INSTALL_PKGCONFIG_DIR}") + endif() +- +-#============================================================================ +-# Example binaries +-#============================================================================ +- +-add_executable(example test/example.c) +-target_link_libraries(example zlib) +-add_test(example example) +- +-add_executable(minigzip test/minigzip.c) +-target_link_libraries(minigzip zlib) +- +-if(HAVE_OFF64_T) +- add_executable(example64 test/example.c) +- target_link_libraries(example64 zlib) +- set_target_properties(example64 PROPERTIES COMPILE_FLAGS "-D_FILE_OFFSET_BITS=64") +- add_test(example64 example64) +- +- add_executable(minigzip64 test/minigzip.c) +- target_link_libraries(minigzip64 zlib) +- set_target_properties(minigzip64 PROPERTIES COMPILE_FLAGS "-D_FILE_OFFSET_BITS=64") +-endif() diff --git a/depends/common/zlib/03-install-pkgconfig-in-lib.patch b/depends/common/zlib/03-install-pkgconfig-in-lib.patch new file mode 100644 index 00000000..9c9be0f2 --- /dev/null +++ b/depends/common/zlib/03-install-pkgconfig-in-lib.patch @@ -0,0 +1,12 @@ +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -12,7 +12,7 @@ set(INSTALL_BIN_DIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Installation direc + set(INSTALL_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Installation directory for libraries") + set(INSTALL_INC_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH "Installation directory for headers") + set(INSTALL_MAN_DIR "${CMAKE_INSTALL_PREFIX}/share/man" CACHE PATH "Installation directory for manual pages") +-set(INSTALL_PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/share/pkgconfig" CACHE PATH "Installation directory for pkgconfig (.pc) files") ++set(INSTALL_PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig" CACHE PATH "Installation directory for pkgconfig (.pc) files") + + include(CheckTypeSize) + include(CheckFunctionExists) + diff --git a/depends/common/zlib/zlib.sha256 b/depends/common/zlib/zlib.sha256 new file mode 100644 index 00000000..d765328c --- /dev/null +++ b/depends/common/zlib/zlib.sha256 @@ -0,0 +1 @@ +d14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98 \ No newline at end of file diff --git a/depends/common/zlib/zlib.txt b/depends/common/zlib/zlib.txt new file mode 100644 index 00000000..1c345b9a --- /dev/null +++ b/depends/common/zlib/zlib.txt @@ -0,0 +1 @@ +zlib http://mirrors.kodi.tv/build-deps/sources/zlib-1.2.13.tar.xz diff --git a/pvr.nextpvr/addon.xml.in b/pvr.nextpvr/addon.xml.in index ea6791b3..a1c8d263 100644 --- a/pvr.nextpvr/addon.xml.in +++ b/pvr.nextpvr/addon.xml.in @@ -1,11 +1,11 @@ @ADDON_DEPENDS@ - + #include #include +#include using namespace NextPVR::utilities; @@ -96,41 +97,46 @@ namespace NextPVR response.append(buffer, count); } stream.Close(); - retError = doc.Parse(response.c_str()); - if (retError == tinyxml2::XML_SUCCESS) + retError = ParseMethodRequest(doc, response); + } + int milliseconds = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count()); + kodi::Log(ADDON_LOG_DEBUG, "DoMethodRequest %s %d %d %d", resource.c_str(), retError, response.length(), milliseconds); + return retError; + } + + tinyxml2::XMLError Request::ParseMethodRequest(tinyxml2::XMLDocument& doc, const std::string& xml) + { + tinyxml2::XMLError retError = doc.Parse(xml.c_str());; + if (retError == tinyxml2::XML_SUCCESS) + { + const char* attrib = doc.RootElement()->Attribute("stat"); + if (attrib == nullptr || strcmp(attrib, "ok")) { - const char* attrib = doc.RootElement()->Attribute("stat"); - if ( attrib == nullptr || strcmp(attrib, "ok")) + kodi::Log(ADDON_LOG_DEBUG, "DoMethodRequest bad return %s", attrib); + retError = tinyxml2::XML_NO_ATTRIBUTE; + if (!strcmp(attrib, "fail")) { - kodi::Log(ADDON_LOG_DEBUG, "DoMethodRequest bad return %s", attrib); - retError = tinyxml2::XML_NO_ATTRIBUTE; - if (!strcmp(attrib, "fail")) + const tinyxml2::XMLElement* err = doc.RootElement()->FirstChildElement("err"); + if (err) { - const tinyxml2::XMLElement* err = doc.RootElement()->FirstChildElement("err"); - if (err) + const char* code = err->Attribute("code"); + if (code) { - const char* code = err->Attribute("code"); - if (code) + kodi::Log(ADDON_LOG_DEBUG, "DoMethodRequest error code %s", code); + if (atoi(code) == 8) { - kodi::Log(ADDON_LOG_DEBUG, "DoMethodRequest error code %s", code); - if (atoi(code) == 8) - { - ClearSID(); - retError = tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED; - //m_pvrclient.ResetConnection(); - } + ClearSID(); + retError = tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED; } } } } - else - { - RenewSID(); - } + } + else + { + RenewSID(); } } - int milliseconds = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count()); - kodi::Log(ADDON_LOG_DEBUG, "DoMethodRequest %s %d %d %d", resource.c_str(), retError, response.length(), milliseconds); return retError; } diff --git a/src/BackendRequest.h b/src/BackendRequest.h index 49fab96a..37ee0967 100644 --- a/src/BackendRequest.h +++ b/src/BackendRequest.h @@ -53,7 +53,7 @@ namespace NextPVR private: Request(Request const&) = delete; void operator=(Request const&) = delete; - + tinyxml2::XMLError ParseMethodRequest(tinyxml2::XMLDocument& doc, const std::string& xml); std::shared_ptr m_settings; mutable std::mutex m_mutexRequest; time_t m_start = 0; diff --git a/src/Channels.cpp b/src/Channels.cpp index 94c5d58c..95baf002 100644 --- a/src/Channels.cpp +++ b/src/Channels.cpp @@ -10,6 +10,7 @@ #include "pvrclient-nextpvr.h" #include +#include "zlib.h" using namespace NextPVR; using namespace NextPVR::utilities; @@ -29,7 +30,7 @@ int Channels::GetNumChannels() if (channelCount == 0) { tinyxml2::XMLDocument doc; - if (m_request.DoMethodRequest("channel.list", doc) == tinyxml2::XML_SUCCESS) + if (ReadCachedChannelList(doc) == tinyxml2::XML_SUCCESS) { tinyxml2::XMLNode* channelsNode = doc.RootElement()->FirstChildElement("channels"); tinyxml2::XMLNode* pChannelNode; @@ -100,7 +101,7 @@ PVR_ERROR Channels::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& r } tinyxml2::XMLDocument doc; - if (m_request.DoMethodRequest("channel.list&extras=true", doc) == tinyxml2::XML_SUCCESS) + if (ReadCachedChannelList(doc) == tinyxml2::XML_SUCCESS) { tinyxml2::XMLNode* channelsNode = doc.RootElement()->FirstChildElement("channels"); tinyxml2::XMLNode* pChannelNode; @@ -200,7 +201,7 @@ PVR_ERROR Channels::GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsRe selectedGroups.clear(); bool hasAllChannels = false; tinyxml2::XMLDocument doc; - if (m_request.DoMethodRequest("channel.list&extras=true", doc) == tinyxml2::XML_SUCCESS) + if (ReadCachedChannelList(doc) == tinyxml2::XML_SUCCESS) { tinyxml2::XMLNode* channelsNode = doc.RootElement()->FirstChildElement("channels"); tinyxml2::XMLNode* pChannelNode; @@ -282,21 +283,21 @@ PVR_ERROR Channels::GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsRe PVR_ERROR Channels::GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group, kodi::addon::PVRChannelGroupMembersResultSet& results) { - std::string request; PVR_ERROR returnValue = PVR_ERROR_SERVER_ERROR; + tinyxml2::XMLDocument doc; + tinyxml2::XMLError retCode; if (group.GetGroupName() == GetAllChannelsGroupName(group.GetIsRadio())) { - request = "channel.list"; + retCode = ReadCachedChannelList(doc); } else { const std::string encodedGroupName = UriEncode(group.GetGroupName()); - request = "channel.list&group_id=" + encodedGroupName; + retCode = m_request.DoMethodRequest("channel.list&group_id=" + encodedGroupName, doc); } - tinyxml2::XMLDocument doc; - if (m_request.DoMethodRequest(request, doc) == tinyxml2::XML_SUCCESS) + if (retCode == tinyxml2::XML_SUCCESS) { tinyxml2::XMLNode* channelsNode = doc.RootElement()->FirstChildElement("channels"); tinyxml2::XMLNode* pChannelNode; @@ -352,7 +353,7 @@ bool Channels::IsChannelAPlugin(int uid) void Channels::LoadLiveStreams() { std::string response; - const std::string URL = "/public/LiveStreams.xml"; + const std::string URL = "/public/service.xml"; m_liveStreams.clear(); if (m_request.DoRequest(URL, response) == HTTP_OK) { @@ -388,3 +389,50 @@ void Channels::LoadLiveStreams() } } } +bool Channels::CacheAllChannels(time_t updateTime) +{ + std::string response; + const std::string filename = kodi::tools::StringUtils::Format("%s%s", m_settings->m_instanceDirectory.c_str(), "channel.cache"); + gzFile gz_file; + struct { time_t update; unsigned long size; } header{0,0}; + if (kodi::vfs::FileExists(filename)) + { + gz_file = gzopen(kodi::vfs::TranslateSpecialProtocol(filename).c_str(), "rb"); + gzread(gz_file, (void*)&header, sizeof(header)); + gzclose(gz_file); + if (updateTime == header.update) + { + return true; + } + } + if (m_request.DoRequest("/service?method=channel.list&extras=true", response) == HTTP_OK) + { + gz_file = gzopen(kodi::vfs::TranslateSpecialProtocol(filename).c_str(), "wb"); + header.size = sizeof(char) * response.size(); + header.update = updateTime - m_settings->m_serverTimeOffset; + gzwrite(gz_file, (void*)&header, sizeof(header)); + gzwrite(gz_file, (void*)(response.c_str()), header.size); + gzclose(gz_file); + return true; + } + return false; +} + +tinyxml2::XMLError Channels::ReadCachedChannelList(tinyxml2::XMLDocument& doc) +{ + auto start = std::chrono::steady_clock::now(); + std::string response; + const std::string filename = kodi::tools::StringUtils::Format("%s%s", m_settings->m_instanceDirectory.c_str(), "channel.cache"); + struct { time_t update; unsigned long size; } header{0,0}; + gzFile gz_file = gzopen(kodi::vfs::TranslateSpecialProtocol(filename).c_str(), "rb"); + gzread(gz_file, (void*)&header, sizeof(header)); + response.resize(header.size / sizeof(char)); + gzread(gz_file, (void*)response.data(), header.size); + gzclose(gz_file); + tinyxml2::XMLError xmlCheck = doc.Parse(response.c_str()); + if (doc.Parse(response.c_str()) != tinyxml2::XML_SUCCESS) + return m_request.DoMethodRequest("channel.list&extras=true", doc); + int milliseconds = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count()); + kodi::Log(ADDON_LOG_DEBUG, "ReadCachedChannelList %d %d %d %d", m_settings->m_instanceNumber, xmlCheck, response.length(), milliseconds); + return xmlCheck; +} diff --git a/src/Channels.h b/src/Channels.h index d86d9c5e..06054de0 100644 --- a/src/Channels.h +++ b/src/Channels.h @@ -25,6 +25,8 @@ namespace NextPVR /* Channel handling */ int GetNumChannels(); + bool CacheAllChannels(time_t updateTime); + PVR_ERROR GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& results); /* Channel group handling */ PVR_ERROR GetChannelGroupsAmount(int& amount); @@ -51,5 +53,6 @@ namespace NextPVR std::string GetChannelIcon(int channelID); const std::shared_ptr m_settings; Request& m_request; + tinyxml2::XMLError ReadCachedChannelList(tinyxml2::XMLDocument& doc); }; } // namespace NextPVR diff --git a/src/EPG.cpp b/src/EPG.cpp index ac71f8d4..499b2940 100644 --- a/src/EPG.cpp +++ b/src/EPG.cpp @@ -68,8 +68,6 @@ PVR_ERROR EPG::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi:: } } - broadcast.SetYear(XMLUtils::GetIntValue(pListingNode, "year")); - std::string startTime; std::string endTime; XMLUtils::GetString(pListingNode, "start", startTime); @@ -80,7 +78,6 @@ PVR_ERROR EPG::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi:: const std::string oidLookup(endTime + ":" + std::to_string(channelUid)); broadcast.SetTitle(title); - broadcast.SetEpisodeName(subtitle); broadcast.SetUniqueChannelId(channelUid); broadcast.SetStartTime(stol(startTime)); broadcast.SetUniqueBroadcastId(stoi(endTime)); @@ -111,7 +108,7 @@ PVR_ERROR EPG::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi:: { // genre type broadcast.SetGenreType(XMLUtils::GetIntValue(pListingNode, "genre_type")); - broadcast.SetGenreSubType(XMLUtils::GetIntValue(pListingNode, "genre_sub_type")); + broadcast.SetGenreSubType(XMLUtils::GetIntValue(pListingNode, "genre_subtype")); } std::string allGenres; @@ -132,13 +129,67 @@ PVR_ERROR EPG::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi:: } } - broadcast.SetSeriesNumber(XMLUtils::GetIntValue(pListingNode, "season", EPG_TAG_INVALID_SERIES_EPISODE)); - broadcast.SetEpisodeNumber(XMLUtils::GetIntValue(pListingNode, "episode", EPG_TAG_INVALID_SERIES_EPISODE)); + + int season{EPG_TAG_INVALID_SERIES_EPISODE}; + int episode{EPG_TAG_INVALID_SERIES_EPISODE}; + XMLUtils::GetInt(pListingNode, "season", season); + XMLUtils::GetInt(pListingNode, "episode", episode); + broadcast.SetEpisodeNumber(episode); broadcast.SetEpisodePartNumber(EPG_TAG_INVALID_SERIES_EPISODE); + // Backend could send episode only as S00 and parts are not supported + if (season <= 0 || episode == EPG_TAG_INVALID_SERIES_EPISODE) + { + static std::regex base_regex("^.*\\([eE][pP](\\d+)(?:/?(\\d+))?\\)"); + std::smatch base_match; + if (std::regex_search(description, base_match, base_regex)) + { + broadcast.SetEpisodeNumber(std::atoi(base_match[1].str().c_str())); + if (base_match[2].matched) + broadcast.SetEpisodePartNumber(std::atoi(base_match[2].str().c_str())); + } + else if (std::regex_search(description, base_match, std::regex("^([1-9]\\d*)/([1-9]\\d*)\\."))) + { + broadcast.SetEpisodeNumber(std::atoi(base_match[1].str().c_str())); + broadcast.SetEpisodePartNumber(std::atoi(base_match[2].str().c_str())); + } + } + if (season != EPG_TAG_INVALID_SERIES_EPISODE) + { + // clear out NextPVR formatted data, Kodi supports S/E display + if (subtitle == kodi::tools::StringUtils::Format("S%02dE%02d", season, episode)) + { + subtitle.clear(); + } + if (season == 0) + season = EPG_TAG_INVALID_SERIES_EPISODE; + } + broadcast.SetSeriesNumber(season); + broadcast.SetEpisodeName(subtitle); + + int year{YEAR_NOT_SET}; + if (XMLUtils::GetInt(pListingNode, "year", year)) + { + broadcast.SetYear(year); + } std::string original; - XMLUtils::GetString(pListingNode, "original", original); - broadcast.SetFirstAired(original); + if (XMLUtils::GetString(pListingNode, "original", original)) + { + // For movies with YYYY-MM-DD use only YYYY + if (broadcast.GetGenreType() == EPG_EVENT_CONTENTMASK_MOVIEDRAMA && broadcast.GetGenreSubType() == EPG_EVENT_CONTENTSUBMASK_MOVIEDRAMA_GENERAL + && year == YEAR_NOT_SET && original.length() > 4) + { + const std::string originalYear = kodi::tools::StringUtils::Mid(original, 0, 4); + year = std::atoi(originalYear.c_str()); + if (year != 0) + broadcast.SetYear(year); + } + else + { + broadcast.SetFirstAired(original); + } + } + bool firstrun; if (XMLUtils::GetBoolean(pListingNode, "firstrun", firstrun)) diff --git a/src/EPG.h b/src/EPG.h index e717e692..3c38ec23 100644 --- a/src/EPG.h +++ b/src/EPG.h @@ -15,6 +15,7 @@ namespace NextPVR { + const int YEAR_NOT_SET = -1; class ATTR_DLL_LOCAL EPG { public: diff --git a/src/Recordings.cpp b/src/Recordings.cpp index ed5c22f0..d7a27da3 100644 --- a/src/Recordings.cpp +++ b/src/Recordings.cpp @@ -321,22 +321,17 @@ bool Recordings::UpdatePvrRecording(const tinyxml2::XMLNode* pRecordingNode, kod XMLUtils::GetString(pRecordingNode, "id", buffer); tag.SetRecordingId(buffer); - tag.SetSeriesNumber(PVR_RECORDING_INVALID_SERIES_EPISODE); - tag.SetEpisodeNumber(PVR_RECORDING_INVALID_SERIES_EPISODE); - - if (XMLUtils::GetString(pRecordingNode, "subtitle", buffer)) + if (ParseNextPVRSubtitle(pRecordingNode, tag)) { - if (ParseNextPVRSubtitle(pRecordingNode, tag)) + if (m_settings->m_separateSeasons && multipleSeasons && tag.GetSeriesNumber() != PVR_RECORDING_INVALID_SERIES_EPISODE) { - if (m_settings->m_separateSeasons && multipleSeasons && tag.GetSeriesNumber() != PVR_RECORDING_INVALID_SERIES_EPISODE) - { - if (status != "Failed") - tag.SetDirectory(kodi::tools::StringUtils::Format("/%s/%s %d", tag.GetTitle().c_str(), kodi::addon::GetLocalizedString(20373).c_str(), tag.GetSeriesNumber())); - else - tag.SetDirectory(kodi::tools::StringUtils::Format("/%s/%s/%s %d", kodi::addon::GetLocalizedString(30166).c_str(),tag.GetTitle().c_str(), kodi::addon::GetLocalizedString(20373).c_str(), tag.GetSeriesNumber())); - } + if (status != "Failed") + tag.SetDirectory(kodi::tools::StringUtils::Format("/%s/%s %d", tag.GetTitle().c_str(), kodi::addon::GetLocalizedString(20373).c_str(), tag.GetSeriesNumber())); + else + tag.SetDirectory(kodi::tools::StringUtils::Format("/%s/%s/%s %d", kodi::addon::GetLocalizedString(30166).c_str(),tag.GetTitle().c_str(), kodi::addon::GetLocalizedString(20373).c_str(), tag.GetSeriesNumber())); } } + tag.SetYear(XMLUtils::GetIntValue(pRecordingNode, "year")); std::string original; @@ -473,24 +468,27 @@ bool Recordings::ParseNextPVRSubtitle(const tinyxml2::XMLNode *pRecordingNode, k bool hasSeasonEpisode = false; if (XMLUtils::GetString(pRecordingNode, "subtitle", buffer)) { - std::regex base_regex("S(\\d{2,4})E(\\d+) - ?(.+)?"); + static std::regex base_regex("S(\\d{2,4})E(\\d+)(?: - ?(.+)$)?"); std::smatch base_match; // note NextPVR does not support S0 for specials - if (std::regex_match(buffer, base_match, base_regex)) + if (std::regex_search(buffer, base_match, base_regex)) { if (base_match.size() == 3 || base_match.size() == 4) { - std::ssub_match base_sub_match = base_match[1]; - tag.SetSeriesNumber(std::stoi(base_sub_match.str())); + int season = std::stoi(base_sub_match.str()); + if (season != 0) + { + tag.SetSeriesNumber(season); + hasSeasonEpisode = true; + } base_sub_match = base_match[2]; tag.SetEpisodeNumber(std::stoi(base_sub_match.str())); - if (base_match.size() == 4) + if (base_match[3].matched) { base_sub_match = base_match[3]; tag.SetEpisodeName(base_sub_match.str()); } - hasSeasonEpisode = true; } } else @@ -504,7 +502,7 @@ bool Recordings::ParseNextPVRSubtitle(const tinyxml2::XMLNode *pRecordingNode, k std::string recordingFile; if (XMLUtils::GetString(pRecordingNode, "file", recordingFile)) { - std::regex base_regex("S(\\d{2,4})E(\\d+)"); + static std::regex base_regex("S(\\d{2,4})E(\\d+)"); std::smatch base_match; if (std::regex_search(recordingFile, base_match, base_regex)) { @@ -518,6 +516,23 @@ bool Recordings::ParseNextPVRSubtitle(const tinyxml2::XMLNode *pRecordingNode, k } } } + const std::string plot = tag.GetPlot(); + if (tag.GetEpisodeNumber() == PVR_RECORDING_INVALID_SERIES_EPISODE && !plot.empty()); + { + static std::regex base_regex("^.*\\([eE][pP](\\d+)(?:/?(\\d+))?\\)"); + std::smatch base_match; + if (std::regex_search(plot, base_match, base_regex)) + { + tag.SetEpisodeNumber(std::atoi(base_match[1].str().c_str())); + if (base_match[2].matched) + tag.SetEpisodePartNumber(std::atoi(base_match[2].str().c_str())); + } + else if (std::regex_search(plot, base_match, std::regex("^([1-9]\\d*)/([1-9]\\d*)\\."))) + { + tag.SetEpisodeNumber(std::atoi(base_match[1].str().c_str())); + tag.SetEpisodePartNumber(std::atoi(base_match[2].str().c_str())); + } + } } return hasSeasonEpisode; } diff --git a/src/buffers/ClientTimeshift.h b/src/buffers/ClientTimeshift.h index eff11a4c..3cf5b605 100644 --- a/src/buffers/ClientTimeshift.h +++ b/src/buffers/ClientTimeshift.h @@ -92,5 +92,9 @@ namespace timeshift { } virtual PVR_ERROR GetStreamTimes(kodi::addon::PVRStreamTimes& times) override; + virtual bool IsRealTimeStream() const override + { + return std::time(nullptr) - m_streamStart < 10 + m_prebuffer; + } }; } diff --git a/src/pvrclient-nextpvr.cpp b/src/pvrclient-nextpvr.cpp index 0830a752..7e08df9f 100644 --- a/src/pvrclient-nextpvr.cpp +++ b/src/pvrclient-nextpvr.cpp @@ -305,6 +305,7 @@ void cPVRClientNextPVR::ConfigurePostConnectionOptions() if (m_lastEPGUpdateTime == 0) m_request.GetLastUpdate("system.epg.summary", m_lastEPGUpdateTime); + m_channels.CacheAllChannels(m_lastEPGUpdateTime); } /* IsUp()