diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0cc65450..6dd5abc3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -28,6 +28,7 @@ set(NEXTPVR_SOURCES src/addon.cpp
src/buffers/ClientTimeshift.cpp
src/buffers/RecordingBuffer.cpp
src/buffers/CircularBuffer.cpp
+ src/utilities/GenreMapper.cpp
src/utilities/SettingsMigration.cpp
src/buffers/Seeker.cpp)
@@ -50,6 +51,7 @@ set(NEXTPVR_HEADERS src/addon.h
src/buffers/RecordingBuffer.h
src/buffers/CircularBuffer.h
src/buffers/Seeker.h
+ src/utilities/GenreMapper.h
src/utilities/SettingsMigration.h
src/utilities/XMLUtils.h)
diff --git a/pvr.nextpvr/resources/genre-mapping.xml b/pvr.nextpvr/resources/genre-mapping.xml
new file mode 100644
index 00000000..57f7841f
--- /dev/null
+++ b/pvr.nextpvr/resources/genre-mapping.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/EPG.cpp b/src/EPG.cpp
index 499b2940..a56b8040 100644
--- a/src/EPG.cpp
+++ b/src/EPG.cpp
@@ -19,10 +19,11 @@ using namespace NextPVR::utilities;
/************************************************************/
/** EPG handling */
-EPG::EPG(const std::shared_ptr& settings, Request& request, Recordings& recordings, Channels& channels) :
+EPG::EPG(const std::shared_ptr& settings, Request& request, Recordings& recordings, Channels& channels,GenreMapper& genreMapper) :
m_settings(settings),
m_request(request),
m_recordings(recordings),
+ m_genreMapper(genreMapper),
m_channels(channels)
{
}
@@ -51,7 +52,7 @@ PVR_ERROR EPG::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi::
if (m_request.DoMethodRequest(request, doc) == tinyxml2::XML_SUCCESS)
{
tinyxml2::XMLNode* listingsNode = doc.RootElement()->FirstChildElement("listings");
- for (tinyxml2::XMLNode* pListingNode = listingsNode->FirstChildElement("l"); pListingNode; pListingNode = pListingNode->NextSiblingElement())
+ for (const tinyxml2::XMLNode* pListingNode = listingsNode->FirstChildElement("l"); pListingNode; pListingNode = pListingNode->NextSiblingElement())
{
kodi::addon::PVREPGTag broadcast;
std::string title;
@@ -82,6 +83,14 @@ PVR_ERROR EPG::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi::
broadcast.SetStartTime(stol(startTime));
broadcast.SetUniqueBroadcastId(stoi(endTime));
broadcast.SetEndTime(stol(endTime));
+
+ static std::regex yearRegex("^(.+[12]\\d{3})\\n");
+ std::smatch base_match;
+ if (std::regex_search(description, base_match, yearRegex))
+ {
+ kodi::tools::StringUtils::Replace(description, base_match[0].str(), base_match[1].str() + " ");
+ }
+
broadcast.SetPlot(description);
std::string artworkPath;
@@ -111,23 +120,13 @@ PVR_ERROR EPG::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi::
broadcast.SetGenreSubType(XMLUtils::GetIntValue(pListingNode, "genre_subtype"));
}
- std::string allGenres;
- if (XMLUtils::GetAdditiveString(pListingNode->FirstChildElement("genres"), "genre", EPG_STRING_TOKEN_SEPARATOR, allGenres, true))
- {
- if (allGenres.find(EPG_STRING_TOKEN_SEPARATOR) != std::string::npos)
- {
- if (broadcast.GetGenreType() != EPG_GENRE_USE_STRING)
- {
- broadcast.SetGenreSubType(EPG_GENRE_USE_STRING);
- }
- broadcast.SetGenreDescription(allGenres);
- }
- else if (m_settings->m_genreString && broadcast.GetGenreSubType() != EPG_GENRE_USE_STRING)
- {
- broadcast.SetGenreDescription(allGenres);
- broadcast.SetGenreSubType(EPG_GENRE_USE_STRING);
- }
+ NextPVR::GenreBlock genreBlock = { sGenre, broadcast.GetGenreType(), EPG_EVENT_CONTENTMASK_UNDEFINED };
+ if (m_genreMapper.ParseAllGenres(pListingNode, genreBlock))
+ {
+ broadcast.SetGenreDescription(genreBlock.description);
+ broadcast.SetGenreType(genreBlock.genreType);
+ broadcast.SetGenreSubType(genreBlock.genreSubType);
}
int season{EPG_TAG_INVALID_SERIES_EPISODE};
diff --git a/src/EPG.h b/src/EPG.h
index 3c38ec23..2b1d6215 100644
--- a/src/EPG.h
+++ b/src/EPG.h
@@ -12,6 +12,7 @@
#include
#include "Channels.h"
#include "Recordings.h"
+#include "utilities/GenreMapper.h"
namespace NextPVR
{
@@ -19,7 +20,7 @@ namespace NextPVR
class ATTR_DLL_LOCAL EPG
{
public:
- EPG(const std::shared_ptr& settings, Request& request, Recordings& recordings, Channels& channels);
+ EPG(const std::shared_ptr& settings, Request& request, Recordings& recordings, Channels& channels, GenreMapper& genreMapper);
PVR_ERROR GetEPGForChannel(int channelUid, time_t start, time_t end, kodi::addon::PVREPGTagsResultSet& results);
private:
@@ -31,5 +32,6 @@ namespace NextPVR
Request& m_request;
Recordings& m_recordings;
Channels& m_channels;
+ GenreMapper& m_genreMapper;
};
} // namespace NextPVR
diff --git a/src/Recordings.cpp b/src/Recordings.cpp
index d7a27da3..5e5ff164 100644
--- a/src/Recordings.cpp
+++ b/src/Recordings.cpp
@@ -22,11 +22,13 @@ using namespace NextPVR::utilities;
/************************************************************/
/** Record handling **/
-Recordings::Recordings(const std::shared_ptr& settings, Request& request, Timers& timers, Channels& channels, cPVRClientNextPVR& pvrclient) :
+Recordings::Recordings(const std::shared_ptr& settings, Request& request, Timers& timers, Channels& channels,
+ GenreMapper& genreMapper, cPVRClientNextPVR& pvrclient) :
m_settings(settings),
m_request(request),
m_timers(timers),
m_channels(channels),
+ m_genreMapper(genreMapper),
m_pvrclient(pvrclient)
{
@@ -441,11 +443,13 @@ bool Recordings::UpdatePvrRecording(const tinyxml2::XMLNode* pRecordingNode, kod
tag.SetFanartPath(artworkPath + "&prefer=fanart");
tag.SetThumbnailPath(artworkPath + "&prefer=poster");
}
- if (XMLUtils::GetAdditiveString(pRecordingNode->FirstChildElement("genres"), "genre", EPG_STRING_TOKEN_SEPARATOR, buffer, true))
+
+ NextPVR::GenreBlock genreBlock = { "", EPG_EVENT_CONTENTMASK_UNDEFINED, EPG_EVENT_CONTENTMASK_UNDEFINED };
+ if (m_genreMapper.ParseAllGenres(pRecordingNode, genreBlock))
{
- tag.SetGenreType(EPG_GENRE_USE_STRING);
- tag.SetGenreSubType(0);
- tag.SetGenreDescription(buffer);
+ tag.SetGenreDescription(genreBlock.description);
+ tag.SetGenreType(genreBlock.genreType);
+ tag.SetGenreSubType(genreBlock.genreSubType);
}
std::string significance;
diff --git a/src/Recordings.h b/src/Recordings.h
index ce558c75..1bc8e771 100644
--- a/src/Recordings.h
+++ b/src/Recordings.h
@@ -10,6 +10,7 @@
#include "BackendRequest.h"
#include "Timers.h"
+#include "utilities/GenreMapper.h"
#include
@@ -21,7 +22,8 @@ namespace NextPVR
{
public:
- Recordings(const std::shared_ptr& settings, Request& request, Timers& timers, Channels& channels, cPVRClientNextPVR& pvrclent);
+ Recordings(const std::shared_ptr& settings, Request& request, Timers& timers, Channels& channels,
+ GenreMapper& genreMapper, cPVRClientNextPVR& pvrclent);
/* Recording handling **/
PVR_ERROR GetRecordingsAmount(bool deleted, int& amount);
PVR_ERROR GetDriveSpace(uint64_t& total, uint64_t& used);
@@ -48,6 +50,7 @@ namespace NextPVR
Request& m_request;
Timers& m_timers;
Channels& m_channels;
+ GenreMapper& m_genreMapper;
cPVRClientNextPVR& m_pvrclient;
// update these at end of counting loop can be called during action
diff --git a/src/pvrclient-nextpvr.cpp b/src/pvrclient-nextpvr.cpp
index d5542294..788365db 100644
--- a/src/pvrclient-nextpvr.cpp
+++ b/src/pvrclient-nextpvr.cpp
@@ -91,9 +91,10 @@ cPVRClientNextPVR::cPVRClientNextPVR(const CNextPVRAddon& base, const kodi::addo
m_request(m_settings),
m_channels(m_settings, m_request),
m_timers(m_settings, m_request, m_channels, *this),
- m_recordings(m_settings, m_request, m_timers, m_channels, *this),
+ m_recordings(m_settings, m_request, m_timers, m_channels,m_genreMapper, *this),
m_menuhook(m_settings, m_recordings, m_channels, *this),
- m_epg(m_settings, m_request, m_recordings, m_channels)
+ m_genreMapper(m_settings),
+ m_epg(m_settings, m_request, m_recordings, m_channels, m_genreMapper)
{
if (!kodi::vfs::DirectoryExists(m_settings->m_instanceDirectory))
{
diff --git a/src/pvrclient-nextpvr.h b/src/pvrclient-nextpvr.h
index 02aa0284..6b140ca1 100644
--- a/src/pvrclient-nextpvr.h
+++ b/src/pvrclient-nextpvr.h
@@ -21,6 +21,7 @@
#include "Recordings.h"
#include "InstanceSettings.h"
#include "Timers.h"
+#include "utilities/GenreMapper.h"
#include "buffers/ClientTimeshift.h"
#include "buffers/DummyBuffer.h"
#include "buffers/RecordingBuffer.h"
@@ -156,6 +157,7 @@ class ATTR_DLL_LOCAL cPVRClientNextPVR : public kodi::addon::CInstancePVRClient
EPG m_epg;
MenuHook m_menuhook;
Recordings m_recordings;
+ GenreMapper m_genreMapper;
Timers m_timers;
void SetConnectionState(PVR_CONNECTION_STATE state, std::string displayMessage = "");
diff --git a/src/utilities/GenreMapper.cpp b/src/utilities/GenreMapper.cpp
new file mode 100644
index 00000000..6a054ef0
--- /dev/null
+++ b/src/utilities/GenreMapper.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2005-2024 Team Kodi (https://kodi.tv)
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSE.md for more information.
+ */
+
+
+#include
+#include "GenreMapper.h"
+#include "XMLUtils.h"
+#include "tinyxml2.h"
+
+#include
+
+using namespace NextPVR;
+using namespace NextPVR::utilities;
+
+GenreMapper::GenreMapper(const std::shared_ptr& settings) : m_settings(settings)
+{
+ LoadGenreTextMappingFiles();
+}
+
+GenreMapper::~GenreMapper() {}
+
+
+bool GenreMapper::IsEnabled()
+{
+ return !m_settings->m_genreString;
+}
+
+int GenreMapper::GetGenreTypeFromCombined(int combinedGenreType)
+{
+ return combinedGenreType & 0xF0;
+}
+
+int GenreMapper::GetGenreSubTypeFromCombined(int combinedGenreType)
+{
+ return combinedGenreType & 0x0F;
+}
+
+
+int GenreMapper::LookupGenreValueInMaps(const std::string& genreText)
+{
+ int genreType = EPG_EVENT_CONTENTMASK_UNDEFINED;
+
+ auto genreMapSearch = m_genreMap.find(genreText);
+ if (genreMapSearch != m_genreMap.end())
+ {
+ genreType = genreMapSearch->second;
+ }
+ return genreType;
+}
+
+void GenreMapper::LoadGenreTextMappingFiles()
+{
+ if (!LoadTextToIdGenreFile(GENRE_KODI_DVB_FILEPATH, m_genreMap))
+ kodi::Log(ADDON_LOG_ERROR, "%s Could not load text to genre id file: %s", __func__, GENRE_KODI_DVB_FILEPATH.c_str());
+
+}
+
+bool GenreMapper::ParseAllGenres(const tinyxml2::XMLNode* node, GenreBlock& genreBlock)
+{
+ std::string allGenres;
+ if (XMLUtils::GetAdditiveString(node->FirstChildElement("genres"), "genre", EPG_STRING_TOKEN_SEPARATOR, allGenres, true))
+ {
+ if (allGenres.find(EPG_STRING_TOKEN_SEPARATOR) != std::string::npos)
+ {
+ if (IsEnabled())
+ {
+ std::vector genreCodes = kodi::tools::StringUtils::Split(allGenres, EPG_STRING_TOKEN_SEPARATOR);
+ if (genreCodes.size() == 2)
+ {
+ if (genreBlock.genreType == EPG_EVENT_CONTENTMASK_UNDEFINED)
+ genreBlock.genreType = GetGenreType(genreCodes[0]);
+
+ if (genreCodes[0] == "Show / Game show")
+ genreBlock.genreType = 48;
+
+ if (genreBlock.genreType == GetGenreType(genreCodes[0]))
+ {
+ if (genreBlock.genreType == GetGenreType(genreCodes[1]))
+ genreBlock.genreSubType = GetGenreSubType(genreCodes[1]);
+ }
+ }
+ }
+ if (genreBlock.genreSubType == EPG_EVENT_CONTENTMASK_UNDEFINED)
+ {
+ if (genreBlock.genreType != EPG_GENRE_USE_STRING)
+ {
+ genreBlock.genreType = EPG_GENRE_USE_STRING;
+ }
+ genreBlock.description = allGenres;
+ }
+ }
+ else if (!IsEnabled() && genreBlock.genreSubType != EPG_GENRE_USE_STRING)
+ {
+ genreBlock.description = allGenres;
+ genreBlock.genreType = EPG_GENRE_USE_STRING;
+ }
+
+ return true;
+ }
+ return false;
+}
+
+bool GenreMapper::LoadTextToIdGenreFile(const std::string& xmlFile, std::map& map)
+{
+ map.clear();
+
+ if (!kodi::vfs::FileExists(xmlFile.c_str()))
+ {
+ kodi::Log(ADDON_LOG_ERROR, "%s No XML file found: %s", __func__, xmlFile.c_str());
+ return false;
+ }
+
+ kodi::Log(ADDON_LOG_DEBUG, "%s Loading XML File: %s", __func__, xmlFile.c_str());
+
+ std::string fileContents;
+ kodi::vfs::CFile loadXml;
+ if (loadXml.OpenFile(xmlFile, ADDON_READ_NO_CACHE))
+ {
+ char buffer[1025] = { 0 };
+ int count;
+ while ((count = loadXml.Read(buffer, 1024)))
+ {
+ fileContents.append(buffer, count);
+ }
+ }
+ loadXml.Close();
+
+ tinyxml2::XMLDocument xmlDoc;
+
+ if (xmlDoc.Parse(fileContents.c_str()) != tinyxml2::XML_SUCCESS)
+ {
+ kodi::Log(ADDON_LOG_ERROR, "%s Unable to parse XML: %s at line %d", __func__, xmlDoc.ErrorStr(), xmlDoc.ErrorLineNum());
+ return false;
+ }
+
+ tinyxml2::XMLHandle hDoc(&xmlDoc);
+
+ tinyxml2::XMLElement* pNode = hDoc.FirstChildElement("translations").ToElement();
+
+ if (!pNode)
+ {
+ kodi::Log(ADDON_LOG_ERROR, "%s Could not find element", __func__);
+ return false;
+ }
+
+ pNode = pNode->FirstChildElement("genre");
+
+ if (!pNode)
+ {
+ kodi::Log(ADDON_LOG_ERROR, "%s Could not find element", __func__);
+ return false;
+ }
+
+ for (; pNode != nullptr; pNode = pNode->NextSiblingElement("genre"))
+ {
+ std::string textMapping;
+
+ textMapping = pNode->Attribute("name");
+ int type = atoi(pNode->Attribute("type"));
+ int subtype = atoi(pNode->Attribute("subtype"));
+ if (!textMapping.empty())
+ {
+ map.insert({ textMapping, type | subtype });
+ kodi::Log(ADDON_LOG_DEBUG, "%s Read Text Mapping text=%s, targetId=%#02X", __func__, textMapping.c_str(), type|subtype);
+ }
+ }
+ return true;
+}
+
+int GenreMapper::GetGenreType(std::string code)
+{
+ return GetGenreTypeFromCombined(LookupGenreValueInMaps(code));
+};
+
+int GenreMapper::GetGenreSubType(std::string code)
+{
+ return GetGenreSubTypeFromCombined(LookupGenreValueInMaps(code));
+};
\ No newline at end of file
diff --git a/src/utilities/GenreMapper.h b/src/utilities/GenreMapper.h
new file mode 100644
index 00000000..1a70df98
--- /dev/null
+++ b/src/utilities/GenreMapper.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2005-2024 Team Kodi (https://kodi.tv)
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSE.md for more information.
+ */
+
+#pragma once
+
+#include
+#include
+#include "../InstanceSettings.h"
+#include