From 02541a4b1960b75bc314572973f8e55b8709878c Mon Sep 17 00:00:00 2001 From: Martin Vallevand Date: Thu, 4 Jul 2024 15:39:22 -0400 Subject: [PATCH 1/2] Timeshift change Start timeshift in real time mode. Low bit radio was taking to long to buffer. --- src/buffers/ClientTimeshift.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/buffers/ClientTimeshift.h b/src/buffers/ClientTimeshift.h index eff11a4c..69322745 100644 --- a/src/buffers/ClientTimeshift.h +++ b/src/buffers/ClientTimeshift.h @@ -92,5 +92,10 @@ namespace timeshift { } virtual PVR_ERROR GetStreamTimes(kodi::addon::PVRStreamTimes& times) override; + virtual bool IsRealTimeStream() const override + { + return time(nullptr) - m_streamStart < 10 + m_prebuffer; + } + }; } From c439b3519cde17352e808361d4e3d9c61fd9f3c4 Mon Sep 17 00:00:00 2001 From: Martin Vallevand Date: Sat, 6 Jul 2024 21:19:10 -0400 Subject: [PATCH 2/2] Series and episode clean up Remove duplicated series information from recordings, timer and EPG. Parse episode display information from the description. --- pvr.nextpvr/addon.xml.in | 2 +- pvr.nextpvr/changelog.txt | 5 ++++ src/EPG.cpp | 63 ++++++++++++++++++++++++++++++++++----- src/Recordings.cpp | 47 +++++++++++++++++++---------- 4 files changed, 93 insertions(+), 24 deletions(-) diff --git a/pvr.nextpvr/addon.xml.in b/pvr.nextpvr/addon.xml.in index 4d5c3d2a..a259390c 100644 --- a/pvr.nextpvr/addon.xml.in +++ b/pvr.nextpvr/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ diff --git a/pvr.nextpvr/changelog.txt b/pvr.nextpvr/changelog.txt index efe0b0e5..b1e27594 100644 --- a/pvr.nextpvr/changelog.txt +++ b/pvr.nextpvr/changelog.txt @@ -1,3 +1,8 @@ +v21.2.0 +- Start timeshift in realtime for radio playback +- Add support for episode and episode part parsing +- Clean up duplicated S/E information sent from NextPVR in the subtitle when no subtitle is present. + v21.1.1 - Translations updates from Weblate - af_za, am_et, ar_sa, ast_es, az_az, be_by, bg_bg, bs_ba, ca_es, cs_cz, cy_gb, da_dk, de_de, el_gr, en_au, en_nz, en_us, eo, es_ar, es_es, es_mx, et_ee, eu_es, fa_af, fa_ir, fi_fi, fo_fo, fr_ca, fr_fr, gl_es, he_il, hi_in, hr_hr, hu_hu, hy_am, id_id, is_is, it_it, ja_jp, ko_kr, lt_lt, lv_lv, mi, mk_mk, ml_in, mn_mn, ms_my, mt_mt, my_mm, nb_no, nl_nl, pl_pl, pt_br, pt_pt, ro_ro, ru_ru, si_lk, sk_sk, sl_si, sq_al, sr_rs, sr_rs@latin, sv_se, szl, ta_in, te_in, tg_tj, th_th, tr_tr, uk_ua, uz_uz, vi_vn, zh_cn, zh_tw diff --git a/src/EPG.cpp b/src/EPG.cpp index ac71f8d4..d02d43f4 100644 --- a/src/EPG.cpp +++ b/src/EPG.cpp @@ -68,8 +68,6 @@ PVR_ERROR EPG::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi:: } } - broadcast.SetYear(XMLUtils::GetIntValue(pListingNode, "year")); - std::string startTime; std::string endTime; XMLUtils::GetString(pListingNode, "start", startTime); @@ -80,7 +78,6 @@ PVR_ERROR EPG::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi:: const std::string oidLookup(endTime + ":" + std::to_string(channelUid)); broadcast.SetTitle(title); - broadcast.SetEpisodeName(subtitle); broadcast.SetUniqueChannelId(channelUid); broadcast.SetStartTime(stol(startTime)); broadcast.SetUniqueBroadcastId(stoi(endTime)); @@ -132,13 +129,65 @@ PVR_ERROR EPG::GetEPGForChannel(int channelUid, time_t start, time_t end, kodi:: } } - broadcast.SetSeriesNumber(XMLUtils::GetIntValue(pListingNode, "season", EPG_TAG_INVALID_SERIES_EPISODE)); - broadcast.SetEpisodeNumber(XMLUtils::GetIntValue(pListingNode, "episode", EPG_TAG_INVALID_SERIES_EPISODE)); + + int season{ EPG_TAG_INVALID_SERIES_EPISODE }; + int episode = {EPG_TAG_INVALID_SERIES_EPISODE}; + XMLUtils::GetInt(pListingNode, "season", season); + XMLUtils::GetInt(pListingNode, "episode", episode); + broadcast.SetEpisodeNumber(episode); broadcast.SetEpisodePartNumber(EPG_TAG_INVALID_SERIES_EPISODE); + // Backend could send epidode only as S00 and parts are not support + if (season <= 0 || episode == EPG_TAG_INVALID_SERIES_EPISODE) + { + std::regex base_regex("^.*\\([eE][pP](\\d+)(?:/?(\\d+))?\\)"); + std::smatch base_match; + if (std::regex_search(description, base_match, base_regex)) + { + broadcast.SetEpisodeNumber(std::atoi(base_match[1].str().c_str())); + if (base_match[2].matched) + broadcast.SetEpisodePartNumber(std::atoi(base_match[2].str().c_str())); + } + else if (std::regex_search(description, base_match, std::regex("^([1-9]\\d*)/([1-9]\\d*)\\."))) + { + broadcast.SetEpisodeNumber(std::atoi(base_match[1].str().c_str())); + broadcast.SetEpisodePartNumber(std::atoi(base_match[2].str().c_str())); + } + } + if (season != EPG_TAG_INVALID_SERIES_EPISODE) + { + // clear out NextPVR formatted data, Kodi supports S/E display + if (subtitle == kodi::tools::StringUtils::Format("S%02dE%02d", season, episode)) + { + subtitle.clear(); + } + if (season == 0) + season = EPG_TAG_INVALID_SERIES_EPISODE; + } + broadcast.SetSeriesNumber(season); + broadcast.SetEpisodeName(subtitle); + + int year{-1}; + if (XMLUtils::GetInt(pListingNode, "year", year)) + { + broadcast.SetYear(year); + } std::string original; - XMLUtils::GetString(pListingNode, "original", original); - broadcast.SetFirstAired(original); + if (XMLUtils::GetString(pListingNode, "original", original)) + { + if (broadcast.GetGenreType() == 16 && broadcast.GetGenreSubType() == 0 && year == -1 && original.length() > 4) + { + const std::string originalYear = kodi::tools::StringUtils::Mid(original, 0, 4); + year = atoi(originalYear.c_str()); + if (year != 0) + broadcast.SetYear(year); + } + else + { + broadcast.SetFirstAired(original); + } + } + bool firstrun; if (XMLUtils::GetBoolean(pListingNode, "firstrun", firstrun)) diff --git a/src/Recordings.cpp b/src/Recordings.cpp index ed5c22f0..dd764305 100644 --- a/src/Recordings.cpp +++ b/src/Recordings.cpp @@ -323,20 +323,17 @@ bool Recordings::UpdatePvrRecording(const tinyxml2::XMLNode* pRecordingNode, kod tag.SetSeriesNumber(PVR_RECORDING_INVALID_SERIES_EPISODE); tag.SetEpisodeNumber(PVR_RECORDING_INVALID_SERIES_EPISODE); - - if (XMLUtils::GetString(pRecordingNode, "subtitle", buffer)) + if (ParseNextPVRSubtitle(pRecordingNode, tag)) { - if (ParseNextPVRSubtitle(pRecordingNode, tag)) + if (m_settings->m_separateSeasons && multipleSeasons && tag.GetSeriesNumber() != PVR_RECORDING_INVALID_SERIES_EPISODE) { - if (m_settings->m_separateSeasons && multipleSeasons && tag.GetSeriesNumber() != PVR_RECORDING_INVALID_SERIES_EPISODE) - { - if (status != "Failed") - tag.SetDirectory(kodi::tools::StringUtils::Format("/%s/%s %d", tag.GetTitle().c_str(), kodi::addon::GetLocalizedString(20373).c_str(), tag.GetSeriesNumber())); - else - tag.SetDirectory(kodi::tools::StringUtils::Format("/%s/%s/%s %d", kodi::addon::GetLocalizedString(30166).c_str(),tag.GetTitle().c_str(), kodi::addon::GetLocalizedString(20373).c_str(), tag.GetSeriesNumber())); - } + if (status != "Failed") + tag.SetDirectory(kodi::tools::StringUtils::Format("/%s/%s %d", tag.GetTitle().c_str(), kodi::addon::GetLocalizedString(20373).c_str(), tag.GetSeriesNumber())); + else + tag.SetDirectory(kodi::tools::StringUtils::Format("/%s/%s/%s %d", kodi::addon::GetLocalizedString(30166).c_str(),tag.GetTitle().c_str(), kodi::addon::GetLocalizedString(20373).c_str(), tag.GetSeriesNumber())); } } + tag.SetYear(XMLUtils::GetIntValue(pRecordingNode, "year")); std::string original; @@ -473,24 +470,27 @@ bool Recordings::ParseNextPVRSubtitle(const tinyxml2::XMLNode *pRecordingNode, k bool hasSeasonEpisode = false; if (XMLUtils::GetString(pRecordingNode, "subtitle", buffer)) { - std::regex base_regex("S(\\d{2,4})E(\\d+) - ?(.+)?"); + std::regex base_regex("S(\\d{2,4})E(\\d+)(?: - ?(.+)$)?"); std::smatch base_match; // note NextPVR does not support S0 for specials - if (std::regex_match(buffer, base_match, base_regex)) + if (std::regex_search(buffer, base_match, base_regex)) { if (base_match.size() == 3 || base_match.size() == 4) { - std::ssub_match base_sub_match = base_match[1]; - tag.SetSeriesNumber(std::stoi(base_sub_match.str())); + int season = std::stoi(base_sub_match.str()); + if (season != 0) + { + tag.SetSeriesNumber(season); + hasSeasonEpisode = true; + } base_sub_match = base_match[2]; tag.SetEpisodeNumber(std::stoi(base_sub_match.str())); - if (base_match.size() == 4) + if (base_match[3].matched) { base_sub_match = base_match[3]; tag.SetEpisodeName(base_sub_match.str()); } - hasSeasonEpisode = true; } } else @@ -518,6 +518,21 @@ bool Recordings::ParseNextPVRSubtitle(const tinyxml2::XMLNode *pRecordingNode, k } } } + const std::string plot = tag.GetPlot(); + if (tag.GetEpisodeNumber() == PVR_RECORDING_INVALID_SERIES_EPISODE && !plot.empty()); + { + // Kodi doesn't support episode parts on recordings + std::regex base_regex("^.*\\([eE][pP](\\d+)(?:/?(\\d+))?\\)"); + std::smatch base_match; + if (std::regex_search(plot, base_match, base_regex)) + { + tag.SetEpisodeNumber(std::atoi(base_match[1].str().c_str())); + } + else if (std::regex_search(plot, base_match, std::regex("^([1-9]\\d*)/([1-9]\\d*)\\."))) + { + tag.SetEpisodeNumber(std::atoi(base_match[1].str().c_str())); + } + } } return hasSeasonEpisode; }