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()