diff --git a/pvr.iptvsimple/addon.xml.in b/pvr.iptvsimple/addon.xml.in index 50120d2c..efdc7ce4 100644 --- a/pvr.iptvsimple/addon.xml.in +++ b/pvr.iptvsimple/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ diff --git a/pvr.iptvsimple/changelog.txt b/pvr.iptvsimple/changelog.txt index 64ff7afc..cfdf7081 100644 --- a/pvr.iptvsimple/changelog.txt +++ b/pvr.iptvsimple/changelog.txt @@ -1,3 +1,9 @@ +v20.11.1 +- EPG entry selection criteria for timezone shift calculation works for Media as well as channels +- Fix being able to disable media from settings +- Fix group name in recording path being disabled in addon settings when media is not enabled +- Add support for Genres for Media + v20.11.0 - M3U format specifier to override realtime processing in Kodi PVR where the stream should not be treated like VOD/Media in the UI - Revert the support of Async connect that was causing Connection Lost error messages for users of this add-on diff --git a/pvr.iptvsimple/resources/instance-settings.xml b/pvr.iptvsimple/resources/instance-settings.xml index 051f132e..78a2ff4e 100644 --- a/pvr.iptvsimple/resources/instance-settings.xml +++ b/pvr.iptvsimple/resources/instance-settings.xml @@ -608,6 +608,9 @@ + + true + diff --git a/src/iptvsimple/Epg.cpp b/src/iptvsimple/Epg.cpp index 1db4e6a2..9ac6286b 100644 --- a/src/iptvsimple/Epg.cpp +++ b/src/iptvsimple/Epg.cpp @@ -33,6 +33,8 @@ Epg::Epg(kodi::addon::CInstancePVRClient* client, Channels& channels, Media& med { MoveOldGenresXMLFileToNewLocation(); } + + m_media.SetGenreMappings(m_genreMappings); } bool Epg::Init(int epgMaxPastDays, int epgMaxFutureDays) @@ -83,7 +85,7 @@ void Epg::SetEPGMaxFutureDays(int epgMaxFutureDays) m_epgMaxFutureDaysSeconds = DEFAULT_EPG_MAX_DAYS * 24 * 60 * 60; } -bool Epg::LoadEPG(time_t start, time_t end) +bool Epg::LoadEPG(time_t epgWindowStart, time_t epgWindowEnd) { auto started = std::chrono::high_resolution_clock::now(); Logger::Log(LEVEL_DEBUG, "%s - EPG Load Start", __FUNCTION__); @@ -125,7 +127,7 @@ bool Epg::LoadEPG(time_t start, time_t end) if (!LoadChannelEpgs(rootElement)) return false; - LoadEpgEntries(rootElement, start, end); + LoadEpgEntries(rootElement, epgWindowStart, epgWindowEnd); xmlDoc.reset(); } @@ -283,7 +285,7 @@ bool Epg::LoadChannelEpgs(const xml_node& rootElement) return true; } -void Epg::LoadEpgEntries(const xml_node& rootElement, int start, int end) +void Epg::LoadEpgEntries(const xml_node& rootElement, int epgWindowStart, int epgWindowEnd) { int minShiftTime = m_epgTimeShift; int maxShiftTime = m_epgTimeShift; @@ -299,6 +301,14 @@ void Epg::LoadEpgEntries(const xml_node& rootElement, int start, int end) if (channel.GetTvgShift() + m_epgTimeShift > maxShiftTime) maxShiftTime = channel.GetTvgShift() + m_epgTimeShift; } + + for (const auto& mediaEntry : m_media.GetMediaEntryList()) + { + if (mediaEntry.GetTvgShift() + m_epgTimeShift < minShiftTime) + minShiftTime = mediaEntry.GetTvgShift() + m_epgTimeShift; + if (mediaEntry.GetTvgShift() + m_epgTimeShift > maxShiftTime) + maxShiftTime = mediaEntry.GetTvgShift() + m_epgTimeShift; + } } ChannelEpg* channelEpg = nullptr; @@ -317,7 +327,7 @@ void Epg::LoadEpgEntries(const xml_node& rootElement, int start, int end) } EpgEntry entry{m_settings}; - if (entry.UpdateFrom(programmeNode, id, start, end, minShiftTime, maxShiftTime)) + if (entry.UpdateFrom(programmeNode, id, epgWindowStart, epgWindowEnd, minShiftTime, maxShiftTime)) { count++; @@ -350,23 +360,23 @@ void Epg::ReloadEPG() } } -PVR_ERROR Epg::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi::addon::PVREPGTagsResultSet& results) +PVR_ERROR Epg::GetEPGForChannel(int channelUid, time_t epgWindowStart, time_t epgWindowEnd, kodi::addon::PVREPGTagsResultSet& results) { for (const auto& myChannel : m_channels.GetChannelsList()) { if (myChannel.GetUniqueId() != channelUid) continue; - if (start > m_lastStart || end > m_lastEnd) + if (epgWindowStart > m_lastStart || epgWindowEnd > m_lastEnd) { // reload EPG for new time interval only - LoadEPG(start, end); + LoadEPG(epgWindowStart, epgWindowEnd); { MergeEpgDataIntoMedia(); // doesn't matter is epg loaded or not we shouldn't try to load it for same interval - m_lastStart = static_cast(start); - m_lastEnd = static_cast(end); + m_lastStart = static_cast(epgWindowStart); + m_lastEnd = static_cast(epgWindowEnd); } } @@ -379,7 +389,7 @@ PVR_ERROR Epg::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi:: for (auto& epgEntryPair : channelEpg->GetEpgEntries()) { auto& epgEntry = epgEntryPair.second; - if ((epgEntry.GetEndTime() + shift) < start) + if ((epgEntry.GetEndTime() + shift) < epgWindowStart) continue; kodi::addon::PVREPGTag tag; @@ -388,7 +398,7 @@ PVR_ERROR Epg::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi:: results.Add(tag); - if ((epgEntry.GetStartTime() + shift) > end) + if ((epgEntry.GetStartTime() + shift) > epgWindowEnd) break; } @@ -609,6 +619,6 @@ void Epg::MergeEpgDataIntoMedia() // then return the first entry as matching. This is a common pattern // for channel that only contain a single media item. if (channelEpg && !channelEpg->GetEpgEntries().empty()) - mediaEntry.UpdateFrom(channelEpg->GetEpgEntries().begin()->second); + mediaEntry.UpdateFrom(channelEpg->GetEpgEntries().begin()->second, m_genreMappings); } } \ No newline at end of file diff --git a/src/iptvsimple/Epg.h b/src/iptvsimple/Epg.h index c4b0317e..11787997 100644 --- a/src/iptvsimple/Epg.h +++ b/src/iptvsimple/Epg.h @@ -44,7 +44,7 @@ namespace iptvsimple bool Init(int epgMaxPastDays, int epgMaxFutureDays); - PVR_ERROR GetEPGForChannel(int channelUid, time_t start, time_t end, kodi::addon::PVREPGTagsResultSet& results); + PVR_ERROR GetEPGForChannel(int channelUid, time_t epgWindowStart, time_t epgWindowEnd, kodi::addon::PVREPGTagsResultSet& results); void SetEPGMaxPastDays(int epgMaxPastDays); void SetEPGMaxFutureDays(int epgMaxFutureDays); void Clear(); @@ -62,7 +62,7 @@ namespace iptvsimple bool GetXMLTVFileWithRetries(std::string& data); char* FillBufferFromXMLTVData(std::string& data, std::string& decompressedData); bool LoadChannelEpgs(const pugi::xml_node& rootElement); - void LoadEpgEntries(const pugi::xml_node& rootElement, int start, int end); + void LoadEpgEntries(const pugi::xml_node& rootElement, int epgWindowStart, int epgWindowEnd); bool LoadGenres(); void MergeEpgDataIntoMedia(); diff --git a/src/iptvsimple/Media.h b/src/iptvsimple/Media.h index 355d72e5..81cc05da 100644 --- a/src/iptvsimple/Media.h +++ b/src/iptvsimple/Media.h @@ -9,6 +9,7 @@ #pragma once #include "ChannelGroups.h" +#include "data/EpgGenre.h" #include "data/MediaEntry.h" #include @@ -31,6 +32,8 @@ namespace iptvsimple std::vector& GetMediaEntryList() { return m_media; } + void SetGenreMappings(std::vector& genreMappings) { m_genreMappings = genreMappings; } + private: data::MediaEntry GetMediaEntry(const std::string& mediaEntryId) const; bool IsInVirtualMediaEntryFolder(const data::MediaEntry& mediaEntry) const; @@ -38,6 +41,8 @@ namespace iptvsimple std::vector m_media; std::unordered_map m_mediaIdMap; + std::vector m_genreMappings; + bool m_haveMediaTypes = false; std::shared_ptr m_settings; diff --git a/src/iptvsimple/PlaylistLoader.cpp b/src/iptvsimple/PlaylistLoader.cpp index 45d61118..a28c7e71 100644 --- a/src/iptvsimple/PlaylistLoader.cpp +++ b/src/iptvsimple/PlaylistLoader.cpp @@ -215,9 +215,19 @@ bool PlaylistLoader::LoadPlayList() } else if (line[0] != '#') { - Logger::Log(LEVEL_DEBUG, "%s - Adding channel '%s' with URL: '%s'", __FUNCTION__, tmpChannel.GetChannelName().c_str(), line.c_str()); + Logger::Log(LEVEL_DEBUG, "%s - Adding channel or Media Entry '%s' with URL: '%s'", __FUNCTION__, tmpChannel.GetChannelName().c_str(), line.c_str()); - if ((isRealTime || overrideRealTime || !m_settings->IsMediaEnabled() || !m_settings->ShowVodAsRecordings()) && !isMediaEntry) + if (m_settings->IsMediaEnabled() && + (isMediaEntry || (m_settings->ShowVodAsRecordings() && !isRealTime))) + { + MediaEntry entry = tmpMediaEntry; + entry.UpdateFrom(tmpChannel); + entry.SetStreamURL(line); + + if (!m_media.AddMediaEntry(entry, currentChannelGroupIdList, m_channelGroups, channelHadGroups)) + Logger::Log(LEVEL_DEBUG, "%s - Counld not add media entry as an entry with the same gnenerated unique ID already exists", __func__); + } + else { // There are cases where we want the stream to be represetned as a channel with live streaming disabled // to allow features such as passthrough to work. We don't want this to be VOD as then it would be treated like media. @@ -230,15 +240,7 @@ bool PlaylistLoader::LoadPlayList() if (!m_channels.AddChannel(channel, currentChannelGroupIdList, m_channelGroups, channelHadGroups)) Logger::Log(LEVEL_DEBUG, "%s - Not adding channel '%s' as only channels with groups are supported for %s channels per add-on settings", __func__, tmpChannel.GetChannelName().c_str(), channel.IsRadio() ? "radio" : "tv"); - } - else // We have media - { - MediaEntry entry = tmpMediaEntry; - entry.UpdateFrom(tmpChannel); - entry.SetStreamURL(line); - if (!m_media.AddMediaEntry(entry, currentChannelGroupIdList, m_channelGroups, channelHadGroups)) - Logger::Log(LEVEL_DEBUG, "%s - Counld not add media entry as an entry with the same gnenerated unique ID already exists", __func__); } tmpChannel.Reset(); diff --git a/src/iptvsimple/data/EpgEntry.cpp b/src/iptvsimple/data/EpgEntry.cpp index e421b1ca..3c792e49 100644 --- a/src/iptvsimple/data/EpgEntry.cpp +++ b/src/iptvsimple/data/EpgEntry.cpp @@ -186,31 +186,40 @@ int ParseStarRating(const std::string& starRatingString) return static_cast(std::round(starRating)); } +bool FirstRun(int start, int end) +{ + return start == 0 && end == 0; +} + } // unnamed namespace bool EpgEntry::UpdateFrom(const xml_node& programmeNode, const std::string& id, - int start, int end, int minShiftTime, int maxShiftTime) + int epgWindowsStart, int epgWindowsEnd, int minShiftTime, int maxShiftTime) { std::string strStart, strStop; if (!GetAttributeValue(programmeNode, "start", strStart) || !GetAttributeValue(programmeNode, "stop", strStop)) return false; - long long tmpStart = ParseDateTime(strStart); - long long tmpEnd = ParseDateTime(strStop); + long long programmeStart = ParseDateTime(strStart); + long long programmeEnd = ParseDateTime(strStop); GetAttributeValue(programmeNode, "catchup-id", m_catchupId); m_catchupId = StringUtils::Trim(m_catchupId); - if ((tmpEnd + maxShiftTime < start) || (tmpStart + minShiftTime > end)) + // Discard only if this is not a first run AND + // - The programme end time + the max timeshift is earlier than the EPG window start OR + // - The programme start time + the min timeshift is after than the EPG window end + // I.e. we discard any programme that does not start of finish during the EPG window + if (!FirstRun(epgWindowsStart, epgWindowsEnd) && ((programmeEnd + maxShiftTime < epgWindowsStart) || (programmeStart + minShiftTime > epgWindowsEnd))) return false; - m_broadcastId = static_cast(tmpStart); + m_broadcastId = static_cast(programmeStart); m_channelId = std::atoi(id.c_str()); m_genreType = 0; m_genreSubType = 0; m_plotOutline.clear(); - m_startTime = static_cast(tmpStart); - m_endTime = static_cast(tmpEnd); + m_startTime = static_cast(programmeStart); + m_endTime = static_cast(programmeEnd); m_year = 0; m_starRating = 0; m_episodeNumber = EPG_TAG_INVALID_SERIES_EPISODE; diff --git a/src/iptvsimple/data/EpgEntry.h b/src/iptvsimple/data/EpgEntry.h index 302fff0d..5bde80b1 100644 --- a/src/iptvsimple/data/EpgEntry.h +++ b/src/iptvsimple/data/EpgEntry.h @@ -49,7 +49,7 @@ namespace iptvsimple void UpdateTo(kodi::addon::PVREPGTag& left, int iChannelUid, int timeShift, const std::vector& genres); bool UpdateFrom(const pugi::xml_node& programmeNode, const std::string& id, - int start, int end, int minShiftTime, int maxShiftTime); + int epgWindowsStart, int epgWindowsEnd, int minShiftTime, int maxShiftTime); private: bool SetEpgGenre(std::vector genreMappings); diff --git a/src/iptvsimple/data/MediaEntry.cpp b/src/iptvsimple/data/MediaEntry.cpp index 5661e85d..75134b91 100644 --- a/src/iptvsimple/data/MediaEntry.cpp +++ b/src/iptvsimple/data/MediaEntry.cpp @@ -55,6 +55,8 @@ void MediaEntry::Reset() m_providerUniqueId = PVR_PROVIDER_INVALID_UID; m_directory.clear(); m_sizeInBytes = 0; + + m_tvgShift = 0; } void MediaEntry::UpdateFrom(iptvsimple::data::Channel channel) @@ -66,6 +68,7 @@ void MediaEntry::UpdateFrom(iptvsimple::data::Channel channel) m_iconPath = channel.GetIconPath(); m_tvgId = channel.GetTvgId(); m_tvgName = channel.GetTvgId(); + m_tvgShift = channel.GetTvgShift(); m_startTime = std::time(nullptr); m_providerUniqueId = channel.GetProviderUniqueId(); @@ -73,7 +76,7 @@ void MediaEntry::UpdateFrom(iptvsimple::data::Channel channel) m_inputStreamName = channel.GetInputStreamName(); } -void MediaEntry::UpdateFrom(iptvsimple::data::EpgEntry epgEntry) +void MediaEntry::UpdateFrom(iptvsimple::data::EpgEntry epgEntry, const std::vector& genreMappings) { // All from Base Entry m_startTime = epgEntry.GetStartTime(); @@ -92,6 +95,19 @@ void MediaEntry::UpdateFrom(iptvsimple::data::EpgEntry epgEntry) if (!epgEntry.GetIconPath().empty()) m_iconPath = epgEntry.GetIconPath(); m_genreString = epgEntry.GetGenreString(); + if (SetEpgGenre(genreMappings)) + { + if (m_settings->UseEpgGenreTextWhenMapping()) + { + //Setting this value in sub type allows custom text to be displayed + //while still sending the type used for EPG colour + m_genreSubType = EPG_GENRE_USE_STRING; + } + } + else + { + m_genreType = EPG_GENRE_USE_STRING; + } m_cast = epgEntry.GetCast(); m_director = epgEntry.GetDirector(); m_writer = epgEntry.GetWriter(); @@ -105,6 +121,30 @@ void MediaEntry::UpdateFrom(iptvsimple::data::EpgEntry epgEntry) m_premiere = epgEntry.IsPremiere(); } +bool MediaEntry::SetEpgGenre(const std::vector genreMappings) +{ + if (genreMappings.empty()) + return false; + + for (const auto& genre : StringUtils::Split(m_genreString, EPG_STRING_TOKEN_SEPARATOR)) + { + if (genre.empty()) + continue; + + for (const auto& genreMapping : genreMappings) + { + if (StringUtils::EqualsNoCase(genreMapping.GetGenreString(), genre)) + { + m_genreType = genreMapping.GetGenreType(); + m_genreSubType = genreMapping.GetGenreSubType(); + return true; + } + } + } + + return false; +} + namespace { diff --git a/src/iptvsimple/data/MediaEntry.h b/src/iptvsimple/data/MediaEntry.h index 531eb9cc..f1c13eb5 100644 --- a/src/iptvsimple/data/MediaEntry.h +++ b/src/iptvsimple/data/MediaEntry.h @@ -70,6 +70,8 @@ namespace iptvsimple const std::string& GetM3UName() const { return m_m3uName; } const std::string& GetTvgId() const { return m_tvgId; } const std::string& GetTvgName() const { return m_tvgName; } + int GetTvgShift() const { return m_tvgShift; } + void SetTvgShift(int value) { m_tvgShift = value; } const std::map& GetProperties() const { return m_properties; } void SetProperties(std::map& value) { m_properties = value; } @@ -84,10 +86,12 @@ namespace iptvsimple void Reset(); void UpdateFrom(iptvsimple::data::Channel channel); - void UpdateFrom(iptvsimple::data::EpgEntry epgEntry); + void UpdateFrom(iptvsimple::data::EpgEntry epgEntry, const std::vector& genres); void UpdateTo(kodi::addon::PVRRecording& left, bool isInVirtualMediaEntryFolder, bool haveMediaTypes); private: + bool SetEpgGenre(std::vector genreMappings); + std::string m_mediaEntryId; bool m_radio = false; time_t m_startTime = 0; @@ -106,6 +110,7 @@ namespace iptvsimple std::string m_m3uName; std::string m_tvgId; std::string m_tvgName; + int m_tvgShift = 0; // Props std::map m_properties;