From 155f613e4a24c74ca6f581c6b1177bf7d1ae524c Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Mon, 26 Aug 2019 13:24:31 +0100 Subject: [PATCH 01/21] support multiple actor/director/writers elements in epg entry --- src/iptvsimple/data/EpgEntry.cpp | 8 ++++---- src/iptvsimple/utilities/XMLUtils.h | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/iptvsimple/data/EpgEntry.cpp b/src/iptvsimple/data/EpgEntry.cpp index d533f26b7..c4845083c 100644 --- a/src/iptvsimple/data/EpgEntry.cpp +++ b/src/iptvsimple/data/EpgEntry.cpp @@ -165,11 +165,11 @@ bool EpgEntry::UpdateFrom(rapidxml::xml_node<>* channelNode, const std::string& m_episodeName = GetNodeValue(channelNode, "sub-title"); xml_node<> *creditsNode = channelNode->first_node("credits"); - if (creditsNode != NULL) + if (creditsNode) { - m_cast = GetNodeValue(creditsNode, "actor"); - m_director = GetNodeValue(creditsNode, "director"); - m_writer = GetNodeValue(creditsNode, "writer"); + m_cast = GetJoinedNodeValues(creditsNode, "actor"); + m_director = GetJoinedNodeValues(creditsNode, "director"); + m_writer = GetJoinedNodeValues(creditsNode, "writer"); } xml_node<>* iconNode = channelNode->first_node("icon"); diff --git a/src/iptvsimple/utilities/XMLUtils.h b/src/iptvsimple/utilities/XMLUtils.h index 73fbe1a45..d91e70ceb 100644 --- a/src/iptvsimple/utilities/XMLUtils.h +++ b/src/iptvsimple/utilities/XMLUtils.h @@ -34,6 +34,25 @@ inline std::string GetNodeValue(const rapidxml::xml_node* rootNode, const ch return childNode->value(); } +template +inline std::string GetJoinedNodeValues(const rapidxml::xml_node* rootNode, const char* tag) +{ + std::string stringValue; + + rapidxml::xml_node* childNode = nullptr; + for (childNode = rootNode->first_node(tag); childNode; childNode = childNode->next_sibling(tag)) + { + if (childNode) + { + if (!stringValue.empty()) + stringValue += ","; + stringValue += childNode->value(); + } + } + + return stringValue; +} + template inline bool GetAttributeValue(const rapidxml::xml_node* node, const char* attributeName, std::string& stringValue) { From bbb00776cd843e3803c5fbfb54149e0a6c3fddf7 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Mon, 26 Aug 2019 13:24:49 +0100 Subject: [PATCH 02/21] Update OSX build script --- build-install-mac.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build-install-mac.sh b/build-install-mac.sh index 079aa89bd..6cc85127f 100755 --- a/build-install-mac.sh +++ b/build-install-mac.sh @@ -54,4 +54,6 @@ make XBMC_BUILD_ADDON_INSTALL_DIR=$(cd "$SCRIPT_DIR$1/addons/$ADDON_NAME" 2> /dev/null && pwd -P) rm -rf "$KODI_ADDONS_DIR/$ADDON_NAME" +echo "Removed previous addon build from: $KODI_ADDONS_DIR" cp -rf "$XBMC_BUILD_ADDON_INSTALL_DIR" "$KODI_ADDONS_DIR" +echo "Copied new addon build to: $KODI_ADDONS_DIR" From 7e9f415d4efc0af5fed9a14163172e64748c955a Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Mon, 26 Aug 2019 14:53:59 +0100 Subject: [PATCH 03/21] support firstAired and year in epg entry --- src/iptvsimple/data/EpgEntry.cpp | 17 +++++++++++++++-- src/iptvsimple/data/EpgEntry.h | 8 ++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/iptvsimple/data/EpgEntry.cpp b/src/iptvsimple/data/EpgEntry.cpp index c4845083c..237d0aabb 100644 --- a/src/iptvsimple/data/EpgEntry.cpp +++ b/src/iptvsimple/data/EpgEntry.cpp @@ -28,6 +28,7 @@ #include "rapidxml/rapidxml.hpp" #include +#include using namespace iptvsimple; using namespace iptvsimple::data; @@ -46,7 +47,7 @@ void EpgEntry::UpdateTo(EPG_TAG& left, int iChannelUid, int timeShift, std::vect left.strCast = m_cast.c_str(); left.strDirector = m_director.c_str(); left.strWriter = m_writer.c_str(); - left.iYear = 0; /* not supported */ + left.iYear = m_year; left.strIMDBNumber = nullptr; /* not supported */ left.strIconPath = m_iconPath.c_str(); if (SetEpgGenre(genres, m_genreString)) @@ -68,6 +69,7 @@ void EpgEntry::UpdateTo(EPG_TAG& left, int iChannelUid, int timeShift, std::vect left.iEpisodePartNumber = 0; /* not supported */ left.strEpisodeName = m_episodeName.c_str(); left.iFlags = EPG_TAG_FLAG_UNDEFINED; + left.firstAired = m_firstAired; } bool EpgEntry::SetEpgGenre(std::vector genres, const std::string& genreToFind) @@ -155,15 +157,26 @@ bool EpgEntry::UpdateFrom(rapidxml::xml_node<>* channelNode, const std::string& m_channelId = std::atoi(id.c_str()); m_genreType = 0; m_genreSubType = 0; - m_plotOutline= ""; + m_plotOutline.clear(); m_startTime = static_cast(tmpStart); m_endTime = static_cast(tmpEnd); + m_year = 0; + m_firstAired = 0; m_title = GetNodeValue(channelNode, "title"); m_plot = GetNodeValue(channelNode, "desc"); m_genreString = GetNodeValue(channelNode, "category"); m_episodeName = GetNodeValue(channelNode, "sub-title"); + const std::string dateString = GetNodeValue(channelNode, "date"); + if (!dateString.empty()) + { + if (std::regex_match(dateString, std::regex("^[1-9][0-9][0-9][0-9][0-9][1-9][0-9][1-9]"))) + m_firstAired = static_cast(ParseDateTime(dateString)); + + std::sscanf(dateString.c_str(), "%04d", &m_year); + } + xml_node<> *creditsNode = channelNode->first_node("credits"); if (creditsNode) { diff --git a/src/iptvsimple/data/EpgEntry.h b/src/iptvsimple/data/EpgEntry.h index d412be623..3539a2a2d 100644 --- a/src/iptvsimple/data/EpgEntry.h +++ b/src/iptvsimple/data/EpgEntry.h @@ -49,12 +49,18 @@ namespace iptvsimple int GetGenreSubType() const { return m_genreSubType; } void SetGenreSubType(int value) { m_genreSubType = value; } + int GetYear() const { return m_year; } + void SetYear(int value) { m_year = value; } + time_t GetStartTime() const { return m_startTime; } void SetStartTime(time_t value) { m_startTime = value; } time_t GetEndTime() const { return m_endTime; } void SetEndTime(time_t value) { m_endTime = value; } + time_t GetFirstAired() const { return m_firstAired; } + void SetFirstAired(time_t value) { m_firstAired = value; } + const std::string& GetTitle() const { return m_title; } void SetTitle(const std::string& value) { m_title = value; } @@ -93,8 +99,10 @@ namespace iptvsimple int m_channelId; int m_genreType; int m_genreSubType; + int m_year; time_t m_startTime; time_t m_endTime; + time_t m_firstAired; std::string m_title; std::string m_episodeName; std::string m_plotOutline; From 4e6b0622a155ee5fcb01d5528a0f2dd797dc7b33 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Mon, 26 Aug 2019 17:11:31 +0100 Subject: [PATCH 04/21] support star rating in epg entry --- src/iptvsimple/data/EpgEntry.cpp | 31 ++++++++++++++++++++++++++++--- src/iptvsimple/data/EpgEntry.h | 6 ++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/iptvsimple/data/EpgEntry.cpp b/src/iptvsimple/data/EpgEntry.cpp index 237d0aabb..fe75c941e 100644 --- a/src/iptvsimple/data/EpgEntry.cpp +++ b/src/iptvsimple/data/EpgEntry.cpp @@ -27,6 +27,7 @@ #include "p8-platform/util/StringUtils.h" #include "rapidxml/rapidxml.hpp" +#include #include #include @@ -63,7 +64,7 @@ void EpgEntry::UpdateTo(EPG_TAG& left, int iChannelUid, int timeShift, std::vect left.strGenreDescription = m_genreString.c_str(); } left.iParentalRating = 0; /* not supported */ - left.iStarRating = 0; /* not supported */ + left.iStarRating = m_starRating; left.iSeriesNumber = 0; /* not supported */ left.iEpisodeNumber = 0; /* not supported */ left.iEpisodePartNumber = 0; /* not supported */ @@ -129,7 +130,7 @@ long long ParseDateTime(const std::string& strDate) int offset_hours = 0; int offset_minutes = 0; - sscanf(strDate.c_str(), "%04d%02d%02d%02d%02d%02d %c%02d%02d", &year, &mon, &mday, &hour, &min, &sec, &offset_sign, &offset_hours, &offset_minutes); + std::sscanf(strDate.c_str(), "%04d%02d%02d%02d%02d%02d %c%02d%02d", &year, &mon, &mday, &hour, &min, &sec, &offset_sign, &offset_hours, &offset_minutes); long offset_of_date = (offset_hours * 60 + offset_minutes) * 60; if (offset_sign == '-') @@ -138,6 +139,25 @@ long long ParseDateTime(const std::string& strDate) return GetUTCTime(year, mon, mday, hour, min, sec) - offset_of_date; } +int ParseStarRating(const std::string& starRatingString) +{ + float starRating = 0; + float starRatingScale; + + int ret = std::sscanf(starRatingString.c_str(), "%f/ %f", &starRating, &starRatingScale); + + if (ret == 2 && starRatingScale != STAR_RATING_SCALE && starRatingScale != 0.0f) + { + starRating /= starRatingScale; + starRating *= 10; + } + + if (ret >= 1 && starRating > STAR_RATING_SCALE) + starRating = STAR_RATING_SCALE; + + return static_cast(std::round(starRating)); +} + } // unnamed namespace bool EpgEntry::UpdateFrom(rapidxml::xml_node<>* channelNode, const std::string& id, int broadcastId, @@ -162,7 +182,8 @@ bool EpgEntry::UpdateFrom(rapidxml::xml_node<>* channelNode, const std::string& m_endTime = static_cast(tmpEnd); m_year = 0; m_firstAired = 0; - + m_starRating = 0; + m_title = GetNodeValue(channelNode, "title"); m_plot = GetNodeValue(channelNode, "desc"); m_genreString = GetNodeValue(channelNode, "category"); @@ -177,6 +198,10 @@ bool EpgEntry::UpdateFrom(rapidxml::xml_node<>* channelNode, const std::string& std::sscanf(dateString.c_str(), "%04d", &m_year); } + xml_node<>* starRatingNode = channelNode->first_node("star-rating"); + if (starRatingNode) + m_starRating = ParseStarRating(GetNodeValue(starRatingNode, "value")); + xml_node<> *creditsNode = channelNode->first_node("credits"); if (creditsNode) { diff --git a/src/iptvsimple/data/EpgEntry.h b/src/iptvsimple/data/EpgEntry.h index 3539a2a2d..cc852b34c 100644 --- a/src/iptvsimple/data/EpgEntry.h +++ b/src/iptvsimple/data/EpgEntry.h @@ -34,6 +34,8 @@ namespace iptvsimple { namespace data { + static const float STAR_RATING_SCALE = 10.0f; + class EpgEntry { public: @@ -52,6 +54,9 @@ namespace iptvsimple int GetYear() const { return m_year; } void SetYear(int value) { m_year = value; } + int GetStarRating() const { return m_starRating; } + void SetStarRating(int value) { m_starRating = value; } + time_t GetStartTime() const { return m_startTime; } void SetStartTime(time_t value) { m_startTime = value; } @@ -100,6 +105,7 @@ namespace iptvsimple int m_genreType; int m_genreSubType; int m_year; + int m_starRating; time_t m_startTime; time_t m_endTime; time_t m_firstAired; From a47d90688b0739b33c3683abe818c3ea15619bd4 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Tue, 27 Aug 2019 13:29:51 +0100 Subject: [PATCH 05/21] Update readme for supported M3U and XMLTV formats and genres --- README.md | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/README.md b/README.md index c99e04f12..d8a3cf862 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ IPTV Live TV and Radio PVR client addon for [Kodi](https://kodi.tv) +For a listing of the supported M3U and XMLTV elements see the appendix [here](#supported-m3u-and-xmltv-elements) + ## Build instructions ### Linux @@ -66,6 +68,8 @@ General settings required for the addon to function. ### EPG Settings Settings related to the EPG. +My default the addon will read the first `` element of a `programme` and use this as the genre string. It is also possible to supply a mapping file to convert the genre string to a genre ID, allowing colour coding of the EPG. Please see: [Using a mapping file for Genres](#using-a-mapping-file-for-genres) in the Appendix for details on how to set this up. + * **Location**: Select where to find the XMLTV resource. The options are: - `Local path` - A path to an XMLTV file whether it be on the device or the local network. - `Remote path` - A URL specifying the location of the XMLTV file. @@ -90,6 +94,138 @@ Settings realted to Channel Logos. ## Appendix +### Supported M3U and XMLTV elements + +### Using a mapping file for Genres + +Users can create there own genre mapping files to map their genre strings to genre IDs. This allows the EPG UI to be colour coded per genre. + +Kodi uses the following standard for it's genre IDs: https://www.etsi.org/deliver/etsi_en/300400_300499/300468/01.11.01_60/en_300468v011101p.pdf + +The file created must be called `genres.xml` and be located here: `userdata/addon_data/pvr.iptvsimple`. + +Here is an exmaple of the file: + +``` + + My Streams Genres Mappings + General Movie/Drama + TV Show + Game Show + Show/Game Show + Show/Game Show + +``` + +- ``: There should be a single `` element. The value should denote the purpose of this genre mapping file. +- ``: There can be many `genre` elements. Both the `type` and `subtype` attributes can contain a value from 0 to 15 (would be 0x0 to 0xF if in DVB hex). `subtype` is optional. The value of the `` element is what is used to map from in order to get the genre IDs. Many mapping values are allowed to map to the same IDs. +Note: Once mapped to genre IDs the text displayed will be the DVB standard text and not the genre string text supplied in the XML. + +#### M3U format elemnents: + +``` +#EXTM3U tvg-shift="-4.5" +#EXTINF:0 tvg-id="channel-x" tvg-name="Channel_X" group-title="Entertainment" tvg-chno="10" tvg-logo="http://path-to-icons/channel-x.png" radio="true" tvg-shift="-3.5",Channel X +#EXTVLCOPT:program=745 +#KODIPROP:key=val +http://path-to-stream/live/channel-x.ts +#EXTINF:0 tvg-id="channel-x" tvg-name="Channel-X-HD" group-title="Entertainment;HD Channels",Channel X HD +http://path-to-stream/live/channel-x-hd.ts +#EXTINF:0 tvg-id="channel-y" tvg-name="Channel_Y",Channel Y +#EXTGRP:Entertainment +http://path-to-stream/live/channel-y.ts +#EXTINF:0,Channel Z +http://path-to-stream/live/channel-z.ts +``` + +Note: The minimum required for a channel/stream is an `#EXTINF` line with a channel name and the `URL` line. E.g. a minimal version of the exmaple file above would be: + +``` +#EXTM3U +#EXTINF:0,Channel X +http://path-to-stream/live/channel-x.ts +#EXTINF:0,Channel X HD +http://path-to-stream/live/channel-x-hd.ts +#EXTINF:0,Channel Y +http://path-to-stream/live/channel-y.ts +#EXTINF:0,Channel Z +http://path-to-stream/live/channel-z.ts +``` + +- `#EXTM3U`: Marker for the start of an M3U file. Has an optional `tvg-shift` value that will be used for all channels if a `tvg-shift` value is not supplied per channel. +- `#EXTINF`: Contains a set of values, ending with a comma followed by the `channel name`. + - `tvg-id`: A unique identifier for this channel used to map to the EPG XMLTV data. + - `tvg-name`: A name for this channel in the EPG XMLTV data. + - `group-title`: A semi-colon separted list of channel groups that this channel belongs to. + - `tvg-chno`: The number to be used for this channel. + - `tvg-logo`: A URL pointing to the logo for this channel. + - `radio`: If the value matches "true" (case insensitive) this is a radio channel. + - `tvg-shift`: Channel specific shift value in hours. +- `#EXTGRP`: A semi-colon separted list of channel groups that this channel belongs to. +- `#KODIPROP`: A single property in the format `key=value` that can be passed to Kodi. Multiple can be passed. +- `#EXTVLCOPT`: A single property in the format `key=value` that can be passed to Kodi. Multiple can be passed. +- `#EXT-X-PLAYLIST-TYPE`: If this element is present with a value of `VOD` (Video on Demand) the stream is marked as not being live. +- `URL`: The final line in each channel stanza is the URL used for the stream. Appending `|User-Agent=` will change the user agent. + +When processing an XMLTV file the addon will attempt to find a channel loaded from the M3U that matches the EPG channel. It will cycle through the full set of M3U channels checking for one condition on each pass. The first channel found to match is the channel chosen for this EPG channel data. + + - *1st pass*: Does the`id` attribute of the `` element from the XMLTV match the `tvg-id` from the M3U channel. If yes we have a match, don't continue. + - *Before the second pass*: Was a value provided, if not skip this channels EPG data. + - *2nd pass*: Does the as it is or with spaces replaced with '_''s match `tvg-name` from the M3U channel. If yes we have a match, don't continue. + - *3rd pass*: Does the match the M3U `channel name`. If yes we have a match, phew, eventually found a match. + +#### XMLTV format elemnents: + +General information on the XMLTV format can be found [here](http://wiki.xmltv.org/index.php/XMLTVFormat). There is also the [DTD](https://github.com/XMLTV/xmltv/blob/master/xmltv.dtd). + +**Channel elements** +``` + + Channel X + Channel X HD + + +``` + +- When matching against M3U channels the `id` attribute will be used first, followed by each `display-name`. +- If multiple `icon` elements are provided only the first will be used. + +**Programme elements** +``` + + My Show + Description of My Show + Drama + Mystery + Episode name for My Show + 20080711 + + 6/10 + + 0.1.0/1 + S01E02 + + Director One + Writer One + Actor One + + + +``` +The `programme` element supports the attributes `start`/`stop` in the format `YYYmmddHHMMSS +/-HHMM` and the attribute `channel` which needs to match the `channel` element's attribute `id`. + +- `title`: The title of the prgramme. +- `desc`: A descption of the programme. +- `category`: If multiple elements are provided only the first will be used to populate the genre. +- `sub-title`: Used to populate episode name. +- `date`: Used to populate year and first aired date. +- `star-rating`: If multiple elements are provided only the first will be used. The value will be converted to a scale of 10 if required. +- `episode-num`: The`xmltv_ns`system will be preferred over `onscreen` and the first successfully parsed element will be used. + - For `episode-num` elements using the `xmltv_ns` system at least season and episode must be supplied, i.e. `0.1` (season 1, episode 2). If the 3rd element episode part number is supplied it must contain both the part number and the total number of parts, i.e. `0.1.0/2` (season 1, episode 2, part 1 of 2). + - For `episode-num` elements using the `onscreen` system only the `S01E02` format is supported. +- `credits`: Only director, writer and actor are supported (multiple of each can be supplied). +- `icon`: If multiple elements are provided only the first will be used. + ### Manual Steps to rebuild the addon on MacOSX The following steps can be followed manually instead of using the `build-install-mac.sh` in the root of the addon repo after the [initial addon build](#build-tools-and-initial-addon-build) has been completed. From 7b1031dae4026564af7200f7aa8ffc562a74383b Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Tue, 27 Aug 2019 14:00:11 +0100 Subject: [PATCH 06/21] support episode-num for both xmltv_ns and onscreen systems in epg entry --- src/iptvsimple/data/EpgEntry.cpp | 97 ++++++++++++++++++++++++++++++-- src/iptvsimple/data/EpgEntry.h | 15 +++++ 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/src/iptvsimple/data/EpgEntry.cpp b/src/iptvsimple/data/EpgEntry.cpp index fe75c941e..41684beb6 100644 --- a/src/iptvsimple/data/EpgEntry.cpp +++ b/src/iptvsimple/data/EpgEntry.cpp @@ -65,9 +65,9 @@ void EpgEntry::UpdateTo(EPG_TAG& left, int iChannelUid, int timeShift, std::vect } left.iParentalRating = 0; /* not supported */ left.iStarRating = m_starRating; - left.iSeriesNumber = 0; /* not supported */ - left.iEpisodeNumber = 0; /* not supported */ - left.iEpisodePartNumber = 0; /* not supported */ + left.iSeriesNumber = m_seasonNumber; + left.iEpisodeNumber = m_episodeNumber; + left.iEpisodePartNumber = m_episodePartNumber; left.strEpisodeName = m_episodeName.c_str(); left.iFlags = EPG_TAG_FLAG_UNDEFINED; left.firstAired = m_firstAired; @@ -183,7 +183,10 @@ bool EpgEntry::UpdateFrom(rapidxml::xml_node<>* channelNode, const std::string& m_year = 0; m_firstAired = 0; m_starRating = 0; - + m_episodeNumber = 0; + m_episodePartNumber = 0; + m_seasonNumber = 0; + m_title = GetNodeValue(channelNode, "title"); m_plot = GetNodeValue(channelNode, "desc"); m_genreString = GetNodeValue(channelNode, "category"); @@ -202,6 +205,17 @@ bool EpgEntry::UpdateFrom(rapidxml::xml_node<>* channelNode, const std::string& if (starRatingNode) m_starRating = ParseStarRating(GetNodeValue(starRatingNode, "value")); + std::vector> episodeNumbersList; + for (xml_node<>* episodeNumNode = channelNode->first_node("episode-num"); episodeNumNode; episodeNumNode = episodeNumNode->next_sibling("episode-num")) + { + std::string episodeNumberSystem; + if (GetAttributeValue(episodeNumNode, "system", episodeNumberSystem)) + episodeNumbersList.push_back({episodeNumberSystem, episodeNumNode->value()}); + } + + if (!episodeNumbersList.empty()) + ParseEpisodeNumberInfo(episodeNumbersList); + xml_node<> *creditsNode = channelNode->first_node("credits"); if (creditsNode) { @@ -218,4 +232,79 @@ bool EpgEntry::UpdateFrom(rapidxml::xml_node<>* channelNode, const std::string& m_iconPath = iconPath; return true; +} + +bool EpgEntry::ParseEpisodeNumberInfo(std::vector>& episodeNumbersList) +{ + //First check xmltv_ns + for (const auto& pair : episodeNumbersList) + { + if (pair.first == "xmltv_ns" && ParseXmltvNsEpisodeNumberInfo(pair.second)) + return true; + } + + //If not found try onscreen + for (const auto& pair : episodeNumbersList) + { + if (pair.first == "onscreen" && ParseOnScreenEpisodeNumberInfo(pair.second)) + return true; + } + + return false; +} + +bool EpgEntry::ParseXmltvNsEpisodeNumberInfo(const std::string& episodeNumberString) +{ + size_t found = episodeNumberString.find("."); + if (found != std::string::npos) + { + const std::string seasonString = episodeNumberString.substr(0, found); + std::string episodeString = episodeNumberString.substr(found + 1); + std::string episodePartString; + + found = episodeString.find("."); + if (found != std::string::npos) + { + episodePartString = episodeString.substr(found + 1); + episodeString = episodeString.substr(0, found); + } + + if (std::sscanf(seasonString.c_str(), "%d", &m_seasonNumber) == 1) + m_seasonNumber++; + + if (std::sscanf(episodeString.c_str(), "%d", &m_episodeNumber) == 1) + m_episodeNumber++; + + if (!episodePartString.empty()) + { + int totalNumberOfParts; + int numElementsParsed = std::sscanf(episodePartString.c_str(), "%d/%d", &m_episodePartNumber, &totalNumberOfParts); + + if (numElementsParsed == 2) + m_episodePartNumber++; + else if (numElementsParsed == 1) + m_episodePartNumber = 0; + } + } + + return m_episodeNumber; +} + +bool EpgEntry::ParseOnScreenEpisodeNumberInfo(const std::string& episodeNumberString) +{ + const std::string text = std::regex_replace(episodeNumberString, std::regex("[ \\txX_\\.]"), ""); + + std::smatch match; + if (std::regex_match(text, match, std::regex("^[sS]([0-9][0-9]*)[eE][pP]?([0-9][0-9]*)$"))) + { + if (match.size() == 3) + { + m_seasonNumber = std::atoi(match[1].str().c_str()); + m_episodeNumber = std::atoi(match[2].str().c_str()); + + return true; + } + } + + return false; } \ No newline at end of file diff --git a/src/iptvsimple/data/EpgEntry.h b/src/iptvsimple/data/EpgEntry.h index cc852b34c..de103eea4 100644 --- a/src/iptvsimple/data/EpgEntry.h +++ b/src/iptvsimple/data/EpgEntry.h @@ -57,6 +57,15 @@ namespace iptvsimple int GetStarRating() const { return m_starRating; } void SetStarRating(int value) { m_starRating = value; } + int GetEpisodeNumber() const { return m_episodeNumber; } + void SetEpisodeNumber(int value) { m_episodeNumber = value; } + + int GetEpisodePartNumber() const { return m_episodePartNumber; } + void SetEpisodePartNumber(int value) { m_episodePartNumber = value; } + + int GetSeasonNumber() const { return m_seasonNumber; } + void SetSeasonNumber(int value) { m_seasonNumber = value; } + time_t GetStartTime() const { return m_startTime; } void SetStartTime(time_t value) { m_startTime = value; } @@ -99,6 +108,9 @@ namespace iptvsimple private: bool SetEpgGenre(std::vector genres, const std::string& genreToFind); + bool ParseEpisodeNumberInfo(std::vector>& episodeNumbersList); + bool ParseXmltvNsEpisodeNumberInfo(const std::string& episodeNumberString); + bool ParseOnScreenEpisodeNumberInfo(const std::string& episodeNumberString); int m_broadcastId; int m_channelId; @@ -106,6 +118,9 @@ namespace iptvsimple int m_genreSubType; int m_year; int m_starRating; + int m_episodeNumber = 0; + int m_episodePartNumber = 0; + int m_seasonNumber = 0; time_t m_startTime; time_t m_endTime; time_t m_firstAired; From 5fe3bd5143b4e1c1cb41523a3097b1fcbd5cb2fc Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Tue, 27 Aug 2019 15:21:22 +0100 Subject: [PATCH 07/21] refactoring --- src/iptvsimple/Epg.cpp | 4 ++-- src/iptvsimple/PlaylistLoader.cpp | 2 +- src/iptvsimple/data/EpgEntry.cpp | 2 +- src/iptvsimple/utilities/XMLUtils.h | 3 +-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/iptvsimple/Epg.cpp b/src/iptvsimple/Epg.cpp index 7a92a7458..dabacbff5 100644 --- a/src/iptvsimple/Epg.cpp +++ b/src/iptvsimple/Epg.cpp @@ -241,7 +241,7 @@ void Epg::LoadEpgEntries(xml_node<>* rootElement, int start, int end) if (!GetAttributeValue(channelNode, "channel", id)) continue; - if (!channelEpg || StringUtils::CompareNoCase(channelEpg->GetId(), id) != 0) + if (!channelEpg || !StringUtils::EqualsNoCase(channelEpg->GetId(), id)) { if (!(channelEpg = FindEpgForChannel(id))) continue; @@ -324,7 +324,7 @@ ChannelEpg* Epg::FindEpgForChannel(const std::string& id) { for (auto& myChannelEpg : m_channelEpgs) { - if (StringUtils::CompareNoCase(myChannelEpg.GetId(), id) == 0) + if (StringUtils::EqualsNoCase(myChannelEpg.GetId(), id)) return &myChannelEpg; } diff --git a/src/iptvsimple/PlaylistLoader.cpp b/src/iptvsimple/PlaylistLoader.cpp index 34588f3a0..d7068fe36 100644 --- a/src/iptvsimple/PlaylistLoader.cpp +++ b/src/iptvsimple/PlaylistLoader.cpp @@ -195,7 +195,7 @@ std::string PlaylistLoader::ParseIntoChannel(const std::string& line, Channel& c double tvgShiftDecimal = std::atof(strTvgShift.c_str()); - bool isRadio = !StringUtils::CompareNoCase(strRadio, "true"); + bool isRadio = StringUtils::EqualsNoCase(strRadio, "true"); channel.SetTvgId(strTvgId); channel.SetTvgName(XBMC->UnknownToUTF8(strTvgName.c_str())); channel.SetTvgLogo(XBMC->UnknownToUTF8(strTvgLogo.c_str())); diff --git a/src/iptvsimple/data/EpgEntry.cpp b/src/iptvsimple/data/EpgEntry.cpp index 41684beb6..a07a19b5d 100644 --- a/src/iptvsimple/data/EpgEntry.cpp +++ b/src/iptvsimple/data/EpgEntry.cpp @@ -80,7 +80,7 @@ bool EpgEntry::SetEpgGenre(std::vector genres, const std::string& genr for (const auto& myGenre : genres) { - if (StringUtils::CompareNoCase(myGenre.GetGenreString(), genreToFind) == 0) + if (StringUtils::EqualsNoCase(myGenre.GetGenreString(), genreToFind)) { m_genreType = myGenre.GetGenreType(); m_genreSubType = myGenre.GetGenreSubType(); diff --git a/src/iptvsimple/utilities/XMLUtils.h b/src/iptvsimple/utilities/XMLUtils.h index d91e70ceb..9c4680351 100644 --- a/src/iptvsimple/utilities/XMLUtils.h +++ b/src/iptvsimple/utilities/XMLUtils.h @@ -58,9 +58,8 @@ inline bool GetAttributeValue(const rapidxml::xml_node* node, const char* at { rapidxml::xml_attribute* attribute = node->first_attribute(attributeName); if (!attribute) - { return false; - } + stringValue = attribute->value(); return true; } From 939cca1c28eba5f15a4f826a125bbe374aaae086 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Tue, 27 Aug 2019 17:27:03 +0100 Subject: [PATCH 08/21] Some providers incorrectly use tvg-ID instead of tvg-id --- src/iptvsimple/PlaylistLoader.cpp | 3 +++ src/iptvsimple/PlaylistLoader.h | 1 + 2 files changed, 4 insertions(+) diff --git a/src/iptvsimple/PlaylistLoader.cpp b/src/iptvsimple/PlaylistLoader.cpp index d7068fe36..45614e4c3 100644 --- a/src/iptvsimple/PlaylistLoader.cpp +++ b/src/iptvsimple/PlaylistLoader.cpp @@ -180,6 +180,9 @@ std::string PlaylistLoader::ParseIntoChannel(const std::string& line, Channel& c std::string strRadio = ReadMarkerValue(infoLine, RADIO_MARKER); std::string strTvgShift = ReadMarkerValue(infoLine, TVG_INFO_SHIFT_MARKER); + if (strTvgId.empty()) + strTvgId = ReadMarkerValue(infoLine, TVG_INFO_ID_MARKER_UC); + if (strTvgId.empty()) { char buff[255]; diff --git a/src/iptvsimple/PlaylistLoader.h b/src/iptvsimple/PlaylistLoader.h index 7e3c825e7..133d8ee7a 100644 --- a/src/iptvsimple/PlaylistLoader.h +++ b/src/iptvsimple/PlaylistLoader.h @@ -34,6 +34,7 @@ namespace iptvsimple static const std::string M3U_INFO_MARKER = "#EXTINF"; static const std::string M3U_GROUP_MARKER = "#EXTGRP:"; static const std::string TVG_INFO_ID_MARKER = "tvg-id="; + static const std::string TVG_INFO_ID_MARKER_UC = "tvg-ID="; //some provider incorrectly use an uppercase ID static const std::string TVG_INFO_NAME_MARKER = "tvg-name="; static const std::string TVG_INFO_LOGO_MARKER = "tvg-logo="; static const std::string TVG_INFO_SHIFT_MARKER = "tvg-shift="; From 68d1934ac8a354dc1012cfde294a92960440d9f6 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Tue, 27 Aug 2019 18:11:05 +0100 Subject: [PATCH 09/21] Support full timeshift range of -12 to +14 hours --- README.md | 2 +- .../resources/language/resource.language.en_gb/strings.po | 2 +- pvr.iptvsimple/resources/settings.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d8a3cf862..e186141fc 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ My default the addon will read the first `` element of a `programme` a * **XMLTV path**: If location is `Local Path` this setting should contain a valid path. * **XMLTV URL**: If location is `Remote Path` this setting should contain a valid URL. * **Cache XMLTV at local storage**: If location is `Remote path` select whether or not the the XMLTV file should be cached locally. -* **EPG time shift**: Adjust the EPG times by this value in minutes, range is from -720 mins to +720 mins (+/- 12 hours). +* **EPG time shift**: Adjust the EPG times by this value in minutes, range is from -720 mins to +840 mins (- 12 hours to +14 hours). * **Apply time shift to all channels**: Whether or not to override the time shift for all channels with `EPG time shift`. If not enabled `EPG time shift` plus the individual time shift per channel (if available) will be used. ### Channel Logos diff --git a/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po b/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po index 583940706..09d6c5da0 100644 --- a/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po +++ b/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po @@ -214,7 +214,7 @@ msgstr "" #help: EPG Settings - epgTimeShift msgctxt "#30625" -msgid "Adjust the EPG times by this value in minutes, range is from -720 mins to +720 mins (+/- 12 hours)." +msgid "Adjust the EPG times by this value in minutes, range is from -720 mins to +840 mins (- 12 hours to +14 hours)." msgstr "" #help: EPG Settings - epgTSOverside diff --git a/pvr.iptvsimple/resources/settings.xml b/pvr.iptvsimple/resources/settings.xml index ca628dbcb..bed33452b 100644 --- a/pvr.iptvsimple/resources/settings.xml +++ b/pvr.iptvsimple/resources/settings.xml @@ -110,7 +110,7 @@ -720 30 - 720 + 840 14044 From 34c086cec0e5fbe736c0fb0ba3e69d286abd346b Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Tue, 27 Aug 2019 19:51:03 +0100 Subject: [PATCH 10/21] URLEncode and append .png ext for remote logos built from channel name --- CMakeLists.txt | 4 ++- src/iptvsimple/PlaylistLoader.cpp | 10 ++++++ src/iptvsimple/utilities/WebUtils.cpp | 52 +++++++++++++++++++++++++++ src/iptvsimple/utilities/WebUtils.h | 36 +++++++++++++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/iptvsimple/utilities/WebUtils.cpp create mode 100644 src/iptvsimple/utilities/WebUtils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9de063241..008ef44e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,8 @@ set(IPTV_SOURCES src/client.cpp src/iptvsimple/data/EpgEntry.cpp src/iptvsimple/data/EpgGenre.cpp src/iptvsimple/utilities/FileUtils.cpp - src/iptvsimple/utilities/Logger.cpp) + src/iptvsimple/utilities/Logger.cpp + src/iptvsimple/utilities/WebUtils.cpp) set(IPTV_HEADERS src/client.h src/PVRIptvData.h @@ -47,6 +48,7 @@ set(IPTV_HEADERS src/client.h src/iptvsimple/data/EpgGenre.h src/iptvsimple/utilities/FileUtils.h src/iptvsimple/utilities/Logger.h + src/iptvsimple/utilities/WebUtils.h src/iptvsimple/utilities/XMLUtils.h) addon_version(pvr.iptvsimple IPTV) diff --git a/src/iptvsimple/PlaylistLoader.cpp b/src/iptvsimple/PlaylistLoader.cpp index 45614e4c3..456799c85 100644 --- a/src/iptvsimple/PlaylistLoader.cpp +++ b/src/iptvsimple/PlaylistLoader.cpp @@ -26,6 +26,7 @@ #include "../client.h" #include "utilities/FileUtils.h" #include "utilities/Logger.h" +#include "utilities/WebUtils.h" #include "p8-platform/util/StringUtils.h" @@ -190,8 +191,12 @@ std::string PlaylistLoader::ParseIntoChannel(const std::string& line, Channel& c strTvgId.append(buff); } + bool logoSetFromChannelName = false; if (strTvgLogo.empty()) + { strTvgLogo = channelName; + logoSetFromChannelName = true; + } if (!strChnlNo.empty()) channel.SetChannelNumber(std::atoi(strChnlNo.c_str())); @@ -205,6 +210,11 @@ std::string PlaylistLoader::ParseIntoChannel(const std::string& line, Channel& c channel.SetTvgShift(static_cast(tvgShiftDecimal * 3600.0)); channel.SetRadio(isRadio); + // urlencode channel logo when set from channel name and source is Remote Path + // append extension as channel name wouldn't have it + if (Settings::GetInstance().GetLogoPathType() == PathType::REMOTE_PATH && logoSetFromChannelName) + channel.SetTvgLogo(WebUtils::UrlEncode(channel.GetTvgLogo()) + CHANNEL_LOGO_EXTENSION); + if (strTvgShift.empty()) channel.SetTvgShift(epgTimeShift); diff --git a/src/iptvsimple/utilities/WebUtils.cpp b/src/iptvsimple/utilities/WebUtils.cpp new file mode 100644 index 000000000..4225b15c5 --- /dev/null +++ b/src/iptvsimple/utilities/WebUtils.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2019 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "WebUtils.h" + +#include +#include +#include + +using namespace iptvsimple; +using namespace iptvsimple::utilities; + +// http://stackoverflow.com/a/17708801 +const std::string WebUtils::UrlEncode(const std::string& value) +{ + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) + { + // Keep alphanumeric and other accepted characters intact + if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') + { + escaped << c; + continue; + } + + // Any other characters are percent-encoded + escaped << '%' << std::setw(2) << int(static_cast(c)); + } + + return escaped.str(); +} \ No newline at end of file diff --git a/src/iptvsimple/utilities/WebUtils.h b/src/iptvsimple/utilities/WebUtils.h new file mode 100644 index 000000000..aa1d97d44 --- /dev/null +++ b/src/iptvsimple/utilities/WebUtils.h @@ -0,0 +1,36 @@ +#pragma once + +/* + * Copyright (C) 2005-2019 Team Kodi + * http://kodi.tv + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include + +namespace iptvsimple +{ + namespace utilities + { + class WebUtils + { + public: + static const std::string UrlEncode(const std::string& value); + }; + } // namespace utilities +} // namespace iptvsimple From 8d9b73a52aa8fd91cea3e89c834e4fed2003b8da Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Wed, 28 Aug 2019 15:16:51 +0100 Subject: [PATCH 11/21] Support multiple display-names and case insensitive tvg-id is always first, next tvg-name and then channel name find order --- src/iptvsimple/Channels.cpp | 24 ++++++++++++++++-------- src/iptvsimple/Channels.h | 2 +- src/iptvsimple/Epg.cpp | 25 +++++++++++++++++++------ src/iptvsimple/data/ChannelEpg.cpp | 22 ++++++++++++++-------- src/iptvsimple/data/ChannelEpg.h | 6 +++--- 5 files changed, 53 insertions(+), 26 deletions(-) diff --git a/src/iptvsimple/Channels.cpp b/src/iptvsimple/Channels.cpp index 0c9ad0044..9c04d8245 100644 --- a/src/iptvsimple/Channels.cpp +++ b/src/iptvsimple/Channels.cpp @@ -28,6 +28,8 @@ #include "utilities/FileUtils.h" #include "utilities/Logger.h" +#include "p8-platform/util/StringUtils.h" + #include using namespace iptvsimple; @@ -109,22 +111,28 @@ Channel* Channels::GetChannel(int uniqueId) return nullptr; } -const Channel* Channels::FindChannel(const std::string& id, const std::string& name) const +const Channel* Channels::FindChannel(const std::string& id, const std::string& displayName) const { - const std::string tvgName = std::regex_replace(name, std::regex(" "), "_"); - for (const auto& myChannel : m_channels) { - if (myChannel.GetTvgId() == id) + if (StringUtils::EqualsNoCase(myChannel.GetTvgId(), id)) return &myChannel; + } - if (tvgName.empty()) - continue; + if (displayName.empty()) + return nullptr; - if (myChannel.GetTvgName() == tvgName) + const std::string convertedDisplayName = std::regex_replace(displayName, std::regex(" "), "_"); + for (const auto& myChannel : m_channels) + { + if (StringUtils::EqualsNoCase(myChannel.GetTvgName(), convertedDisplayName) || + StringUtils::EqualsNoCase(myChannel.GetTvgName(), displayName)) return &myChannel; + } - if (myChannel.GetChannelName() == name) + for (const auto& myChannel : m_channels) + { + if (StringUtils::EqualsNoCase(myChannel.GetChannelName(), displayName)) return &myChannel; } diff --git a/src/iptvsimple/Channels.h b/src/iptvsimple/Channels.h index fb8dd5d9e..4e068552e 100644 --- a/src/iptvsimple/Channels.h +++ b/src/iptvsimple/Channels.h @@ -48,7 +48,7 @@ namespace iptvsimple void AddChannel(iptvsimple::data::Channel& channel, std::vector& groupIdList, iptvsimple::ChannelGroups& channelGroups); iptvsimple::data::Channel* GetChannel(int uniqueId); - const iptvsimple::data::Channel* FindChannel(const std::string& id, const std::string& name) const; + const iptvsimple::data::Channel* FindChannel(const std::string& id, const std::string& displayName) const; const std::vector& GetChannelsList() const { return m_channels; } void Clear(); void ApplyChannelLogos(); diff --git a/src/iptvsimple/Epg.cpp b/src/iptvsimple/Epg.cpp index dabacbff5..12606f780 100644 --- a/src/iptvsimple/Epg.cpp +++ b/src/iptvsimple/Epg.cpp @@ -335,15 +335,28 @@ ChannelEpg* Epg::FindEpgForChannel(const Channel& channel) { for (auto& myChannelEpg : m_channelEpgs) { - if (myChannelEpg.GetId() == channel.GetTvgId()) + if (StringUtils::EqualsNoCase(myChannelEpg.GetId(), channel.GetTvgId())) return &myChannelEpg; + } - const std::string name = std::regex_replace(myChannelEpg.GetName(), std::regex(" "), "_"); - if (name == channel.GetTvgName() || myChannelEpg.GetName() == channel.GetTvgName()) - return &myChannelEpg; + for (auto& myChannelEpg : m_channelEpgs) + { + for (const std::string& displayName : myChannelEpg.GetNames()) + { + const std::string convertedDisplayName = std::regex_replace(displayName, std::regex(" "), "_"); + if (StringUtils::EqualsNoCase(convertedDisplayName, channel.GetTvgName()) || + StringUtils::EqualsNoCase(displayName, channel.GetTvgName())) + return &myChannelEpg; + } + } - if (myChannelEpg.GetName() == channel.GetChannelName()) - return &myChannelEpg; + for (auto& myChannelEpg : m_channelEpgs) + { + for (const std::string& displayName : myChannelEpg.GetNames()) + { + if (StringUtils::EqualsNoCase(displayName, channel.GetChannelName())) + return &myChannelEpg; + } } return nullptr; diff --git a/src/iptvsimple/data/ChannelEpg.cpp b/src/iptvsimple/data/ChannelEpg.cpp index f5e52a150..9a682e9c9 100644 --- a/src/iptvsimple/data/ChannelEpg.cpp +++ b/src/iptvsimple/data/ChannelEpg.cpp @@ -30,16 +30,22 @@ using namespace rapidxml; bool ChannelEpg::UpdateFrom(xml_node<>* channelNode, Channels& channels) { - std::string id; - if (!GetAttributeValue(channelNode, "id", id)) + if (!GetAttributeValue(channelNode, "id", m_id)) return false; - const std::string name = GetNodeValue(channelNode, "display-name"); - if (!channels.FindChannel(id, name)) - return false; - - m_id = id; - m_name = name; + bool foundChannel = false; + for (xml_node<>* displayNameNode = channelNode->first_node("display-name"); displayNameNode; displayNameNode = displayNameNode->next_sibling("display-name")) + { + const std::string name = displayNameNode->value(); + if (channels.FindChannel(m_id, name)) + { + foundChannel = true; + m_names.emplace_back(name); + } + } + + if (!foundChannel) + return false; // get icon if available xml_node<>* iconNode = channelNode->first_node("icon"); diff --git a/src/iptvsimple/data/ChannelEpg.h b/src/iptvsimple/data/ChannelEpg.h index dd2d79ef8..8dbb73a74 100644 --- a/src/iptvsimple/data/ChannelEpg.h +++ b/src/iptvsimple/data/ChannelEpg.h @@ -40,8 +40,8 @@ namespace iptvsimple const std::string& GetId() const { return m_id; } void SetId(const std::string& value) { m_id = value; } - const std::string& GetName() const { return m_name; } - void SetName(const std::string& value) { m_name = value; } + const std::vector& GetNames() const { return m_names; } + void AddName(const std::string& value) { m_names.emplace_back(value); } const std::string& GetIcon() const { return m_icon; } void SetIcon(const std::string& value) { m_icon = value; } @@ -53,7 +53,7 @@ namespace iptvsimple private: std::string m_id; - std::string m_name; + std::vector m_names; std::string m_icon; std::vector m_epgEntries; }; From 169c8f15c8dfc6ba006a02dfa2352deeda903a80 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Wed, 28 Aug 2019 15:24:45 +0100 Subject: [PATCH 12/21] Timing for Playlist and EPG Load --- src/iptvsimple/Epg.cpp | 12 +++++++++--- src/iptvsimple/PlaylistLoader.cpp | 11 ++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/iptvsimple/Epg.cpp b/src/iptvsimple/Epg.cpp index 12606f780..2b81e0dd6 100644 --- a/src/iptvsimple/Epg.cpp +++ b/src/iptvsimple/Epg.cpp @@ -52,6 +52,9 @@ void Epg::Clear() bool Epg::LoadEPG(time_t start, time_t end) { + auto started = std::chrono::high_resolution_clock::now(); + Logger::Log(LEVEL_DEBUG, "%s EPG Load Start", __FUNCTION__); + if (m_xmltvLocation.empty()) { Logger::Log(LEVEL_NOTICE, "EPG file path is not configured. EPG not loaded."); @@ -99,11 +102,14 @@ bool Epg::LoadEPG(time_t start, time_t end) LoadGenres(); - Logger::Log(LEVEL_NOTICE, "EPG Loaded."); - if (Settings::GetInstance().GetEpgLogosMode() != EpgLogosMode::IGNORE_XMLTV) ApplyChannelsLogosFromEPG(); + int milliseconds = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - started).count(); + + Logger::Log(LEVEL_NOTICE, "%s EPG Loaded - %d (ms)", __FUNCTION__, milliseconds); + return true; } @@ -389,7 +395,7 @@ void Epg::ApplyChannelsLogosFromEPG() } bool Epg::LoadGenres() -{ +{ // try to load genres from userdata folder std::string filePath = FileUtils::GetUserFilePath(GENRES_MAP_FILENAME); if (!XBMC->FileExists(filePath.c_str(), false)) diff --git a/src/iptvsimple/PlaylistLoader.cpp b/src/iptvsimple/PlaylistLoader.cpp index 456799c85..42fac51c5 100644 --- a/src/iptvsimple/PlaylistLoader.cpp +++ b/src/iptvsimple/PlaylistLoader.cpp @@ -30,6 +30,7 @@ #include "p8-platform/util/StringUtils.h" +#include #include #include #include @@ -45,6 +46,9 @@ PlaylistLoader::PlaylistLoader(Channels& channels, ChannelGroups& channelGroups) bool PlaylistLoader::LoadPlayList() { + auto started = std::chrono::high_resolution_clock::now(); + Logger::Log(LEVEL_DEBUG, "%s Playlist Load Start", __FUNCTION__); + if (m_m3uLocation.empty()) { Logger::Log(LEVEL_NOTICE, "Playlist file path is not configured. Channels not loaded."); @@ -147,9 +151,14 @@ bool PlaylistLoader::LoadPlayList() stream.clear(); + int milliseconds = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - started).count(); + + Logger::Log(LEVEL_NOTICE, "%s Playlist Loaded - %d (ms)", __FUNCTION__, milliseconds); + if (m_channels.GetChannelsAmount() == 0) { - Logger::Log(LEVEL_ERROR, "Unable to load channels from file '%s': file is corrupted.", m_m3uLocation.c_str()); + Logger::Log(LEVEL_ERROR, "Unable to load channels from file '%s'", m_m3uLocation.c_str()); return false; } From 75a793a2d3c8212952061efc76cb5538ac739d5c Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Wed, 28 Aug 2019 21:55:52 +0100 Subject: [PATCH 13/21] Support for mapping by genre hex ID and added example files and settings --- README.md | 96 ++++++++++-- pvr.iptvsimple/resources/data/genres.xml | 9 ++ .../genreTextMappings/Rytec-UK-Ireland.xml | 134 +++++++++++++++++ .../resources/data/genres/kodiDvbGenres.xml | 138 ++++++++++++++++++ .../data/genres/kodiDvbGenresTypeSubtype.xml | 138 ++++++++++++++++++ .../resource.language.en_gb/strings.po | 72 ++++++++- pvr.iptvsimple/resources/settings.xml | 49 ++++++- src/iptvsimple/Epg.cpp | 72 ++++++--- src/iptvsimple/Epg.h | 6 +- src/iptvsimple/Settings.cpp | 20 +++ src/iptvsimple/Settings.h | 13 ++ src/iptvsimple/data/EpgEntry.cpp | 50 +++++-- src/iptvsimple/data/EpgEntry.h | 6 +- src/iptvsimple/data/EpgGenre.cpp | 31 ++-- src/iptvsimple/utilities/FileUtils.cpp | 108 +++++++++++++- src/iptvsimple/utilities/FileUtils.h | 8 + src/iptvsimple/utilities/XMLUtils.h | 16 ++ 17 files changed, 889 insertions(+), 77 deletions(-) create mode 100755 pvr.iptvsimple/resources/data/genres.xml create mode 100755 pvr.iptvsimple/resources/data/genres/genreTextMappings/Rytec-UK-Ireland.xml create mode 100755 pvr.iptvsimple/resources/data/genres/kodiDvbGenres.xml create mode 100644 pvr.iptvsimple/resources/data/genres/kodiDvbGenresTypeSubtype.xml diff --git a/README.md b/README.md index e186141fc..ff6a54d9e 100644 --- a/README.md +++ b/README.md @@ -65,10 +65,10 @@ General settings required for the addon to function. * **Cache M3U at local storage**: If location is `Remote path` select whether or not the the M3U file should be cached locally. * **Start channel number**: The number to start numbering channels from. -### EPG Settings +### EPG Settings related to the EPG. -My default the addon will read the first `` element of a `programme` and use this as the genre string. It is also possible to supply a mapping file to convert the genre string to a genre ID, allowing colour coding of the EPG. Please see: [Using a mapping file for Genres](#using-a-mapping-file-for-genres) in the Appendix for details on how to set this up. +For settings related to genres please see the next section. * **Location**: Select where to find the XMLTV resource. The options are: - `Local path` - A path to an XMLTV file whether it be on the device or the local network. @@ -79,6 +79,18 @@ My default the addon will read the first `` element of a `programme` a * **EPG time shift**: Adjust the EPG times by this value in minutes, range is from -720 mins to +840 mins (- 12 hours to +14 hours). * **Apply time shift to all channels**: Whether or not to override the time shift for all channels with `EPG time shift`. If not enabled `EPG time shift` plus the individual time shift per channel (if available) will be used. +### Genres +Settings related to genres. + +The addon will read all the `` elements of a `programme` and use this as the genre string. It is also possible to supply a mapping file to convert the genre string to a genre ID, allowing colour coding of the EPG. When using a mapping file each category will be checked in order until a match is found. Please see: [Using a mapping file for Genres](#using-a-mapping-file-for-genres) in the Appendix for details on how to set this up. + +* **Use genre text from XMLTV when mapping genres**: If enabled, and a genre mapping file is used to get a genre type and sub type use the EPG's genre text (i.e. 'category' text) for the genre instead of the kodi default text. Only the genre type (and not the sub type) will be used if a mapping is found. +* **Location**: Select where to find the genres XML resource. The options are: + - `Local path` - A path to a gernes XML file whether it be on the device or the local network. + - `Remote path` - A URL specifying the location of the genres XML file. +* **Genres path**: If location is `Local Path` this setting should contain a valid path. +* **Genres URL**: If location is `Remote Path` this setting should contain a valid URL. + ### Channel Logos Settings realted to Channel Logos. @@ -94,7 +106,15 @@ Settings realted to Channel Logos. ## Appendix -### Supported M3U and XMLTV elements +The various config files have examples allowing users to create their own, making it possible to support custom config, currently regarding genres. The best way to learn about them is to read the config files themselves. Each contains details of how the config file works. + +All of the files listed below are overwritten each time the addon starts (excluding genres.xml). Therefore if you are customising files please create new copies with different file names. Note: that only the files below are overwritten any new files you create will not be touched. + +After adding and selecting new config files you will need to clear the EPG cache `Settings->PVR & Live TV->Guide->Clear cache` for it to take effect in the case of EPG relatd config and for channel related config will need to clear the full cache `Settings->PVR & Live TV->General->Clear cache`. + +If you would like to support other formats/languages please raise an issue at the github project https://github.com/kodi-pvr/pvr.iptvsimple, where you can either create a PR or request your new configs be shipped with the addon. + +There is one config file located here: `userdata/addon_data/pvr.iptvsimple/genres/kodiDvbGenres.xml`. This simply contains the DVB genre IDs that Kodi supports and uses hex for the IDs. Can be a useful reference if creating your own configs. There is also `userdata/addon_data/pvr.iptvsimple/genres/kodiDvbGenresTypeSubtype.xml`, which uses two decimal values instead of hex. This file is also overwritten each time the addon restarts. ### Using a mapping file for Genres @@ -102,24 +122,68 @@ Users can create there own genre mapping files to map their genre strings to gen Kodi uses the following standard for it's genre IDs: https://www.etsi.org/deliver/etsi_en/300400_300499/300468/01.11.01_60/en_300468v011101p.pdf -The file created must be called `genres.xml` and be located here: `userdata/addon_data/pvr.iptvsimple`. +By default the addon will try to load a file called `genres.xml` and expect it to be here: `userdata/addon_data/pvr.iptvsimple/genres/genreTextMappings/`. However any genres file can be chosen in the addon settings. + +The following files are currently available with the addon (this file uses hexadeciaml genreId's): + - `Rytec-UK-Ireland.xml` + +The file can specify either a hexadecimal `genreId` attribute (recommended) or separate integer values for `type` and `subType`. Mathematically `genreId` is equals to the logical OR or `type` and `subType`, i.e. `genreId = type | subType`. + +Note: Once mapped to genre IDs the text displayed can either be the DVB standard text or the genre string text supplied in the XML. If using the text supplied in the XML only the genre type will be passed and each value will correspond to a category and colour (depedning on skin) on the UI. Here are the categories (all examples have 0 for the sub type). It's imortant you map correctly as genres can be used for search. + +``` +- 0x10: General Movie / Drama +- 0x20: News / Current Affairs +- 0x30: Show / Game Show +- 0x40: Sports +- 0x50: Children's / Youth Programmes +- 0x60: Music / Ballet / Dance +- 0x70: Arts / Culture +- 0x80: Social / Political / Economics +- 0x90: Education / Science / Factual +- 0xA0: Leisure / Hobbies +- 0xB0: Special Characteristics +``` + +- ``: There should be a single `` element. The value should denote the purpose of this genre mapping file. +- The value of the `` element is what is used to map from in order to get the genre IDs. Many mapping values are allowed to map to the same IDs. -Here is an exmaple of the file: +**Example using hexadecimal `genreId` attributes (recommended)**: ``` My Streams Genres Mappings - General Movie/Drama - TV Show - Game Show - Show/Game Show - Show/Game Show + Movie + Movie - Comedy + Movie - Romance + TV Show + Game Show + Talk Show + Leisure ``` -- ``: There should be a single `` element. The value should denote the purpose of this genre mapping file. -- ``: There can be many `genre` elements. Both the `type` and `subtype` attributes can contain a value from 0 to 15 (would be 0x0 to 0xF if in DVB hex). `subtype` is optional. The value of the `` element is what is used to map from in order to get the genre IDs. Many mapping values are allowed to map to the same IDs. -Note: Once mapped to genre IDs the text displayed will be the DVB standard text and not the genre string text supplied in the XML. +- The `genreId` attribute is a single hex value ranging from 0x10 to 0xFF. + +**Example using integer `type` and `subtype` attributes**: + +``` + + My Streams Genres Mappings + Movie + Movie - Comedy + Movie - Romance + TV Show + Game Show + Talk Show + Leisure + +``` + +- The `type` attribute can contain a values ranging from 16 to 240 in multiples of 16 (would be 0x10 to 0xF0 if in hex) and the `subtype` attributes can contain a value from 0 to 15 (would be 0x00 to 0x0F if in hex). `subtype` is optional. + + +### Supported M3U and XMLTV elements #### M3U format elemnents: @@ -233,7 +297,7 @@ The following steps can be followed manually instead of using the `build-install **To rebuild the addon after changes** 1. `rm tools/depends/target/binary-addons/.installed-macosx*` -2. `make -j$(getconf _NPROCESSORS_ONLN) -C tools/depends/target/binary-addons ADDONS="pvr.vuplus" ADDON_SRC_PREFIX=$HOME` +2. `make -j$(getconf _NPROCESSORS_ONLN) -C tools/depends/target/binary-addons ADDONS="pvr.iptvsimple" ADDON_SRC_PREFIX=$HOME` or @@ -242,5 +306,5 @@ or **Copy the addon to the Kodi addon directory on Mac** -1. `rm -rf "$HOME/Library/Application Support/Kodi/addons/pvr.vuplus"` -2. `cp -rf $HOME/xbmc-addon/addons/pvr.vuplus "$HOME/Library/Application Support/Kodi/addons"` \ No newline at end of file +1. `rm -rf "$HOME/Library/Application Support/Kodi/addons/pvr.iptvsimple"` +2. `cp -rf $HOME/xbmc-addon/addons/pvr.iptvsimple "$HOME/Library/Application Support/Kodi/addons"` \ No newline at end of file diff --git a/pvr.iptvsimple/resources/data/genres.xml b/pvr.iptvsimple/resources/data/genres.xml new file mode 100755 index 000000000..869eac43d --- /dev/null +++ b/pvr.iptvsimple/resources/data/genres.xml @@ -0,0 +1,9 @@ + + + + Placeholder Genres File + + + diff --git a/pvr.iptvsimple/resources/data/genres/genreTextMappings/Rytec-UK-Ireland.xml b/pvr.iptvsimple/resources/data/genres/genreTextMappings/Rytec-UK-Ireland.xml new file mode 100755 index 000000000..d918df7b2 --- /dev/null +++ b/pvr.iptvsimple/resources/data/genres/genreTextMappings/Rytec-UK-Ireland.xml @@ -0,0 +1,134 @@ + + + + Rytec UK/Ireland + + General Movie/Drama + Film + Animated Movie/Drama + Thriller + Detective/Thriller + Action + Adventure + Adventure/War + Western + Gangster + Fantasy + Science Fiction + Family + Sitcom + Comedy + TV Drama. Comedy + Drama + Soap/Melodrama/Folkloric + TV Drama + TV Drama. Melodrama + TV Drama. Factual + TV Drama. Crime + TV Drama. Period + Medical Drama + Romance + Crime drama + Historical/Period Drama + Police/Crime Drama + + + News + General News/Current Affairs + Documentary + Documentary. News + Discussion. News + + + Series + Show + Vets/Pets + Wildlife + Property + General Show/Game Show + Game Show + Challenge/Reality Show + Show. Variety Show + Variety Show + Entertainment + Miscellaneous + Talk Show + Show. Talk Show + + + Sport + Live/Sport + General Sports + Football. Sports + Martial Sports + Martial Sports. Sports + Wrestling + + + Children + Educational/Schools Programmes + Animation + Cartoons/Puppets + + + Music + General Music/Ballet/Dance + Music. Folk + Musical + + + General Arts/Culture + Arts/Culture + Arts/Culture. Fine Arts + Religion + + + Social/Political + Social/Political. Famous People + + + Education + Educational + History" + Factual" + General Education/Science/Factual Topics + Science + Educational. Nature + Environment + Technology + Computers/Internet/Gaming + + + Leisure + Leisure. Lifestyle + Travel + Health + Leisure. Health + Medicine/Health + Cookery + Leisure. Cooking + Leisure. Shopping + Advertisement/Shopping + Consumer + + + + Factual Crime + diff --git a/pvr.iptvsimple/resources/data/genres/kodiDvbGenres.xml b/pvr.iptvsimple/resources/data/genres/kodiDvbGenres.xml new file mode 100755 index 000000000..b7b6369a7 --- /dev/null +++ b/pvr.iptvsimple/resources/data/genres/kodiDvbGenres.xml @@ -0,0 +1,138 @@ + + + + Kodi DVB Genres using Hexadecimal for genreId + + Undefined + + + General Movie / Drama + Detective / Thriller + Adventure / Western / War + Science Fiction / Fantasy / Horror + Comedy + Soap / Melodrama / Folkloric + Romance + Serious / Classical / Religious / Historical Movie / Drama + Adult Movie / Drama + + + News / Current Affairs + News / Weather Report + News Magazine + Documentary + Discussion / Interview / Debate + + + Show / Game Show + Game Show / Quiz / Contest + Variety Show + Talk Show + + + Sports + Special Event + Sport Magazine + Football + Tennis / Squash + Team Sports + Athletics + Motor Sport + Water Sport + Winter Sports + Equestrian + Martial Sports + + + Children's / Youth Programmes + Pre-school Children's Programmes + Entertainment Programmes for 6 to 14 + Entertainment Programmes for 10 to 16 + Informational / Educational / School Programme + Cartoons / Puppets + + + Music / Ballet / Dance + Rock / Pop + Serious / Classical Music + Folk / Traditional Music + Jazz + Musical / Opera + Ballet + + + Arts / Culture + Performing Arts + Fine Arts + Religion + Popular Culture / Traditional Arts + Literature + Film / Cinema + Experimental Film / Video + Broadcasting / Press + New Media + Arts / Culture Magazines + Fashion + + + Social / Political / Economics + Magazines / Reports / Documentary + Economics / Social Advisory + Remarkable People + + + Education / Science / Factual + Nature / Animals / Environment + Technology / Natural Sciences + Medicine / Physiology / Psychology + Foreign Countries / Expeditions + Social / Spiritual Sciences + Further Education + Languages + + + Leisure / Hobbies + Tourism / Travel + Handicraft + Motoring + Fitness and Health + Cooking + Advertisement / Shopping + Gardening + + + Special Characteristics + Original Language + Black and White + Unpublished + Live Broadcast + + + Drama + Detective / Thriller + Adventure / Western / War + Science Fiction / Fantasy / Horror + + Comedy + Soap / Melodrama / Folkloric + Romance + Serious / ClassicalReligion / Historical + Adult + diff --git a/pvr.iptvsimple/resources/data/genres/kodiDvbGenresTypeSubtype.xml b/pvr.iptvsimple/resources/data/genres/kodiDvbGenresTypeSubtype.xml new file mode 100644 index 000000000..66e7516e4 --- /dev/null +++ b/pvr.iptvsimple/resources/data/genres/kodiDvbGenresTypeSubtype.xml @@ -0,0 +1,138 @@ + + + + Kodi DVB Genres using Integers for type and subtype + + Undefined + + + Movie / Drama + Detective / Thriller + Adventure / Western / War + Science fiction / Fantasy / Horror + Comedy + Soap / Melodrama / Folkloric + Romance + Serious / Classical / Religious / Historical Movie / Drama + Adult Movie / Drama + + + News / Current Affairs + News / Weather Report + News Magazine + Documentary + Discussion / Interview / Debate + + + Show / Game Show + Game Show / Quiz / Contest + Variety show + Talk Show + + + Sports + Special Event + Sports Magazines + Football / Soccer + Tennis / Squash + Team Sports + Athletics + Motor Sport + Water Sport + Winter Sports + Equestrian + Martial Sports + + + Children's / Youth Programs + Pre-school Children's Programs + Entertainment programs for 6 to 14 + Entertainment programs for 10 to 16 + Informational / Educational / School programs + Cartoons / Puppets + + + Music / Ballet / Dance + Rock / Pop + Serious music / Classical Music + Folk / Traditional Music + Jazz + Musical / Opera + Ballet + + + Arts / Culture + Performing Arts + Fine Arts + Religion + Popular Culture / Traditional Arts + Literature + Film / Cinema + Experimental Film / Video + Broadcasting / Press + New Media + Arts magazines / Culture Magazines + Fashion + + + Social / Political issues / Economics + Magazines / Reports / Documentary + Economics / Social Advisory + Remarkable People + + + Education / Science / Factual topics + Nature / Animals / Environment + Technology / Natural sciences + Medicine / Physiology / Psychology + Foreign countries / Expeditions + Social / Spiritual Sciences + Further Education + Languages + + + Leisure Hobbies + Tourism / Travel + Handicraft + Motoring + Fitness and Health + Cooking + Advertisement / Shopping + Gardening + + + Special Characteristics + Original Language + Black & White + Unpublished + Live Broadcast + + + Drama + Detective/Thriller + Adventure/Western/War + Science Fiction/Fantasy/Horror + + Comedy + Soap/Melodrama/Folkloric + Romance + Serious/ClassicalReligion/Historical + Adult + \ No newline at end of file diff --git a/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po b/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po index 09d6c5da0..3d223b62b 100644 --- a/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po +++ b/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po @@ -28,6 +28,7 @@ msgstr "" #label-option: General - m3uPathType #label-option: General - epgPathType #label-option: General - logoPathType +#label-option: General - genresPathType msgctxt "#30001" msgid "Local path (include local network)" msgstr "" @@ -35,6 +36,7 @@ msgstr "" #label-option: General - m3uPathType #label-option: General - epgPathType #label-option: General - logoPathType +#label-option: General - genresPathType msgctxt "#30002" msgid "Remote path (Internet address)" msgstr "" @@ -61,12 +63,16 @@ msgctxt "#30013" msgid "Start channel number" msgstr "" -#empty strings from id 30014 to 30019 +#empty strings from id 30014 to 30018 #label-category: epgsettings +msgctxt "#30019" +msgid "EPG Settings" +msgstr "" + #label-group: EPG Settings - EPG Settings msgctxt "#30020" -msgid "EPG Settings" +msgid "EPG" msgstr "" #label: EPG Settings - epgPath @@ -144,7 +150,30 @@ msgctxt "#30044" msgid "Prefer XMLTV" msgstr "" -#empty strings from id 30045 to 30599 +#empty strings from id 30045 to 30549 + +#label-category: genres +#label-group: Genres - Genres +msgctxt "#30050" +msgid "Genres" +msgstr "" + +#label: Genres - useEpgGenreText +msgctxt "#30051" +msgid "Use genre text from XMLTV when mapping genres" +msgstr "" + +#label: Genres - genresPath +msgctxt "#30052" +msgid "Genres path" +msgstr "" + +#label: Genres - genresUrl +msgctxt "#30053" +msgid "Genres URL" +msgstr "" + +#empty strings from id 30054 to 30599 ############# # help info # @@ -182,7 +211,7 @@ msgctxt "#30605" msgid "The number to start numbering channels from." msgstr "" -#empty strings from id 30006 to 30619 +#empty strings from id 30606 to 30619 #help info - EPG Settings @@ -222,13 +251,13 @@ msgctxt "#30626" msgid "Whether or not to override the time shift for all channels with `EPG time shift`. If not enabled `EPG time shift` plus the individual time shift per channel (if available) will be used." msgstr "" -#empty strings from id 30027 to 30639 +#empty strings from id 30627 to 30639 #help info - Channel Logos #help-category: Channel Logos msgctxt "#30640" -msgid "Settings realted to Channel Logos." +msgid "Settings related to Channel Logos." msgstr "" #help: Channel Logos - logoPathType @@ -249,4 +278,33 @@ msgstr "" #help: Channel Logos - logoFromEpg msgctxt "#30644" msgid "Preference on how to handle channel logos. The options are: [Ignore] - Don't use channel logos from an XMLTV file; [Prefer M3U] - Use the channel logo from the M3U if available otherwise use the XMLTV logo; [Prefer XMLTV] - Use the channel logo from the XMLTV file if available otherwise use the M3U logo." -msgstr "" \ No newline at end of file +msgstr "" + +#empty strings from id 30645 to 30659 + +#help info - Genres + +#help-category: Genres +msgctxt "#30660" +msgid "Settings related to genres." +msgstr "" + +#help: Genres - useEpgGenreText +msgctxt "#30661" +msgid "If enabled, and a genre mapping file is used to get a genre type and sub type use the EPG's genre text (i.e. 'category' text) for the genre instead of the kodi default text." +msgstr "" + +#help: Genres - genresPathType +msgctxt "#30662" +msgid "Select where to find the genres XML resource. The options are: [Local path] - A path to a genres XML file whether it be on the device or the local network; [Remote path] - A URL specifying the location of the genres XML file." +msgstr "" + +#help: Genres - genresPath +msgctxt "#30663" +msgid "If location is [Local Path] this setting should contain a valid path." +msgstr "" + +#help: Genres - genresUrl +msgctxt "#30664" +msgid "If location is [Remote Path] this setting should contain a valid URL." +msgstr "" diff --git a/pvr.iptvsimple/resources/settings.xml b/pvr.iptvsimple/resources/settings.xml index bed33452b..37443259a 100644 --- a/pvr.iptvsimple/resources/settings.xml +++ b/pvr.iptvsimple/resources/settings.xml @@ -59,7 +59,7 @@ - + 0 1 @@ -124,6 +124,53 @@ + + + + + 0 + false + + + + 0 + 0 + + + + + + + + + + 0 + special://userdata/addon_data/pvr.iptvsimple/genres/genreTextMappings/genres.xml + + true + false + + + 0 + + + 1033 + + + + 0 + + + true + + + 1 + + + + + + diff --git a/src/iptvsimple/Epg.cpp b/src/iptvsimple/Epg.cpp index 2b81e0dd6..3bd3d658b 100644 --- a/src/iptvsimple/Epg.cpp +++ b/src/iptvsimple/Epg.cpp @@ -40,14 +40,28 @@ using namespace iptvsimple::data; using namespace iptvsimple::utilities; using namespace rapidxml; -Epg::Epg(Channels& channels) - : m_channels(channels), m_xmltvLocation(Settings::GetInstance().GetEpgLocation()), m_epgTimeShift(Settings::GetInstance().GetEpgTimeshiftSecs()), - m_tsOverride(Settings::GetInstance().GetTsOverride()), m_lastStart(0), m_lastEnd(0) {} +#ifdef TARGET_WINDOWS +#ifdef DeleteFile +#undef DeleteFile +#endif +#endif + +Epg::Epg(Channels& channels) + : m_channels(channels), m_xmltvLocation(Settings::GetInstance().GetEpgLocation()), m_epgTimeShift(Settings::GetInstance().GetEpgTimeshiftSecs()), + m_tsOverride(Settings::GetInstance().GetTsOverride()), m_lastStart(0), m_lastEnd(0) +{ + FileUtils::CopyDirectory(FileUtils::GetResourceDataPath() + GENRE_DIR, GENRE_ADDON_DATA_BASE_DIR, true); + + if (!FileUtils::FileExists(DEFAULT_GENRE_TEXT_MAP_FILE)) + { + MoveOldGenresXMLFileToNewLocation(); + } +} void Epg::Clear() { m_channelEpgs.clear(); - m_genres.clear(); + m_genreMappings.clear(); } bool Epg::LoadEPG(time_t start, time_t end) @@ -312,7 +326,7 @@ PVR_ERROR Epg::GetEPGForChannel(ADDON_HANDLE handle, int iChannelUid, time_t sta EPG_TAG tag = {0}; - epgEntry.UpdateTo(tag, iChannelUid, shift, m_genres); + epgEntry.UpdateTo(tag, iChannelUid, shift, m_genreMappings); PVR->TransferEpgEntry(handle, &tag); @@ -350,9 +364,9 @@ ChannelEpg* Epg::FindEpgForChannel(const Channel& channel) for (const std::string& displayName : myChannelEpg.GetNames()) { const std::string convertedDisplayName = std::regex_replace(displayName, std::regex(" "), "_"); - if (StringUtils::EqualsNoCase(convertedDisplayName, channel.GetTvgName()) || + if (StringUtils::EqualsNoCase(convertedDisplayName, channel.GetTvgName()) || StringUtils::EqualsNoCase(displayName, channel.GetTvgName())) - return &myChannelEpg; + return &myChannelEpg; } } @@ -395,24 +409,17 @@ void Epg::ApplyChannelsLogosFromEPG() } bool Epg::LoadGenres() -{ - // try to load genres from userdata folder - std::string filePath = FileUtils::GetUserFilePath(GENRES_MAP_FILENAME); - if (!XBMC->FileExists(filePath.c_str(), false)) - { - // try to load file from addom folder - filePath = FileUtils::GetClientFilePath(GENRES_MAP_FILENAME); - if (!XBMC->FileExists(filePath.c_str(), false)) - return false; - } +{ + if (!FileUtils::FileExists(Settings::GetInstance().GetGenresLocation())) + return false; std::string data; - FileUtils::GetFileContents(filePath, data); + FileUtils::GetFileContents(Settings::GetInstance().GetGenresLocation(), data); if (data.empty()) return false; - m_genres.clear(); + m_genreMappings.clear(); char* buffer = &(data[0]); xml_document<> xmlDoc; @@ -431,12 +438,31 @@ bool Epg::LoadGenres() for (xml_node<>* pGenreNode = pRootElement->first_node("genre"); pGenreNode; pGenreNode = pGenreNode->next_sibling("genre")) { - EpgGenre genre; + EpgGenre genreMapping; - if (genre.UpdateFrom(pGenreNode)) - m_genres.emplace_back(genre); + if (genreMapping.UpdateFrom(pGenreNode)) + m_genreMappings.emplace_back(genreMapping); } xmlDoc.clear(); + + if (!m_genreMappings.empty()) + Logger::Log(LEVEL_NOTICE, "%s Loaded %d genres", __FUNCTION__, m_genreMappings.size()); + return true; -} \ No newline at end of file +} + +void Epg::MoveOldGenresXMLFileToNewLocation() +{ + //If we don't have a genres.xml file yet copy it if it exists in any of the other old locations. + //If not copy a placeholder file that allows the settings dialog to function. + if (FileUtils::FileExists(ADDON_DATA_BASE_DIR + "/" + GENRES_MAP_FILENAME)) + FileUtils::CopyFile(ADDON_DATA_BASE_DIR + "/" + GENRES_MAP_FILENAME, DEFAULT_GENRE_TEXT_MAP_FILE); + else if (FileUtils::FileExists(FileUtils::GetSystemAddonPath() + "/" + GENRES_MAP_FILENAME)) + FileUtils::CopyFile(FileUtils::GetSystemAddonPath() + "/" + GENRES_MAP_FILENAME, DEFAULT_GENRE_TEXT_MAP_FILE); + else + FileUtils::CopyFile(FileUtils::GetResourceDataPath() + "/" + GENRES_MAP_FILENAME, DEFAULT_GENRE_TEXT_MAP_FILE); + + XBMC->DeleteFile((ADDON_DATA_BASE_DIR + "/" + GENRES_MAP_FILENAME).c_str()); + XBMC->DeleteFile((FileUtils::GetSystemAddonPath() + "/" + GENRES_MAP_FILENAME).c_str()); +} diff --git a/src/iptvsimple/Epg.h b/src/iptvsimple/Epg.h index df4925845..ca88f4edb 100644 --- a/src/iptvsimple/Epg.h +++ b/src/iptvsimple/Epg.h @@ -24,6 +24,7 @@ #include "kodi/libXBMC_pvr.h" #include "Channels.h" +#include "Settings.h" #include "data/ChannelEpg.h" #include "data/EpgGenre.h" @@ -34,6 +35,8 @@ namespace iptvsimple { static const int SECONDS_IN_DAY = 86400; static const std::string GENRES_MAP_FILENAME = "genres.xml"; + static const std::string GENRE_DIR = "/genres"; + static const std::string GENRE_ADDON_DATA_BASE_DIR = ADDON_DATA_BASE_DIR + GENRE_DIR; enum class XmltvFileFormat { @@ -53,6 +56,7 @@ namespace iptvsimple private: static const XmltvFileFormat GetXMLTVFileFormat(const char* buffer); + static void MoveOldGenresXMLFileToNewLocation(); bool LoadEPG(time_t iStart, time_t iEnd); bool GetXMLTVFileWithRetries(std::string& data); @@ -73,6 +77,6 @@ namespace iptvsimple iptvsimple::Channels& m_channels; std::vector m_channelEpgs; - std::vector m_genres; + std::vector m_genreMappings; }; } //namespace iptvsimple \ No newline at end of file diff --git a/src/iptvsimple/Settings.cpp b/src/iptvsimple/Settings.cpp index 81726ce61..e63a7b839 100644 --- a/src/iptvsimple/Settings.cpp +++ b/src/iptvsimple/Settings.cpp @@ -71,6 +71,16 @@ void Settings::ReadFromAddon(const std::string& userPath, const std::string clie if (!XBMC->GetSetting("epgTSOverride", &m_tsOverride)) m_tsOverride = true; + //Genres + if (!XBMC->GetSetting("useEpgGenreText", &m_useEpgGenreTextWhenMapping)) + m_useEpgGenreTextWhenMapping = false; + if (!XBMC->GetSetting("genresPathType", &m_genresPathType)) + m_genresPathType = PathType::LOCAL_PATH; + if (XBMC->GetSetting("genresPath", &buffer)) + m_genresPath = buffer; + if (XBMC->GetSetting("genresUrl", &buffer)) + m_genresUrl = buffer; + // Channel Logos if (!XBMC->GetSetting("logoPathType", &m_logoPathType)) m_logoPathType = PathType::REMOTE_PATH; @@ -120,6 +130,16 @@ ADDON_STATUS Settings::SetValue(const std::string& settingName, const void* sett if (settingName == "epgTSOverride") return SetSetting(settingName, settingValue, m_tsOverride, ADDON_STATUS_OK, ADDON_STATUS_OK); + // Genres + if (settingName == "useEpgGenreText") + return SetSetting(settingName, settingValue, m_useEpgGenreTextWhenMapping, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "genresPathType") + return SetSetting(settingName, settingValue, m_genresPathType, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "genresPath") + return SetStringSetting(settingName, settingValue, m_genresPath, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "genresUrl") + return SetStringSetting(settingName, settingValue, m_genresUrl, ADDON_STATUS_OK, ADDON_STATUS_OK); + // Channel Logos if (settingName == "logoPathType") return SetSetting(settingName, settingValue, m_logoPathType, ADDON_STATUS_OK, ADDON_STATUS_OK); diff --git a/src/iptvsimple/Settings.h b/src/iptvsimple/Settings.h index e5b1021fe..4679044c2 100644 --- a/src/iptvsimple/Settings.h +++ b/src/iptvsimple/Settings.h @@ -31,6 +31,8 @@ namespace iptvsimple { static const std::string M3U_FILE_NAME = "iptv.m3u.cache"; static const std::string TVG_FILE_NAME = "xmltv.xml.cache"; + static const std::string ADDON_DATA_BASE_DIR = "special://userdata/addon_data/pvr.iptvsimple"; + static const std::string DEFAULT_GENRE_TEXT_MAP_FILE = ADDON_DATA_BASE_DIR + "/genres/genreTextMappings/genres.xml"; enum class PathType : int // same type as addon settings @@ -81,6 +83,12 @@ namespace iptvsimple int GetEpgTimeshiftSecs() const { return m_epgTimeShiftMins * 60; } bool GetTsOverride() const { return m_tsOverride; } + const std::string& GetGenresLocation() const { return m_genresPathType == PathType::REMOTE_PATH ? m_genresUrl : m_genresPath; } + bool UseEpgGenreTextWhenMapping() const { return m_useEpgGenreTextWhenMapping; } + const PathType& GetGenresPathType() const { return m_genresPathType; } + const std::string& GetGenresPath() const { return m_genresPath; } + const std::string& GetGenresUrl() const { return m_genresUrl; } + const std::string& GetLogoLocation() const { return m_logoPathType == PathType::REMOTE_PATH ? m_logoBaseUrl : m_logoPath; } const PathType& GetLogoPathType() const { return m_logoPathType; } const std::string& GetLogoPath() const { return m_logoPath; } @@ -138,6 +146,11 @@ namespace iptvsimple int m_epgTimeShiftMins = 0; bool m_tsOverride = true; + bool m_useEpgGenreTextWhenMapping = false; + PathType m_genresPathType = PathType::LOCAL_PATH; + std::string m_genresPath = ""; + std::string m_genresUrl = ""; + PathType m_logoPathType = PathType::REMOTE_PATH; std::string m_logoPath = ""; std::string m_logoBaseUrl = ""; diff --git a/src/iptvsimple/data/EpgEntry.cpp b/src/iptvsimple/data/EpgEntry.cpp index a07a19b5d..93aeab64a 100644 --- a/src/iptvsimple/data/EpgEntry.cpp +++ b/src/iptvsimple/data/EpgEntry.cpp @@ -22,6 +22,7 @@ #include "EpgEntry.h" +#include "../Settings.h" #include "../utilities/XMLUtils.h" #include "p8-platform/util/StringUtils.h" @@ -35,7 +36,7 @@ using namespace iptvsimple; using namespace iptvsimple::data; using namespace rapidxml; -void EpgEntry::UpdateTo(EPG_TAG& left, int iChannelUid, int timeShift, std::vector& genres) +void EpgEntry::UpdateTo(EPG_TAG& left, int iChannelUid, int timeShift, std::vector& genreMappings) { left.iUniqueBroadcastId = m_broadcastId; left.strTitle = m_title.c_str(); @@ -51,11 +52,21 @@ void EpgEntry::UpdateTo(EPG_TAG& left, int iChannelUid, int timeShift, std::vect left.iYear = m_year; left.strIMDBNumber = nullptr; /* not supported */ left.strIconPath = m_iconPath.c_str(); - if (SetEpgGenre(genres, m_genreString)) + if (SetEpgGenre(genreMappings)) { - left.iGenreType = m_genreType; - left.iGenreSubType = m_genreSubType; - left.strGenreDescription = nullptr; + left.iGenreType = m_genreType; + if (Settings::GetInstance().UseEpgGenreTextWhenMapping()) + { + //Setting this value in sub type allows custom text to be displayed + //while still sending the type used for EPG colour + left.iGenreSubType = EPG_GENRE_USE_STRING; + left.strGenreDescription = m_genreString.c_str(); + } + else + { + left.iGenreSubType = m_genreSubType; + left.strGenreDescription = nullptr; + } } else { @@ -67,24 +78,30 @@ void EpgEntry::UpdateTo(EPG_TAG& left, int iChannelUid, int timeShift, std::vect left.iStarRating = m_starRating; left.iSeriesNumber = m_seasonNumber; left.iEpisodeNumber = m_episodeNumber; - left.iEpisodePartNumber = m_episodePartNumber; + left.iEpisodePartNumber = m_episodePartNumber; left.strEpisodeName = m_episodeName.c_str(); left.iFlags = EPG_TAG_FLAG_UNDEFINED; left.firstAired = m_firstAired; } -bool EpgEntry::SetEpgGenre(std::vector genres, const std::string& genreToFind) +bool EpgEntry::SetEpgGenre(std::vector genreMappings) { - if (genres.empty()) + if (genreMappings.empty()) return false; - for (const auto& myGenre : genres) + for (const auto& genre : StringUtils::Split(m_genreString, EPG_STRING_TOKEN_SEPARATOR)) { - if (StringUtils::EqualsNoCase(myGenre.GetGenreString(), genreToFind)) + if (genre.empty()) + continue; + + for (const auto& genreMapping : genreMappings) { - m_genreType = myGenre.GetGenreType(); - m_genreSubType = myGenre.GetGenreSubType(); - return true; + if (StringUtils::EqualsNoCase(genreMapping.GetGenreString(), genre)) + { + m_genreType = genreMapping.GetGenreType(); + m_genreSubType = genreMapping.GetGenreSubType(); + return true; + } } } @@ -185,13 +202,14 @@ bool EpgEntry::UpdateFrom(rapidxml::xml_node<>* channelNode, const std::string& m_starRating = 0; m_episodeNumber = 0; m_episodePartNumber = 0; - m_seasonNumber = 0; + m_seasonNumber = 0; m_title = GetNodeValue(channelNode, "title"); m_plot = GetNodeValue(channelNode, "desc"); - m_genreString = GetNodeValue(channelNode, "category"); m_episodeName = GetNodeValue(channelNode, "sub-title"); + m_genreString = GetJoinedNodeValues(channelNode, "category"); + const std::string dateString = GetNodeValue(channelNode, "date"); if (!dateString.empty()) { @@ -214,7 +232,7 @@ bool EpgEntry::UpdateFrom(rapidxml::xml_node<>* channelNode, const std::string& } if (!episodeNumbersList.empty()) - ParseEpisodeNumberInfo(episodeNumbersList); + ParseEpisodeNumberInfo(episodeNumbersList); xml_node<> *creditsNode = channelNode->first_node("credits"); if (creditsNode) diff --git a/src/iptvsimple/data/EpgEntry.h b/src/iptvsimple/data/EpgEntry.h index de103eea4..840bbeb3b 100644 --- a/src/iptvsimple/data/EpgEntry.h +++ b/src/iptvsimple/data/EpgEntry.h @@ -64,7 +64,7 @@ namespace iptvsimple void SetEpisodePartNumber(int value) { m_episodePartNumber = value; } int GetSeasonNumber() const { return m_seasonNumber; } - void SetSeasonNumber(int value) { m_seasonNumber = value; } + void SetSeasonNumber(int value) { m_seasonNumber = value; } time_t GetStartTime() const { return m_startTime; } void SetStartTime(time_t value) { m_startTime = value; } @@ -107,7 +107,7 @@ namespace iptvsimple int start, int end, int minShiftTime, int maxShiftTime); private: - bool SetEpgGenre(std::vector genres, const std::string& genreToFind); + bool SetEpgGenre(std::vector genreMappings); bool ParseEpisodeNumberInfo(std::vector>& episodeNumbersList); bool ParseXmltvNsEpisodeNumberInfo(const std::string& episodeNumberString); bool ParseOnScreenEpisodeNumberInfo(const std::string& episodeNumberString); @@ -120,7 +120,7 @@ namespace iptvsimple int m_starRating; int m_episodeNumber = 0; int m_episodePartNumber = 0; - int m_seasonNumber = 0; + int m_seasonNumber = 0; time_t m_startTime; time_t m_endTime; time_t m_firstAired; diff --git a/src/iptvsimple/data/EpgGenre.cpp b/src/iptvsimple/data/EpgGenre.cpp index d2c02c6b9..b772ea647 100644 --- a/src/iptvsimple/data/EpgGenre.cpp +++ b/src/iptvsimple/data/EpgGenre.cpp @@ -34,18 +34,31 @@ using namespace rapidxml; bool EpgGenre::UpdateFrom(rapidxml::xml_node<>* genreNode) { std::string buffer; - if (!GetAttributeValue(genreNode, "type", buffer)) - return false; - if (!StringUtils::IsNaturalNumber(buffer)) - return false; + if (GetAttributeValue(genreNode, "genreId", buffer)) + { + //Combined genre id read as a single hex value. + int genreId = std::strtol(buffer.c_str(), nullptr, 16); - m_genreString = genreNode->value(); - m_genreType = std::atoi(buffer.c_str()); - m_genreSubType = 0; + m_genreString = genreNode->value(); + m_genreType = genreId & 0xF0; + m_genreSubType = genreId & 0x0F; + } + else + { + if (!GetAttributeValue(genreNode, "type", buffer)) + return false; - if (GetAttributeValue(genreNode, "subtype", buffer) && StringUtils::IsNaturalNumber(buffer)) - m_genreSubType = std::atoi(buffer.c_str()); + if (!StringUtils::IsNaturalNumber(buffer)) + return false; + + m_genreString = genreNode->value(); + m_genreType = std::atoi(buffer.c_str()); + m_genreSubType = 0; + + if (GetAttributeValue(genreNode, "subtype", buffer) && StringUtils::IsNaturalNumber(buffer)) + m_genreSubType = std::atoi(buffer.c_str()); + } return true; } diff --git a/src/iptvsimple/utilities/FileUtils.cpp b/src/iptvsimple/utilities/FileUtils.cpp index 51fb015dc..04e3154f5 100644 --- a/src/iptvsimple/utilities/FileUtils.cpp +++ b/src/iptvsimple/utilities/FileUtils.cpp @@ -25,6 +25,8 @@ #include "../../client.h" #include "zlib.h" +#include + using namespace iptvsimple; using namespace iptvsimple::utilities; @@ -191,4 +193,108 @@ int FileUtils::GetCachedFileContents(const std::string& cachedName, const std::s } return FileUtils::GetFileContents(cachedPath, contents); -} \ No newline at end of file +} + +bool FileUtils::FileExists(const std::string& file) +{ + return XBMC->FileExists(file.c_str(), false); +} + +bool FileUtils::CopyFile(const std::string& sourceFile, const std::string& targetFile) +{ + bool copySuccessful = true; + + Logger::Log(LEVEL_DEBUG, "%s Copying file: %s, to %s", __FUNCTION__, sourceFile.c_str(), targetFile.c_str()); + + void* sourceFileHandle = XBMC->OpenFile(sourceFile.c_str(), 0x08); //READ_NO_CACHE + + if (sourceFileHandle) + { + const std::string fileContents = ReadFileContents(sourceFileHandle); + + XBMC->CloseFile(sourceFileHandle); + + void* targetFileHandle = XBMC->OpenFileForWrite(targetFile.c_str(), true); + + if (targetFileHandle) + { + XBMC->WriteFile(targetFileHandle, fileContents.c_str(), fileContents.length()); + XBMC->CloseFile(targetFileHandle); + } + else + { + Logger::Log(LEVEL_ERROR, "%s Could not open target file to copy to: %s", __FUNCTION__, targetFile.c_str()); + copySuccessful = false; + } + } + else + { + Logger::Log(LEVEL_ERROR, "%s Could not open source file to copy: %s", __FUNCTION__, sourceFile.c_str()); + copySuccessful = false; + } + + return copySuccessful; +} + +bool FileUtils::CopyDirectory(const std::string& sourceDir, const std::string& targetDir, bool recursiveCopy) +{ + bool copySuccessful = true; + + XBMC->CreateDirectory(targetDir.c_str()); + + VFSDirEntry* entries; + unsigned int numEntries; + + if (XBMC->GetDirectory(sourceDir.c_str(), "", &entries, &numEntries)) + { + for (int i = 0; i < numEntries; i++) + { + if (entries[i].folder && recursiveCopy) + { + copySuccessful = CopyDirectory(sourceDir + "/" + entries[i].label, targetDir + "/" + entries[i].label, true); + } + else if (!entries[i].folder) + { + copySuccessful = CopyFile(sourceDir + "/" + entries[i].label, targetDir + "/" + entries[i].label); + } + } + + XBMC->FreeDirectory(entries, numEntries); + } + else + { + Logger::Log(LEVEL_ERROR, "%s Could not copy directory: %s, to directory: %s", __FUNCTION__, sourceDir.c_str(), targetDir.c_str()); + copySuccessful = false; + } + return copySuccessful; +} + +std::string FileUtils::GetSystemAddonPath() +{ + char path[1024]; + XBMC->GetSetting("__addonpath__", path); + + return path; +} + +std::string FileUtils::GetResourceDataPath() +{ + std::string resourcesDataPath = GetSystemAddonPath(); + resourcesDataPath += "/resources/data"; + + return resourcesDataPath; +} + +std::string FileUtils::ReadFileContents(void* fileHandle) +{ + std::string fileContents; + + char buffer[1024]; + int bytesRead = 0; + + // Read until EOF or explicit error + while ((bytesRead = XBMC->ReadFile(fileHandle, buffer, sizeof(buffer) - 1)) > 0) + fileContents.append(buffer, bytesRead); + + return fileContents; +} diff --git a/src/iptvsimple/utilities/FileUtils.h b/src/iptvsimple/utilities/FileUtils.h index ed0b7e71c..2b5d6b564 100644 --- a/src/iptvsimple/utilities/FileUtils.h +++ b/src/iptvsimple/utilities/FileUtils.h @@ -39,6 +39,14 @@ namespace iptvsimple static bool GzipInflate(const std::string& compressedBytes, std::string& uncompressedBytes); static int GetCachedFileContents(const std::string& cachedName, const std::string& filePath, std::string& content, const bool useCache = false); + static bool FileExists(const std::string& file); + static bool CopyFile(const std::string& sourceFile, const std::string& targetFile); + static bool CopyDirectory(const std::string& sourceDir, const std::string& targetDir, bool recursiveCopy); + static std::string GetSystemAddonPath(); + static std::string GetResourceDataPath(); + + private: + static std::string ReadFileContents(void* fileHandle); }; } // namespace utilities } // namespace iptvsimple diff --git a/src/iptvsimple/utilities/XMLUtils.h b/src/iptvsimple/utilities/XMLUtils.h index 9c4680351..020ea4210 100644 --- a/src/iptvsimple/utilities/XMLUtils.h +++ b/src/iptvsimple/utilities/XMLUtils.h @@ -23,6 +23,7 @@ #include "rapidxml/rapidxml.hpp" #include +#include template inline std::string GetNodeValue(const rapidxml::xml_node* rootNode, const char* tag) @@ -53,6 +54,21 @@ inline std::string GetJoinedNodeValues(const rapidxml::xml_node* rootNode, c return stringValue; } +template +inline std::vector GetNodeValuesList(const rapidxml::xml_node* rootNode, const char* tag) +{ + std::vector stringValues; + + rapidxml::xml_node* childNode = nullptr; + for(childNode = rootNode->first_node(tag); childNode; childNode = childNode->next_sibling(tag)) + { + if (childNode) + stringValues.emplace_back(childNode->value()); + } + + return stringValues; +} + template inline bool GetAttributeValue(const rapidxml::xml_node* node, const char* attributeName, std::string& stringValue) { From 458e3335f3bcaa1c727ed300b228a64a4bc81110 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Fri, 30 Aug 2019 12:24:14 +0100 Subject: [PATCH 14/21] Refactoring --- src/iptvsimple/Epg.cpp | 2 +- src/iptvsimple/PlaylistLoader.cpp | 4 ++-- src/iptvsimple/Settings.cpp | 4 ++-- src/iptvsimple/Settings.h | 4 ++-- src/iptvsimple/utilities/FileUtils.cpp | 11 +++-------- src/iptvsimple/utilities/FileUtils.h | 3 +-- 6 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/iptvsimple/Epg.cpp b/src/iptvsimple/Epg.cpp index 3bd3d658b..31496866a 100644 --- a/src/iptvsimple/Epg.cpp +++ b/src/iptvsimple/Epg.cpp @@ -134,7 +134,7 @@ bool Epg::GetXMLTVFileWithRetries(std::string& data) while (count < 3) // max 3 tries { - if ((bytesRead = FileUtils::GetCachedFileContents(TVG_FILE_NAME, m_xmltvLocation, data, Settings::GetInstance().UseEPGCache())) != 0) + if ((bytesRead = FileUtils::GetCachedFileContents(XMLTV_CACHE_FILENAME, m_xmltvLocation, data, Settings::GetInstance().UseEPGCache())) != 0) break; Logger::Log(LEVEL_ERROR, "Unable to load EPG file '%s': file is missing or empty. :%dth try.", m_xmltvLocation.c_str(), ++count); diff --git a/src/iptvsimple/PlaylistLoader.cpp b/src/iptvsimple/PlaylistLoader.cpp index 42fac51c5..bc1d963d6 100644 --- a/src/iptvsimple/PlaylistLoader.cpp +++ b/src/iptvsimple/PlaylistLoader.cpp @@ -56,7 +56,7 @@ bool PlaylistLoader::LoadPlayList() } std::string playlistContent; - if (!FileUtils::GetCachedFileContents(M3U_FILE_NAME, m_m3uLocation, playlistContent, Settings::GetInstance().UseM3UCache())) + if (!FileUtils::GetCachedFileContents(M3U_CACHE_FILENAME, m_m3uLocation, playlistContent, Settings::GetInstance().UseM3UCache())) { Logger::Log(LEVEL_ERROR, "Unable to load playlist file '%s': file is missing or empty.", m_m3uLocation.c_str()); return false; @@ -192,7 +192,7 @@ std::string PlaylistLoader::ParseIntoChannel(const std::string& line, Channel& c if (strTvgId.empty()) strTvgId = ReadMarkerValue(infoLine, TVG_INFO_ID_MARKER_UC); - + if (strTvgId.empty()) { char buff[255]; diff --git a/src/iptvsimple/Settings.cpp b/src/iptvsimple/Settings.cpp index e63a7b839..92e775caf 100644 --- a/src/iptvsimple/Settings.cpp +++ b/src/iptvsimple/Settings.cpp @@ -96,11 +96,11 @@ ADDON_STATUS Settings::SetValue(const std::string& settingName, const void* sett { // reset cache and restart addon - std::string strFile = FileUtils::GetUserFilePath(M3U_FILE_NAME); + std::string strFile = FileUtils::GetUserDataAddonFilePath(M3U_CACHE_FILENAME); if (XBMC->FileExists(strFile.c_str(), false)) XBMC->DeleteFile(strFile.c_str()); - strFile = FileUtils::GetUserFilePath(TVG_FILE_NAME); + strFile = FileUtils::GetUserDataAddonFilePath(XMLTV_CACHE_FILENAME); if (XBMC->FileExists(strFile.c_str(), false)) XBMC->DeleteFile(strFile.c_str()); diff --git a/src/iptvsimple/Settings.h b/src/iptvsimple/Settings.h index 4679044c2..aa5e936ce 100644 --- a/src/iptvsimple/Settings.h +++ b/src/iptvsimple/Settings.h @@ -29,8 +29,8 @@ namespace iptvsimple { - static const std::string M3U_FILE_NAME = "iptv.m3u.cache"; - static const std::string TVG_FILE_NAME = "xmltv.xml.cache"; + static const std::string M3U_CACHE_FILENAME = "iptv.m3u.cache"; + static const std::string XMLTV_CACHE_FILENAME = "xmltv.xml.cache"; static const std::string ADDON_DATA_BASE_DIR = "special://userdata/addon_data/pvr.iptvsimple"; static const std::string DEFAULT_GENRE_TEXT_MAP_FILE = ADDON_DATA_BASE_DIR + "/genres/genreTextMappings/genres.xml"; diff --git a/src/iptvsimple/utilities/FileUtils.cpp b/src/iptvsimple/utilities/FileUtils.cpp index 04e3154f5..7970fdd72 100644 --- a/src/iptvsimple/utilities/FileUtils.cpp +++ b/src/iptvsimple/utilities/FileUtils.cpp @@ -33,7 +33,7 @@ using namespace iptvsimple::utilities; std::string FileUtils::PathCombine(const std::string& path, const std::string& fileName) { std::string result = path; - + if (!result.empty()) { if (result.at(result.size() - 1) == '\\' || @@ -55,12 +55,7 @@ std::string FileUtils::PathCombine(const std::string& path, const std::string& f return result; } -std::string FileUtils::GetClientFilePath(const std::string& fileName) -{ - return PathCombine(Settings::GetInstance().GetClientPath(), fileName); -} - -std::string FileUtils::GetUserFilePath(const std::string& fileName) +std::string FileUtils::GetUserDataAddonFilePath(const std::string& fileName) { return PathCombine(Settings::GetInstance().GetUserPath(), fileName); } @@ -157,7 +152,7 @@ int FileUtils::GetCachedFileContents(const std::string& cachedName, const std::s std::string& contents, const bool useCache /* false */) { bool needReload = false; - const std::string cachedPath = FileUtils::GetUserFilePath(cachedName); + const std::string cachedPath = FileUtils::GetUserDataAddonFilePath(cachedName); // check cached file is exists if (useCache && XBMC->FileExists(cachedPath.c_str(), false)) diff --git a/src/iptvsimple/utilities/FileUtils.h b/src/iptvsimple/utilities/FileUtils.h index 2b5d6b564..54df40313 100644 --- a/src/iptvsimple/utilities/FileUtils.h +++ b/src/iptvsimple/utilities/FileUtils.h @@ -33,8 +33,7 @@ namespace iptvsimple { public: static std::string PathCombine(const std::string& path, const std::string& fileName); - static std::string GetClientFilePath(const std::string& fileName); - static std::string GetUserFilePath(const std::string& fileName); + static std::string GetUserDataAddonFilePath(const std::string& fileName); static int GetFileContents(const std::string& url, std::string& content); static bool GzipInflate(const std::string& compressedBytes, std::string& uncompressedBytes); static int GetCachedFileContents(const std::string& cachedName, const std::string& filePath, From f47af5dcfbfa69a24967a953fc40c42dc480bbcc Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Fri, 30 Aug 2019 12:25:28 +0100 Subject: [PATCH 15/21] Option to number channels by M3U order only --- README.md | 1 + .../language/resource.language.en_gb/strings.po | 14 ++++++++++++-- pvr.iptvsimple/resources/settings.xml | 5 +++++ src/iptvsimple/PlaylistLoader.cpp | 2 +- src/iptvsimple/Settings.cpp | 4 ++++ src/iptvsimple/Settings.h | 2 ++ 6 files changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ff6a54d9e..75d62b2e8 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ General settings required for the addon to function. * **M3U play list URL**: If location is `Remote path` this setting must contain a valid URL for the addon to function. * **Cache M3U at local storage**: If location is `Remote path` select whether or not the the M3U file should be cached locally. * **Start channel number**: The number to start numbering channels from. +* **Only number by channel order in M3U**: Ignore any 'tvg-chno' tags and only number channels by the order in the M3U starting at 'Start channel number'. ### EPG Settings related to the EPG. diff --git a/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po b/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po index 3d223b62b..47f55753d 100644 --- a/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po +++ b/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po @@ -59,11 +59,17 @@ msgctxt "#30012" msgid "M3U playlist URL" msgstr "" +#label: General - startNum msgctxt "#30013" msgid "Start channel number" msgstr "" -#empty strings from id 30014 to 30018 +#label: General - numberByOrder +msgctxt "#30014" +msgid "Only number by channel order in M3U" +msgstr "" + +#empty strings from id 30015 to 30018 #label-category: epgsettings msgctxt "#30019" @@ -211,8 +217,12 @@ msgctxt "#30605" msgid "The number to start numbering channels from." msgstr "" -#empty strings from id 30606 to 30619 +#help: General - numberByOrder +msgctxt "#30606" +msgid "Ignore any 'tvg-chno' tags and only number channels by the order in the M3U starting at 'Start channel number'." +msgstr "" +#empty strings from id 30607 to 30619 #help info - EPG Settings diff --git a/pvr.iptvsimple/resources/settings.xml b/pvr.iptvsimple/resources/settings.xml index 37443259a..a4c882345 100644 --- a/pvr.iptvsimple/resources/settings.xml +++ b/pvr.iptvsimple/resources/settings.xml @@ -54,6 +54,11 @@ 1 + + 0 + false + + diff --git a/src/iptvsimple/PlaylistLoader.cpp b/src/iptvsimple/PlaylistLoader.cpp index bc1d963d6..7a1118f2c 100644 --- a/src/iptvsimple/PlaylistLoader.cpp +++ b/src/iptvsimple/PlaylistLoader.cpp @@ -207,7 +207,7 @@ std::string PlaylistLoader::ParseIntoChannel(const std::string& line, Channel& c logoSetFromChannelName = true; } - if (!strChnlNo.empty()) + if (!strChnlNo.empty() && !Settings::GetInstance().NumberChannelsByM3uOrderOnly()) channel.SetChannelNumber(std::atoi(strChnlNo.c_str())); double tvgShiftDecimal = std::atof(strTvgShift.c_str()); diff --git a/src/iptvsimple/Settings.cpp b/src/iptvsimple/Settings.cpp index 92e775caf..2a1e7d70e 100644 --- a/src/iptvsimple/Settings.cpp +++ b/src/iptvsimple/Settings.cpp @@ -56,6 +56,8 @@ void Settings::ReadFromAddon(const std::string& userPath, const std::string clie m_cacheM3U = true; if (!XBMC->GetSetting("startNum", &m_startChannelNumber)) m_startChannelNumber = 1; + if (!XBMC->GetSetting("numberByOrder", &m_numberChannelsByM3uOrderOnly)) + m_numberChannelsByM3uOrderOnly = false; // EPG if (!XBMC->GetSetting("epgPathType", &m_epgPathType)) @@ -115,6 +117,8 @@ ADDON_STATUS Settings::SetValue(const std::string& settingName, const void* sett return SetSetting(settingName, settingValue, m_cacheM3U, ADDON_STATUS_OK, ADDON_STATUS_OK); if (settingName == "startNum") return SetSetting(settingName, settingValue, m_startChannelNumber, ADDON_STATUS_OK, ADDON_STATUS_OK); + if (settingName == "numberByOrder") + return SetSetting(settingName, settingValue, m_numberChannelsByM3uOrderOnly, ADDON_STATUS_OK, ADDON_STATUS_OK); // EPG if (settingName == "epgPathType") diff --git a/src/iptvsimple/Settings.h b/src/iptvsimple/Settings.h index aa5e936ce..2961da0c3 100644 --- a/src/iptvsimple/Settings.h +++ b/src/iptvsimple/Settings.h @@ -73,6 +73,7 @@ namespace iptvsimple const std::string& GetM3UUrl() const { return m_m3uUrl; } bool UseM3UCache() const { return m_m3uPathType == PathType::REMOTE_PATH ? m_cacheM3U : false; } int GetStartChannelNumber() const { return m_startChannelNumber; } + bool NumberChannelsByM3uOrderOnly() const { return m_numberChannelsByM3uOrderOnly; } const std::string& GetEpgLocation() const { return m_epgPathType == PathType::REMOTE_PATH ? m_epgUrl : m_epgPath; } const PathType& GetEpgPathType() const { return m_epgPathType; } @@ -138,6 +139,7 @@ namespace iptvsimple std::string m_m3uUrl = ""; bool m_cacheM3U = false; int m_startChannelNumber = 1; + bool m_numberChannelsByM3uOrderOnly = false; PathType m_epgPathType = PathType::REMOTE_PATH; std::string m_epgPath = ""; From f8404effba3bed1005f164a1c3c74faa212474b2 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Fri, 30 Aug 2019 13:06:03 +0100 Subject: [PATCH 16/21] Update debug logging --- src/client.cpp | 2 +- src/iptvsimple/ChannelGroups.cpp | 7 ++++-- src/iptvsimple/Channels.cpp | 8 +++---- src/iptvsimple/Epg.cpp | 32 +++++++++++++++++--------- src/iptvsimple/PlaylistLoader.cpp | 18 +++++++-------- src/iptvsimple/data/ChannelEpg.cpp | 2 +- src/iptvsimple/utilities/FileUtils.cpp | 8 +++---- 7 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 26ed2ca26..e91212a16 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -112,7 +112,7 @@ ADDON_STATUS ADDON_Create(void* hdl, void* props) Logger::GetInstance().SetPrefix("pvr.iptvsimple"); - Logger::Log(LogLevel::LEVEL_INFO, "%s Creating the PVR IPTV Simple add-on", __FUNCTION__); + Logger::Log(LogLevel::LEVEL_INFO, "%s - Creating the PVR IPTV Simple add-on", __FUNCTION__); m_currentStatus = ADDON_STATUS_UNKNOWN; const std::string userPath = pvrprops->strUserPath; diff --git a/src/iptvsimple/ChannelGroups.cpp b/src/iptvsimple/ChannelGroups.cpp index 32355c894..fc21f4d87 100644 --- a/src/iptvsimple/ChannelGroups.cpp +++ b/src/iptvsimple/ChannelGroups.cpp @@ -47,10 +47,10 @@ void ChannelGroups::GetChannelGroups(std::vector& kodiChannel for (const auto& channelGroup : m_channelGroups) { - Logger::Log(LEVEL_DEBUG, "%s - Transfer channelGroup '%s', ChannelGroupIndex '%d'", __FUNCTION__, channelGroup.GetGroupName().c_str(), channelGroup.GetUniqueId()); - if (channelGroup.IsRadio() == radio) { + Logger::Log(LEVEL_DEBUG, "%s - Transfer channelGroup '%s', ChannelGroupId '%d'", __FUNCTION__, channelGroup.GetGroupName().c_str(), channelGroup.GetUniqueId()); + PVR_CHANNEL_GROUP kodiChannelGroup = {0}; channelGroup.UpdateTo(kodiChannelGroup); @@ -79,6 +79,9 @@ PVR_ERROR ChannelGroups::GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_C xbmcGroupMember.iChannelUniqueId = channel.GetUniqueId(); xbmcGroupMember.iChannelNumber = channel.GetChannelNumber(); + Logger::Log(LEVEL_DEBUG, "%s - Transfer channel group '%s' member '%s', ChannelId '%d', ChannelNumberInGroup: '%d'", __FUNCTION__, + myGroup->GetGroupName().c_str(), channel.GetChannelName().c_str(), channel.GetUniqueId(), channelNumberInGroup); + PVR->TransferChannelGroupMember(handle, &xbmcGroupMember); } } diff --git a/src/iptvsimple/Channels.cpp b/src/iptvsimple/Channels.cpp index 9c04d8245..15e31cbbf 100644 --- a/src/iptvsimple/Channels.cpp +++ b/src/iptvsimple/Channels.cpp @@ -36,8 +36,8 @@ using namespace iptvsimple; using namespace iptvsimple::data; using namespace iptvsimple::utilities; -Channels::Channels() - : m_logoLocation(Settings::GetInstance().GetLogoLocation()), +Channels::Channels() + : m_logoLocation(Settings::GetInstance().GetLogoLocation()), m_currentChannelNumber(Settings::GetInstance().GetStartChannelNumber()) {} void Channels::Clear() @@ -58,8 +58,8 @@ void Channels::GetChannels(std::vector& kodiChannels, bool radio) c { if (channel.IsRadio() == radio) { - Logger::Log(LEVEL_DEBUG, "%s - Transfer channel '%s', ChannelIndex '%d'", __FUNCTION__, channel.GetChannelName().c_str(), - channel.GetUniqueId()); + Logger::Log(LEVEL_DEBUG, "%s - Transfer channel '%s', ChannelId '%d', ChannelNumber: '%d'", __FUNCTION__, channel.GetChannelName().c_str(), + channel.GetUniqueId(), channel.GetChannelNumber()); PVR_CHANNEL kodiChannel = {0}; channel.UpdateTo(kodiChannel); diff --git a/src/iptvsimple/Epg.cpp b/src/iptvsimple/Epg.cpp index 31496866a..cd552cd22 100644 --- a/src/iptvsimple/Epg.cpp +++ b/src/iptvsimple/Epg.cpp @@ -67,11 +67,11 @@ void Epg::Clear() bool Epg::LoadEPG(time_t start, time_t end) { auto started = std::chrono::high_resolution_clock::now(); - Logger::Log(LEVEL_DEBUG, "%s EPG Load Start", __FUNCTION__); + Logger::Log(LEVEL_DEBUG, "%s - EPG Load Start", __FUNCTION__); if (m_xmltvLocation.empty()) { - Logger::Log(LEVEL_NOTICE, "EPG file path is not configured. EPG not loaded."); + Logger::Log(LEVEL_NOTICE, "%s - EPG file path is not configured. EPG not loaded.", __FUNCTION__); return false; } @@ -91,14 +91,14 @@ bool Epg::LoadEPG(time_t start, time_t end) } catch (parse_error p) { - Logger::Log(LEVEL_ERROR, "Unable parse EPG XML: %s", p.what()); + Logger::Log(LEVEL_ERROR, "%s - Unable parse EPG XML: %s", __FUNCTION__, p.what()); return false; } xml_node<>* rootElement = xmlDoc.first_node("tv"); if (!rootElement) { - Logger::Log(LEVEL_ERROR, "Invalid EPG XML: no tag found"); + Logger::Log(LEVEL_ERROR, "%s - Invalid EPG XML: no tag found", __FUNCTION__); return false; } @@ -122,7 +122,7 @@ bool Epg::LoadEPG(time_t start, time_t end) int milliseconds = std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - started).count(); - Logger::Log(LEVEL_NOTICE, "%s EPG Loaded - %d (ms)", __FUNCTION__, milliseconds); + Logger::Log(LEVEL_NOTICE, "%s - EPG Loaded - %d (ms)", __FUNCTION__, milliseconds); return true; } @@ -137,7 +137,7 @@ bool Epg::GetXMLTVFileWithRetries(std::string& data) if ((bytesRead = FileUtils::GetCachedFileContents(XMLTV_CACHE_FILENAME, m_xmltvLocation, data, Settings::GetInstance().UseEPGCache())) != 0) break; - Logger::Log(LEVEL_ERROR, "Unable to load EPG file '%s': file is missing or empty. :%dth try.", m_xmltvLocation.c_str(), ++count); + Logger::Log(LEVEL_ERROR, "%s - Unable to load EPG file '%s': file is missing or empty. :%dth try.", __FUNCTION__, m_xmltvLocation.c_str(), ++count); if (count < 3) std::this_thread::sleep_for(std::chrono::microseconds(2 * 1000 * 1000)); // sleep 2 sec before next try. @@ -145,7 +145,7 @@ bool Epg::GetXMLTVFileWithRetries(std::string& data) if (bytesRead == 0) { - Logger::Log(LEVEL_ERROR, "Unable to load EPG file '%s': file is missing or empty. After %d tries.", m_xmltvLocation.c_str(), count); + Logger::Log(LEVEL_ERROR, "%s - Unable to load EPG file '%s': file is missing or empty. After %d tries.", __FUNCTION__, m_xmltvLocation.c_str(), count); return false; } @@ -162,7 +162,7 @@ char* Epg::FillBufferFromXMLTVData(std::string& data) { if (!FileUtils::GzipInflate(data, decompressed)) { - Logger::Log(LEVEL_ERROR, "Invalid EPG file '%s': unable to decompress file.", m_xmltvLocation.c_str()); + Logger::Log(LEVEL_ERROR, "%s - Invalid EPG file '%s': unable to decompress file.", __FUNCTION__, m_xmltvLocation.c_str()); return nullptr; } buffer = &(decompressed[0]); @@ -176,7 +176,7 @@ char* Epg::FillBufferFromXMLTVData(std::string& data) if (fileFormat == XmltvFileFormat::INVALID) { - Logger::Log(LEVEL_ERROR, "Invalid EPG file '%s': unable to parse file.", m_xmltvLocation.c_str()); + Logger::Log(LEVEL_ERROR, "%s - Invalid EPG file '%s': unable to parse file.", __FUNCTION__, m_xmltvLocation.c_str()); return nullptr; } @@ -222,14 +222,22 @@ bool Epg::LoadChannelEpgs(xml_node<>* rootElement) ChannelEpg channelEpg; if (channelEpg.UpdateFrom(channelNode, m_channels)) + { + Logger::Log(LEVEL_DEBUG, "%s - Loaded chanenl EPG with id '%s' with display names: '%s'", __FUNCTION__, channelEpg.GetId().c_str(), StringUtils::Join(channelEpg.GetNames(), EPG_STRING_TOKEN_SEPARATOR).c_str()); + m_channelEpgs.emplace_back(channelEpg); + } } if (m_channelEpgs.size() == 0) { - Logger::Log(LEVEL_ERROR, "EPG channels not found."); + Logger::Log(LEVEL_ERROR, "%s - EPG channels not found.", __FUNCTION__); return false; } + else + { + Logger::Log(LEVEL_NOTICE, "%s - Loaded '%d' EPG channels.", __FUNCTION__, m_channelEpgs.size()); + } return true; } @@ -275,6 +283,8 @@ void Epg::LoadEpgEntries(xml_node<>* rootElement, int start, int end) channelEpg->AddEpgEntry(entry); } } + + Logger::Log(LEVEL_NOTICE, "%s - Loaded '%d' EPG entries.", __FUNCTION__, broadcastId); } @@ -447,7 +457,7 @@ bool Epg::LoadGenres() xmlDoc.clear(); if (!m_genreMappings.empty()) - Logger::Log(LEVEL_NOTICE, "%s Loaded %d genres", __FUNCTION__, m_genreMappings.size()); + Logger::Log(LEVEL_NOTICE, "%s - Loaded %d genres", __FUNCTION__, m_genreMappings.size()); return true; } diff --git a/src/iptvsimple/PlaylistLoader.cpp b/src/iptvsimple/PlaylistLoader.cpp index 7a1118f2c..1eefb0fa0 100644 --- a/src/iptvsimple/PlaylistLoader.cpp +++ b/src/iptvsimple/PlaylistLoader.cpp @@ -47,18 +47,18 @@ PlaylistLoader::PlaylistLoader(Channels& channels, ChannelGroups& channelGroups) bool PlaylistLoader::LoadPlayList() { auto started = std::chrono::high_resolution_clock::now(); - Logger::Log(LEVEL_DEBUG, "%s Playlist Load Start", __FUNCTION__); + Logger::Log(LEVEL_DEBUG, "%s - Playlist Load Start", __FUNCTION__); if (m_m3uLocation.empty()) { - Logger::Log(LEVEL_NOTICE, "Playlist file path is not configured. Channels not loaded."); + Logger::Log(LEVEL_ERROR, "%s - Playlist file path is not configured. Channels not loaded.", __FUNCTION__); return false; } std::string playlistContent; if (!FileUtils::GetCachedFileContents(M3U_CACHE_FILENAME, m_m3uLocation, playlistContent, Settings::GetInstance().UseM3UCache())) { - Logger::Log(LEVEL_ERROR, "Unable to load playlist file '%s': file is missing or empty.", m_m3uLocation.c_str()); + Logger::Log(LEVEL_ERROR, "%s - Unable to load playlist cache file '%s': file is missing or empty.", __FUNCTION__, m_m3uLocation.c_str()); return false; } @@ -78,7 +78,7 @@ bool PlaylistLoader::LoadPlayList() line = StringUtils::TrimRight(line, " \t\r\n"); line = StringUtils::TrimLeft(line, " \t"); - Logger::Log(LEVEL_DEBUG, "Read line: '%s'", line.c_str()); + Logger::Log(LEVEL_DEBUG, "%s - M3U line read: '%s'", __FUNCTION__, line.c_str()); if (line.empty()) continue; @@ -98,8 +98,8 @@ bool PlaylistLoader::LoadPlayList() } else { - Logger::Log(LEVEL_ERROR, "URL '%s' missing %s descriptor on line 1, attempting to parse it anyway.", - m_m3uLocation.c_str(), M3U_START_MARKER.c_str()); + Logger::Log(LEVEL_ERROR, "%s - URL '%s' missing %s descriptor on line 1, attempting to parse it anyway.", + __FUNCTION__, m_m3uLocation.c_str(), M3U_START_MARKER.c_str()); } } @@ -134,7 +134,7 @@ bool PlaylistLoader::LoadPlayList() } else if (line[0] != '#') { - Logger::Log(LEVEL_DEBUG, "Found URL: '%s' (current channel name: '%s')", line.c_str(), tmpChannel.GetChannelName().c_str()); + Logger::Log(LEVEL_DEBUG, "%s - Adding channel '%s' with URL: '%s'", __FUNCTION__, tmpChannel.GetChannelName().c_str(), line.c_str()); if (isRealTime) tmpChannel.AddProperty(PVR_STREAM_PROPERTY_ISREALTIMESTREAM, "true"); @@ -158,13 +158,13 @@ bool PlaylistLoader::LoadPlayList() if (m_channels.GetChannelsAmount() == 0) { - Logger::Log(LEVEL_ERROR, "Unable to load channels from file '%s'", m_m3uLocation.c_str()); + Logger::Log(LEVEL_ERROR, "%s - Unable to load channels from file '%s'", __FUNCTION__, m_m3uLocation.c_str()); return false; } m_channels.ApplyChannelLogos(); - Logger::Log(LEVEL_NOTICE, "Loaded %d channels.", m_channels.GetChannelsAmount()); + Logger::Log(LEVEL_NOTICE, "%s - Loaded %d channels.", __FUNCTION__, m_channels.GetChannelsAmount()); return true; } diff --git a/src/iptvsimple/data/ChannelEpg.cpp b/src/iptvsimple/data/ChannelEpg.cpp index 9a682e9c9..26aa38ae5 100644 --- a/src/iptvsimple/data/ChannelEpg.cpp +++ b/src/iptvsimple/data/ChannelEpg.cpp @@ -45,7 +45,7 @@ bool ChannelEpg::UpdateFrom(xml_node<>* channelNode, Channels& channels) } if (!foundChannel) - return false; + return false; // get icon if available xml_node<>* iconNode = channelNode->first_node("icon"); diff --git a/src/iptvsimple/utilities/FileUtils.cpp b/src/iptvsimple/utilities/FileUtils.cpp index 7970fdd72..7bcc2700b 100644 --- a/src/iptvsimple/utilities/FileUtils.cpp +++ b/src/iptvsimple/utilities/FileUtils.cpp @@ -199,7 +199,7 @@ bool FileUtils::CopyFile(const std::string& sourceFile, const std::string& targe { bool copySuccessful = true; - Logger::Log(LEVEL_DEBUG, "%s Copying file: %s, to %s", __FUNCTION__, sourceFile.c_str(), targetFile.c_str()); + Logger::Log(LEVEL_DEBUG, "%s - Copying file: %s, to %s", __FUNCTION__, sourceFile.c_str(), targetFile.c_str()); void* sourceFileHandle = XBMC->OpenFile(sourceFile.c_str(), 0x08); //READ_NO_CACHE @@ -218,13 +218,13 @@ bool FileUtils::CopyFile(const std::string& sourceFile, const std::string& targe } else { - Logger::Log(LEVEL_ERROR, "%s Could not open target file to copy to: %s", __FUNCTION__, targetFile.c_str()); + Logger::Log(LEVEL_ERROR, "%s - Could not open target file to copy to: %s", __FUNCTION__, targetFile.c_str()); copySuccessful = false; } } else { - Logger::Log(LEVEL_ERROR, "%s Could not open source file to copy: %s", __FUNCTION__, sourceFile.c_str()); + Logger::Log(LEVEL_ERROR, "%s - Could not open source file to copy: %s", __FUNCTION__, sourceFile.c_str()); copySuccessful = false; } @@ -258,7 +258,7 @@ bool FileUtils::CopyDirectory(const std::string& sourceDir, const std::string& t } else { - Logger::Log(LEVEL_ERROR, "%s Could not copy directory: %s, to directory: %s", __FUNCTION__, sourceDir.c_str(), targetDir.c_str()); + Logger::Log(LEVEL_ERROR, "%s - Could not copy directory: %s, to directory: %s", __FUNCTION__, sourceDir.c_str(), targetDir.c_str()); copySuccessful = false; } return copySuccessful; From 83dd3335417e4f9488c90bb7732456d2e710bf71 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Fri, 30 Aug 2019 13:34:34 +0100 Subject: [PATCH 17/21] Channel group member order set to M3U order --- src/iptvsimple/ChannelGroups.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/iptvsimple/ChannelGroups.cpp b/src/iptvsimple/ChannelGroups.cpp index fc21f4d87..10cc2f570 100644 --- a/src/iptvsimple/ChannelGroups.cpp +++ b/src/iptvsimple/ChannelGroups.cpp @@ -67,6 +67,8 @@ PVR_ERROR ChannelGroups::GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_C const ChannelGroup* myGroup = FindChannelGroup(group.strGroupName); if (myGroup) { + int channelNumberInGroup = 1; + for (int memberId : myGroup->GetMemberChannelIndexes()) { if (memberId < 0 || memberId >= static_cast(m_channels.GetChannelsAmount())) @@ -77,12 +79,14 @@ PVR_ERROR ChannelGroups::GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_C strncpy(xbmcGroupMember.strGroupName, group.strGroupName, sizeof(xbmcGroupMember.strGroupName) - 1); xbmcGroupMember.iChannelUniqueId = channel.GetUniqueId(); - xbmcGroupMember.iChannelNumber = channel.GetChannelNumber(); + xbmcGroupMember.iChannelNumber = channelNumberInGroup; //Keep the channels in list order as per the M3U Logger::Log(LEVEL_DEBUG, "%s - Transfer channel group '%s' member '%s', ChannelId '%d', ChannelNumberInGroup: '%d'", __FUNCTION__, myGroup->GetGroupName().c_str(), channel.GetChannelName().c_str(), channel.GetUniqueId(), channelNumberInGroup); PVR->TransferChannelGroupMember(handle, &xbmcGroupMember); + + channelNumberInGroup++; } } From 123852e1a3ab1d52f3a580627f38ab451721893b Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Sat, 31 Aug 2019 18:49:52 +0100 Subject: [PATCH 18/21] Fix segfault for compressed EPG files --- src/iptvsimple/Epg.cpp | 12 ++++++------ src/iptvsimple/Epg.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/iptvsimple/Epg.cpp b/src/iptvsimple/Epg.cpp index cd552cd22..c03854461 100644 --- a/src/iptvsimple/Epg.cpp +++ b/src/iptvsimple/Epg.cpp @@ -79,7 +79,8 @@ bool Epg::LoadEPG(time_t start, time_t end) if (GetXMLTVFileWithRetries(data)) { - char* buffer = FillBufferFromXMLTVData(data); + std::string decompressedData; + char* buffer = FillBufferFromXMLTVData(data, decompressedData); if (!buffer) return false; @@ -152,24 +153,23 @@ bool Epg::GetXMLTVFileWithRetries(std::string& data) return true; } -char* Epg::FillBufferFromXMLTVData(std::string& data) +char* Epg::FillBufferFromXMLTVData(std::string& data, std::string& decompressedData) { - std::string decompressed; char* buffer = nullptr; // gzip packed if (data[0] == '\x1F' && data[1] == '\x8B' && data[2] == '\x08') { - if (!FileUtils::GzipInflate(data, decompressed)) + if (!FileUtils::GzipInflate(data, decompressedData)) { Logger::Log(LEVEL_ERROR, "%s - Invalid EPG file '%s': unable to decompress file.", __FUNCTION__, m_xmltvLocation.c_str()); return nullptr; } - buffer = &(decompressed[0]); + buffer = &(decompressedData[0]); } else { - buffer = &(data[0]); + buffer = &(data[0]); } XmltvFileFormat fileFormat = GetXMLTVFileFormat(buffer); diff --git a/src/iptvsimple/Epg.h b/src/iptvsimple/Epg.h index ca88f4edb..139348735 100644 --- a/src/iptvsimple/Epg.h +++ b/src/iptvsimple/Epg.h @@ -60,7 +60,7 @@ namespace iptvsimple bool LoadEPG(time_t iStart, time_t iEnd); bool GetXMLTVFileWithRetries(std::string& data); - char* FillBufferFromXMLTVData(std::string& data); + char* FillBufferFromXMLTVData(std::string& data, std::string& decompressedData); bool LoadChannelEpgs(rapidxml::xml_node<>* rootElement); void LoadEpgEntries(rapidxml::xml_node<>* rootElement, int start, int end); bool LoadGenres(); From 225d50d10c0231d3ea4b9a8556648d13161d4f0d Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Tue, 3 Sep 2019 21:53:00 +0100 Subject: [PATCH 19/21] Add ordering for groups as per PVR API 6.1.0 --- src/iptvsimple/ChannelGroups.cpp | 16 ++++++++++------ src/iptvsimple/Channels.cpp | 5 +++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/iptvsimple/ChannelGroups.cpp b/src/iptvsimple/ChannelGroups.cpp index 10cc2f570..1f1a9eb31 100644 --- a/src/iptvsimple/ChannelGroups.cpp +++ b/src/iptvsimple/ChannelGroups.cpp @@ -67,7 +67,13 @@ PVR_ERROR ChannelGroups::GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_C const ChannelGroup* myGroup = FindChannelGroup(group.strGroupName); if (myGroup) { - int channelNumberInGroup = 1; + // We set a channel order here that applies to this group in kodi-pvr + // This allows the users to use the 'Backend Order' sort option in the left to + // have the same order as the backend (regardles of the channel numbering used) + // + // We don't set a channel number within this group as different channel numbers + // per group are not supported in M3U files + int channelOrder = 1; for (int memberId : myGroup->GetMemberChannelIndexes()) { @@ -79,14 +85,12 @@ PVR_ERROR ChannelGroups::GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_C strncpy(xbmcGroupMember.strGroupName, group.strGroupName, sizeof(xbmcGroupMember.strGroupName) - 1); xbmcGroupMember.iChannelUniqueId = channel.GetUniqueId(); - xbmcGroupMember.iChannelNumber = channelNumberInGroup; //Keep the channels in list order as per the M3U + xbmcGroupMember.iOrder = channelOrder++; // Keep the channels in list order as per the M3U - Logger::Log(LEVEL_DEBUG, "%s - Transfer channel group '%s' member '%s', ChannelId '%d', ChannelNumberInGroup: '%d'", __FUNCTION__, - myGroup->GetGroupName().c_str(), channel.GetChannelName().c_str(), channel.GetUniqueId(), channelNumberInGroup); + Logger::Log(LEVEL_DEBUG, "%s - Transfer channel group '%s' member '%s', ChannelId '%d', ChannelOrder: '%d'", __FUNCTION__, + myGroup->GetGroupName().c_str(), channel.GetChannelName().c_str(), channel.GetUniqueId(), channelOrder); PVR->TransferChannelGroupMember(handle, &xbmcGroupMember); - - channelNumberInGroup++; } } diff --git a/src/iptvsimple/Channels.cpp b/src/iptvsimple/Channels.cpp index 15e31cbbf..209e6df60 100644 --- a/src/iptvsimple/Channels.cpp +++ b/src/iptvsimple/Channels.cpp @@ -54,6 +54,10 @@ int Channels::GetChannelsAmount() const void Channels::GetChannels(std::vector& kodiChannels, bool radio) const { + // We set a channel order here that applies to the 'Any channels' group in kodi-pvr + // This allows the users to use the 'Backend Order' sort option in the left to + // have the same order as the backend (regardles of the channel numbering used) + int channelOrder = 1; for (const auto& channel : m_channels) { if (channel.IsRadio() == radio) @@ -63,6 +67,7 @@ void Channels::GetChannels(std::vector& kodiChannels, bool radio) c PVR_CHANNEL kodiChannel = {0}; channel.UpdateTo(kodiChannel); + kodiChannel.iOrder = channelOrder++; // Keep the channels in list order as per the M3U kodiChannels.emplace_back(kodiChannel); } From 6dad75f6f6e92642dc8afb2f40ded2892828d573 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Wed, 4 Sep 2019 13:27:14 +0100 Subject: [PATCH 20/21] refactoring --- src/client.cpp | 13 ------------- src/iptvsimple/Epg.cpp | 13 +++---------- src/iptvsimple/Settings.cpp | 14 ++++---------- src/iptvsimple/Settings.h | 20 ++++++++++---------- src/iptvsimple/data/EpgEntry.cpp | 2 +- src/iptvsimple/utilities/FileUtils.cpp | 5 +++++ src/iptvsimple/utilities/FileUtils.h | 1 + src/iptvsimple/utilities/XMLUtils.h | 6 ++---- 8 files changed, 26 insertions(+), 48 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index e91212a16..35d8ad824 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -36,16 +36,6 @@ using namespace iptvsimple; using namespace iptvsimple::data; using namespace iptvsimple::utilities; -#ifdef TARGET_WINDOWS -#define snprintf _snprintf -#ifdef CreateDirectory -#undef CreateDirectory -#endif -#ifdef DeleteFile -#undef DeleteFile -#endif -#endif - bool m_created = false; ADDON_STATUS m_currentStatus = ADDON_STATUS_UNKNOWN; PVRIptvData* m_data = nullptr; @@ -118,9 +108,6 @@ ADDON_STATUS ADDON_Create(void* hdl, void* props) const std::string userPath = pvrprops->strUserPath; const std::string clientPath = pvrprops->strClientPath; - if (!XBMC->DirectoryExists(settings.GetUserPath().c_str())) - XBMC->CreateDirectory(settings.GetUserPath().c_str()); - settings.ReadFromAddon(userPath, clientPath); m_data = new PVRIptvData; diff --git a/src/iptvsimple/Epg.cpp b/src/iptvsimple/Epg.cpp index c03854461..19afe2929 100644 --- a/src/iptvsimple/Epg.cpp +++ b/src/iptvsimple/Epg.cpp @@ -40,12 +40,6 @@ using namespace iptvsimple::data; using namespace iptvsimple::utilities; using namespace rapidxml; -#ifdef TARGET_WINDOWS -#ifdef DeleteFile -#undef DeleteFile -#endif -#endif - Epg::Epg(Channels& channels) : m_channels(channels), m_xmltvLocation(Settings::GetInstance().GetEpgLocation()), m_epgTimeShift(Settings::GetInstance().GetEpgTimeshiftSecs()), m_tsOverride(Settings::GetInstance().GetTsOverride()), m_lastStart(0), m_lastEnd(0) @@ -216,8 +210,7 @@ bool Epg::LoadChannelEpgs(xml_node<>* rootElement) m_channelEpgs.clear(); - xml_node<>* channelNode = nullptr; - for (channelNode = rootElement->first_node("channel"); channelNode; channelNode = channelNode->next_sibling("channel")) + for (xml_node<>* channelNode = rootElement->first_node("channel"); channelNode; channelNode = channelNode->next_sibling("channel")) { ChannelEpg channelEpg; @@ -473,6 +466,6 @@ void Epg::MoveOldGenresXMLFileToNewLocation() else FileUtils::CopyFile(FileUtils::GetResourceDataPath() + "/" + GENRES_MAP_FILENAME, DEFAULT_GENRE_TEXT_MAP_FILE); - XBMC->DeleteFile((ADDON_DATA_BASE_DIR + "/" + GENRES_MAP_FILENAME).c_str()); - XBMC->DeleteFile((FileUtils::GetSystemAddonPath() + "/" + GENRES_MAP_FILENAME).c_str()); + FileUtils::DeleteFile(ADDON_DATA_BASE_DIR + "/" + GENRES_MAP_FILENAME.c_str()); + FileUtils::DeleteFile(FileUtils::GetSystemAddonPath() + "/" + GENRES_MAP_FILENAME.c_str()); } diff --git a/src/iptvsimple/Settings.cpp b/src/iptvsimple/Settings.cpp index 2a1e7d70e..4e3065e24 100644 --- a/src/iptvsimple/Settings.cpp +++ b/src/iptvsimple/Settings.cpp @@ -29,12 +29,6 @@ using namespace ADDON; using namespace iptvsimple; using namespace iptvsimple::utilities; -#ifdef TARGET_WINDOWS -#ifdef DeleteFile -#undef DeleteFile -#endif -#endif - /*************************************************************************** * PVR settings **************************************************************************/ @@ -99,12 +93,12 @@ ADDON_STATUS Settings::SetValue(const std::string& settingName, const void* sett // reset cache and restart addon std::string strFile = FileUtils::GetUserDataAddonFilePath(M3U_CACHE_FILENAME); - if (XBMC->FileExists(strFile.c_str(), false)) - XBMC->DeleteFile(strFile.c_str()); + if (FileUtils::FileExists(strFile.c_str())) + FileUtils::DeleteFile(strFile); strFile = FileUtils::GetUserDataAddonFilePath(XMLTV_CACHE_FILENAME); - if (XBMC->FileExists(strFile.c_str(), false)) - XBMC->DeleteFile(strFile.c_str()); + if (FileUtils::FileExists(strFile.c_str())) + FileUtils::DeleteFile(strFile); // M3U if (settingName == "m3uPathType") diff --git a/src/iptvsimple/Settings.h b/src/iptvsimple/Settings.h index 2961da0c3..0c6a2485d 100644 --- a/src/iptvsimple/Settings.h +++ b/src/iptvsimple/Settings.h @@ -131,31 +131,31 @@ namespace iptvsimple return defaultReturnValue; } - std::string m_userPath = ""; - std::string m_clientPath = ""; + std::string m_userPath; + std::string m_clientPath; PathType m_m3uPathType = PathType::REMOTE_PATH; - std::string m_m3uPath = ""; - std::string m_m3uUrl = ""; + std::string m_m3uPath; + std::string m_m3uUrl; bool m_cacheM3U = false; int m_startChannelNumber = 1; bool m_numberChannelsByM3uOrderOnly = false; PathType m_epgPathType = PathType::REMOTE_PATH; - std::string m_epgPath = ""; - std::string m_epgUrl = ""; + std::string m_epgPath; + std::string m_epgUrl; bool m_cacheEPG = false; int m_epgTimeShiftMins = 0; bool m_tsOverride = true; bool m_useEpgGenreTextWhenMapping = false; PathType m_genresPathType = PathType::LOCAL_PATH; - std::string m_genresPath = ""; - std::string m_genresUrl = ""; + std::string m_genresPath; + std::string m_genresUrl; PathType m_logoPathType = PathType::REMOTE_PATH; - std::string m_logoPath = ""; - std::string m_logoBaseUrl = ""; + std::string m_logoPath; + std::string m_logoBaseUrl; EpgLogosMode m_epgLogosMode = EpgLogosMode::IGNORE_XMLTV; }; } //namespace iptvsimple diff --git a/src/iptvsimple/data/EpgEntry.cpp b/src/iptvsimple/data/EpgEntry.cpp index 93aeab64a..6ad1124a0 100644 --- a/src/iptvsimple/data/EpgEntry.cpp +++ b/src/iptvsimple/data/EpgEntry.cpp @@ -234,7 +234,7 @@ bool EpgEntry::UpdateFrom(rapidxml::xml_node<>* channelNode, const std::string& if (!episodeNumbersList.empty()) ParseEpisodeNumberInfo(episodeNumbersList); - xml_node<> *creditsNode = channelNode->first_node("credits"); + xml_node<>* creditsNode = channelNode->first_node("credits"); if (creditsNode) { m_cast = GetJoinedNodeValues(creditsNode, "actor"); diff --git a/src/iptvsimple/utilities/FileUtils.cpp b/src/iptvsimple/utilities/FileUtils.cpp index 7bcc2700b..3c0d543bb 100644 --- a/src/iptvsimple/utilities/FileUtils.cpp +++ b/src/iptvsimple/utilities/FileUtils.cpp @@ -195,6 +195,11 @@ bool FileUtils::FileExists(const std::string& file) return XBMC->FileExists(file.c_str(), false); } +bool FileUtils::DeleteFile(const std::string& file) +{ + return XBMC->DeleteFile(file.c_str()); +} + bool FileUtils::CopyFile(const std::string& sourceFile, const std::string& targetFile) { bool copySuccessful = true; diff --git a/src/iptvsimple/utilities/FileUtils.h b/src/iptvsimple/utilities/FileUtils.h index 54df40313..ab09f158d 100644 --- a/src/iptvsimple/utilities/FileUtils.h +++ b/src/iptvsimple/utilities/FileUtils.h @@ -39,6 +39,7 @@ namespace iptvsimple static int GetCachedFileContents(const std::string& cachedName, const std::string& filePath, std::string& content, const bool useCache = false); static bool FileExists(const std::string& file); + static bool DeleteFile(const std::string& file); static bool CopyFile(const std::string& sourceFile, const std::string& targetFile); static bool CopyDirectory(const std::string& sourceDir, const std::string& targetDir, bool recursiveCopy); static std::string GetSystemAddonPath(); diff --git a/src/iptvsimple/utilities/XMLUtils.h b/src/iptvsimple/utilities/XMLUtils.h index 020ea4210..9409e122a 100644 --- a/src/iptvsimple/utilities/XMLUtils.h +++ b/src/iptvsimple/utilities/XMLUtils.h @@ -40,8 +40,7 @@ inline std::string GetJoinedNodeValues(const rapidxml::xml_node* rootNode, c { std::string stringValue; - rapidxml::xml_node* childNode = nullptr; - for (childNode = rootNode->first_node(tag); childNode; childNode = childNode->next_sibling(tag)) + for (rapidxml::xml_node* childNode = rootNode->first_node(tag); childNode; childNode = childNode->next_sibling(tag)) { if (childNode) { @@ -59,8 +58,7 @@ inline std::vector GetNodeValuesList(const rapidxml::xml_node* { std::vector stringValues; - rapidxml::xml_node* childNode = nullptr; - for(childNode = rootNode->first_node(tag); childNode; childNode = childNode->next_sibling(tag)) + for(rapidxml::xml_node* childNode = rootNode->first_node(tag); childNode; childNode = childNode->next_sibling(tag)) { if (childNode) stringValues.emplace_back(childNode->value()); From e419cb2fa5ef6f165ace3d6bebacd8a0337ce2a1 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Fri, 30 Aug 2019 12:24:28 +0100 Subject: [PATCH 21/21] changelog and version --- pvr.iptvsimple/addon.xml.in | 59 ++++++++++++++++++++++++++++++++++-- pvr.iptvsimple/changelog.txt | 19 ++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/pvr.iptvsimple/addon.xml.in b/pvr.iptvsimple/addon.xml.in index fa01874e9..d9113c730 100644 --- a/pvr.iptvsimple/addon.xml.in +++ b/pvr.iptvsimple/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ @@ -19,7 +19,7 @@ Kodi PVR Addon für IPTV Unterstützung. https://github.com/afedchin/Kodi-addon-iptvsimple/wiki/IPTV-Simple-Home Πρόσθετο του Kodi για PVR, με υποστήριξη IPTV. Για λεπτομέρειες επισκεφθείτε το: https://github.com/afedchin/Kodi-addon-iptvsimple/wiki/IPTV-Simple-Home Kodi PVR addon for IPTV support. https://github.com/afedchin/Kodi-addon-iptvsimple/wiki/IPTV-Simple-Home - Kodi PVR addon for IPTV support. https://github.com/afedchin/Kodi-addon-iptvsimple/wiki/IPTV-Simple-Home + Kodi PVR addon for IPTV support. Kodi PVR addon for IPTV support. https://github.com/afedchin/Kodi-addon-iptvsimple/wiki/IPTV-Simple-Home Kodi PVR addon for IPTV support. https://github.com/afedchin/Kodi-addon-iptvsimple/wiki/IPTV-Simple-Home Addon Kodi PVR para soporte IPTV. https://github.com/afedchin/Kodi-addon-iptvsimple/wiki/IPTV-Simple-Home @@ -69,7 +69,7 @@ IPTV Simple PVR Client unterstützt m3u Wiedergabelisten, Streaming von Live TV für Multicast/Unicast Quellen, Radiosender und EPG. Απλός Πελάτης PVR του IPTV, με υποστήριξη λιστών αναπαραγωγής m3u, αναπαραγωγή ροών Live TV για πηγές πολλαπλής/μοναδικής διανομής, ακρόαση ραδιοφωνικών καναλιών και EPG. IPTV Simple PVR Client support m3u playlists, streaming of Live TV for multicast/unicast sources, listening to Radio channels and EPG. - IPTV Simple PVR Client support m3u playlists, streaming of Live TV for multicast/unicast sources, listening to Radio channels and EPG. + IPTV Simple PVR Client support m3u playlists, streaming of Live TV for multicast/unicast sources, listening to Radio channels and EPG. For documentation visit: https://github.com/kodi-pvr/pvr.iptvsimple/blob/master/README.md IPTV Simple PVR Client support m3u playlists, streaming of Live TV for multicast/unicast sources, listening to Radio channels and EPG. IPTV Simple PVR Client support m3u playlists, streaming of Live TV for multicast/unicast sources, listening to Radio channels and EPG. Cliente simple PVR IPTV. Reproduce de TV en Vivo multicast/unicast y listas m3u8 . reproduce tambien canales de radio y GEP @@ -158,5 +158,58 @@ 这是不稳定版的软件!作者不对录像失败、错误定时造成时间浪费或其它不良影响负责。 這是測試中的軟體!原創作者無法針對以下情況負責:包括播放失敗,不正確的電子節目表,多餘的時數,或任何不可預期的不良影響。 @PLATFORM@ + +v4.5.0 +- Fixed: Support full timeshift range of -12 to +14 hours +- Fixed: Some providers incorrectly use tvg-ID instead of tvg-id +- Fixed: Support multiple display-names and case insensitive tvg-id is always first, next tvg-name and then channel name find order +- Added: support episode-num for both xmltv_ns and onscreen systems in epg entry +- Added: Update readme for supported M3U and XMLTV formats and genres +- Added: support star rating in epg entry +- Added: support firstAired and year in epg entry +- Added: Update OSX build script +- Added: support multiple actor/director/writers elements in epg entry +- Added: URLEncode and append .png ext for remote logos built from channel name +- Added: Support for mapping by genre hex ID and added example files and settings +- Added: Timing for Playlist and EPG Load +- Added: Option to number channels by M3U order only +- Update: Debug logging +- Added: Channel group member order set to M3U order +- Fixed: Fix segfault for compressed EPG files +- Added: Add ordering for groups as per PVR API 6.1.0 + +v4.4.0 +- Update: Recompile for 6.1.0 PVR Addon API compatibility + +v4.3.0 +- Added: Auto reload channels, groups and EPG on settings change +- Added: Support for #EXTGRP tag in M3U file +- Fixed: Channel with no groups inherit previous channels groups +- Added: update new file kodi headers to start with kodi/ + +v4.2.2 +- Update build system version +- Change header include way +- Add AppVeyor for Windows related build tests + +v4.2.1 +- Fix nullptr initialisation + +v4.2.0 +- Add support for sub-title/actor/director/writer in XML + +v4.1.0 +- Support EXTVCOPT in m3u8 +- Build helper script for OSX + +v4.0.2 +- Fix wrong EPG times due to DST on Windows + +v4.0.1 +- Remove channels loaded notification + +v4.0.0 +- Update to PVR addon API v6.0.0 + diff --git a/pvr.iptvsimple/changelog.txt b/pvr.iptvsimple/changelog.txt index a34f384d5..f5d9d8e4c 100644 --- a/pvr.iptvsimple/changelog.txt +++ b/pvr.iptvsimple/changelog.txt @@ -1,3 +1,22 @@ +v4.5.0 +- Fixed: Support full timeshift range of -12 to +14 hours +- Fixed: Some providers incorrectly use tvg-ID instead of tvg-id +- Fixed: Support multiple display-names and case insensitive tvg-id is always first, next tvg-name and then channel name find order +- Added: support episode-num for both xmltv_ns and onscreen systems in epg entry +- Added: Update readme for supported M3U and XMLTV formats and genres +- Added: support star rating in epg entry +- Added: support firstAired and year in epg entry +- Added: Update OSX build script +- Added: support multiple actor/director/writers elements in epg entry +- Added: URLEncode and append .png ext for remote logos built from channel name +- Added: Support for mapping by genre hex ID and added example files and settings +- Added: Timing for Playlist and EPG Load +- Added: Option to number channels by M3U order only +- Update: Debug logging +- Added: Channel group member order set to M3U order +- Fixed: Fix segfault for compressed EPG files +- Added: Add ordering for groups as per PVR API 6.1.0 + v4.4.0 - Update: Recompile for 6.1.0 PVR Addon API compatibility