diff --git a/README.md b/README.md index a9a7696c0..c78afb19f 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,11 @@ More detail on these can be found in [Supported M3U and XMLTV elements](#support * **Group entries by title**: If multiple entries exist with matching titles, create a virtual folder to group them together. * **Group entries by season**: If multiple entries exist with matching titles, try additionally grouping them in sub-folders representing seasons. * **Include season and episode number in title**: Prepend the season and episode numbers to the title. +* **Use M3U Group name in path**: Select how to use the M3U group title in the path. Note that it will only be used if a single group name is provided. The options are: + - `Never` - Never use it. + - `Always append` - Always append it. + - `When no media-dir is present` - Only use the group title of the M3U when no media-dir is provided. +* **Force all M3U entries to be media**: Force the full playlist to be media, regardless of what tags are present. Since the introduction of multiple instances for PVR add-ons this option can be useful. * **Include VODs as media**: Show VOD as recordings if enabled. If disabled only M3U entries with media attributes will be shown as PVR recordings. ### Timeshift diff --git a/pvr.iptvsimple/addon.xml.in b/pvr.iptvsimple/addon.xml.in index 125d721df..9b9826c41 100644 --- a/pvr.iptvsimple/addon.xml.in +++ b/pvr.iptvsimple/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ diff --git a/pvr.iptvsimple/changelog.txt b/pvr.iptvsimple/changelog.txt index ab9999339..185d6d79d 100644 --- a/pvr.iptvsimple/changelog.txt +++ b/pvr.iptvsimple/changelog.txt @@ -1,3 +1,8 @@ +v21.3.0 +- Add settings options to add M3U group name to directory for media +- Add expert option to force the entire playlist to be Media. Useful now that multiple instances are supported +- Support custom groups for Media + v21.2.1 - Don't URL encode paths using resource:// or special:// protocols diff --git a/pvr.iptvsimple/resources/instance-settings.xml b/pvr.iptvsimple/resources/instance-settings.xml index 1aa1c7eb8..051f132ed 100644 --- a/pvr.iptvsimple/resources/instance-settings.xml +++ b/pvr.iptvsimple/resources/instance-settings.xml @@ -598,6 +598,26 @@ + + 0 + 0 + + + + + + + + + + + 2 + false + + true + + + 2 true 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 042d141a1..f1fe1db5a 100644 --- a/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po +++ b/pvr.iptvsimple/resources/language/resource.language.en_gb/strings.po @@ -615,7 +615,32 @@ msgctxt "#30155" msgid "Show Media as recordings" msgstr "" -#empty strings from id 30156 to 30449 +#. label: Media - mediaUseM3UGroupForGrouping +msgctxt "#30156" +msgid "Use M3U Group name in path" +msgstr "" + +#. label-option: Media - mediaEnabled - IGNORE_GROUP_NAME +msgctxt "#30157" +msgid "Never" +msgstr "" + +#. label-option: Media - mediaEnabled - ALWAYS_APPEND +msgctxt "#30158" +msgid "Always append" +msgstr "" + +#. label-option: Media - mediaEnabled - ONLY_IF_EMPTY +msgctxt "#30159" +msgid "When no media-dir is present" +msgstr "" + +#. label: Media - mediaForcePlaylist +msgctxt "#30160" +msgid "Force all M3U entries to be media" +msgstr "" + +#empty strings from id 30161 to 30449 #. label: TV msgctxt "#30450" @@ -1068,4 +1093,14 @@ msgstr "" #. label: Media - mediaEnabled msgctxt "#30805" msgid "If enabled, all IPTV media entries can be shown as PVR recordings. Otherwise, they appear as regular PVR channels." +msgstr "" + +#. label: Media - mediaM3UGroupPath +msgctxt "#30806" +msgid "Select how to use the M3U group title in the path. Note that it will only be used if a single group name is provided. The options are: [B]Never[/B] - Never use it; [B]Always append[/B] - Always append it; [B]When no media-dir is present[/B] - Only use the group title of the M3U when no media-dir is provided." +msgstr "" + +#. label: Media - mediaForcePlaylist +msgctxt "#30806" +msgid "Force the full playlist to be media, regardless of what tags are present. Since the introduction of multiple instances for PVR add-ons this option can be useful." msgstr "" \ No newline at end of file diff --git a/src/iptvsimple/InstanceSettings.cpp b/src/iptvsimple/InstanceSettings.cpp index 094234e9f..258354940 100644 --- a/src/iptvsimple/InstanceSettings.cpp +++ b/src/iptvsimple/InstanceSettings.cpp @@ -119,11 +119,13 @@ void InstanceSettings::ReadSettings() m_instance.CheckInstanceSettingEnum("logoFromEpg", m_epgLogosMode); m_instance.CheckInstanceSettingBoolean("useLogosLocalPathOnly", m_useLocalLogosOnly); - // Media m_mediaEnabled + // Media m_instance.CheckInstanceSettingBoolean("mediaEnabled", m_mediaEnabled); m_instance.CheckInstanceSettingBoolean("mediaVODAsRecordings", m_showVodAsRecordings); m_instance.CheckInstanceSettingBoolean("mediaGroupByTitle", m_groupMediaByTitle); m_instance.CheckInstanceSettingBoolean("mediaGroupBySeason", m_groupMediaBySeason); + m_instance.CheckInstanceSettingEnum("mediaM3UGroupPath", m_mediaUseM3UGroupPathMode); + m_instance.CheckInstanceSettingBoolean("mediaForcePlaylist", m_mediaForcePlaylist); m_instance.CheckInstanceSettingBoolean("mediaTitleSeasonEpisode", m_includeShowInfoInMediaTitle); // Timeshift @@ -279,6 +281,10 @@ ADDON_STATUS InstanceSettings::SetSetting(const std::string& settingName, const return SetSetting(settingName, settingValue, m_groupMediaBySeason, ADDON_STATUS_OK, ADDON_STATUS_OK); else if (settingName == "mediaTitleSeasonEpisode") return SetSetting(settingName, settingValue, m_includeShowInfoInMediaTitle, ADDON_STATUS_OK, ADDON_STATUS_OK); + else if (settingName == "mediaM3UGroupPath") + return SetEnumSetting(settingName, settingValue, m_mediaUseM3UGroupPathMode, ADDON_STATUS_OK, ADDON_STATUS_OK); + else if (settingName == "mediaForcePlaylist") + return SetSetting(settingName, settingValue, m_mediaForcePlaylist, ADDON_STATUS_OK, ADDON_STATUS_OK); else if (settingName == "mediaVODAsRecordings") return SetSetting(settingName, settingValue, m_showVodAsRecordings, ADDON_STATUS_OK, ADDON_STATUS_OK); // Timeshift diff --git a/src/iptvsimple/InstanceSettings.h b/src/iptvsimple/InstanceSettings.h index 80449d9e1..049133ccc 100644 --- a/src/iptvsimple/InstanceSettings.h +++ b/src/iptvsimple/InstanceSettings.h @@ -59,6 +59,15 @@ namespace iptvsimple PREFER_XMLTV }; + enum class MediaUseM3UGroupPathMode + : int // same type as addon settings + { + IGNORE_GROUP_NAME = 0, + ALWAYS_APPEND, + ONLY_IF_EMPTY + }; + + enum class CatchupOverrideMode : int // same type as addon settings { @@ -133,6 +142,8 @@ namespace iptvsimple bool ShowVodAsRecordings() const { return m_showVodAsRecordings; } bool GroupMediaByTitle() const { return m_groupMediaByTitle; } bool GroupMediaBySeason() const { return m_groupMediaBySeason; } + const MediaUseM3UGroupPathMode& GetMediaUseM3UGroupPathMode() { return m_mediaUseM3UGroupPathMode; } + bool MediaForcePlaylist() const { return m_mediaForcePlaylist; } bool IncludeShowInfoInMediaTitle() const { return m_includeShowInfoInMediaTitle; } bool IsTimeshiftEnabled() const { return m_timeshiftEnabled; } @@ -294,6 +305,8 @@ namespace iptvsimple bool m_groupMediaByTitle = true; bool m_groupMediaBySeason = true; bool m_includeShowInfoInMediaTitle = false; + MediaUseM3UGroupPathMode m_mediaUseM3UGroupPathMode = MediaUseM3UGroupPathMode::IGNORE_GROUP_NAME; + bool m_mediaForcePlaylist = false; bool m_showVodAsRecordings = true; // Timeshift diff --git a/src/iptvsimple/Media.cpp b/src/iptvsimple/Media.cpp index 4caeedf47..f734cc18f 100644 --- a/src/iptvsimple/Media.cpp +++ b/src/iptvsimple/Media.cpp @@ -66,16 +66,35 @@ int GenerateMediaEntryId(const char* providerName, const char* streamUrl) } // unamed namespace -bool Media::AddMediaEntry(MediaEntry& mediaEntry) +bool Media::AddMediaEntry(MediaEntry& mediaEntry, std::vector& groupIdList, ChannelGroups& channelGroups, bool channelHadGroups) { + // If we have no groups set for this media check it that's ok before adding it. + // Note that TV is a proxy for Media. There is no concept of radio for media + if (m_settings->AllowTVChannelGroupsOnly() && groupIdList.empty()) + return false; + std::string mediaEntryId = std::to_string(GenerateMediaEntryId(mediaEntry.GetProviderName().c_str(), mediaEntry.GetStreamURL().c_str())); mediaEntryId.append("-" + mediaEntry.GetDirectory() + mediaEntry.GetTitle()); mediaEntry.SetMediaEntryId(mediaEntryId); + // Media Ids must be unique if (m_mediaIdMap.find(mediaEntryId) != m_mediaIdMap.end()) return false; + bool belongsToGroup = false; + for (int myGroupId : groupIdList) + { + if (channelGroups.GetChannelGroup(myGroupId) != nullptr) + belongsToGroup = true; + } + + + // We only care if a media entry belongs to a group if it had groups to begin with + // Note that a channel can have had groups but no have no groups valid currently. + if (!belongsToGroup && channelHadGroups) + return false; + m_media.emplace_back(mediaEntry); m_mediaIdMap.insert({mediaEntryId, mediaEntry}); diff --git a/src/iptvsimple/Media.h b/src/iptvsimple/Media.h index 9743600d6..355d72e50 100644 --- a/src/iptvsimple/Media.h +++ b/src/iptvsimple/Media.h @@ -8,6 +8,7 @@ #pragma once +#include "ChannelGroups.h" #include "data/MediaEntry.h" #include @@ -26,7 +27,7 @@ namespace iptvsimple const std::string GetMediaEntryURL(const kodi::addon::PVRRecording& mediaEntry); const iptvsimple::data::MediaEntry* FindMediaEntry(const std::string& id, const std::string& displayName) const; - bool AddMediaEntry(iptvsimple::data::MediaEntry& entry); + bool AddMediaEntry(iptvsimple::data::MediaEntry& entry, std::vector& groupIdList, iptvsimple::ChannelGroups& channelGroups, bool channelHadGroups); std::vector& GetMediaEntryList() { return m_media; } diff --git a/src/iptvsimple/PlaylistLoader.cpp b/src/iptvsimple/PlaylistLoader.cpp index ccf29d746..ee322c5d7 100644 --- a/src/iptvsimple/PlaylistLoader.cpp +++ b/src/iptvsimple/PlaylistLoader.cpp @@ -146,7 +146,8 @@ bool PlaylistLoader::LoadPlayList() isMediaEntry = line.find(MEDIA) != std::string::npos || line.find(MEDIA_DIR) != std::string::npos || - line.find(MEDIA_SIZE) != std::string::npos; + line.find(MEDIA_SIZE) != std::string::npos || + m_settings->MediaForcePlaylist(); const std::string groupNamesListString = ParseIntoChannel(line, tmpChannel, tmpMediaEntry, currentChannelGroupIdList, epgTimeShift, catchupCorrectionSecs, xeevCatchup); @@ -203,7 +204,7 @@ bool PlaylistLoader::LoadPlayList() entry.UpdateFrom(tmpChannel); entry.SetStreamURL(line); - if (!m_media.AddMediaEntry(entry)) + if (!m_media.AddMediaEntry(entry, currentChannelGroupIdList, m_channelGroups, channelHadGroups)) Logger::Log(LEVEL_DEBUG, "%s - Counld not add media entry as an entry with the same gnenerated unique ID already exists", __func__); } @@ -454,10 +455,24 @@ std::string PlaylistLoader::ParseIntoChannel(const std::string& line, Channel& c if (!strMediaDir.empty()) mediaEntry.SetDirectory(strMediaDir); + std::string groupNames = ReadMarkerValue(infoLine, GROUP_NAME_MARKER); + auto& m3uGroupPathMode = m_settings->GetMediaUseM3UGroupPathMode(); + if (m3uGroupPathMode != MediaUseM3UGroupPathMode::IGNORE_GROUP_NAME) + { + if (m3uGroupPathMode == MediaUseM3UGroupPathMode::ALWAYS_APPEND || strMediaDir.empty()) + { + if (!groupNames.empty() && groupNames.find(';') == std::string::npos) + { + //A media entry directory will always end with a "/" + mediaEntry.SetDirectory(mediaEntry.GetDirectory() + groupNames); + } + } + } + if (!strMediaSize.empty()) mediaEntry.SetSizeInBytes(std::strtoll(strMediaSize.c_str(), nullptr, 10)); - return ReadMarkerValue(infoLine, GROUP_NAME_MARKER); + return groupNames; } return ""; diff --git a/src/iptvsimple/data/MediaEntry.cpp b/src/iptvsimple/data/MediaEntry.cpp index bce21bbb6..5661e85d2 100644 --- a/src/iptvsimple/data/MediaEntry.cpp +++ b/src/iptvsimple/data/MediaEntry.cpp @@ -177,6 +177,12 @@ std::string FixPath(const std::string& path) } // unamed namespace +void MediaEntry::SetDirectory(const std::string& value) +{ + m_directory = FixPath(value); +} + + void MediaEntry::UpdateTo(kodi::addon::PVRRecording& left, bool isInVirtualMediaEntryFolder, bool haveMediaTypes) { left.SetTitle(CreateTitle(m_title, m_seasonNumber, m_episodeNumber, m_settings)); diff --git a/src/iptvsimple/data/MediaEntry.h b/src/iptvsimple/data/MediaEntry.h index b8fbe396d..531eb9ccd 100644 --- a/src/iptvsimple/data/MediaEntry.h +++ b/src/iptvsimple/data/MediaEntry.h @@ -62,7 +62,7 @@ namespace iptvsimple void SetProviderUniqueId(int value) { m_providerUniqueId = value; } const std::string& GetDirectory() const { return m_directory; } - void SetDirectory(const std::string& value) { m_directory = value; } + void SetDirectory(const std::string& value); int64_t GetSizeInBytes() const { return m_sizeInBytes; } void SetSizeInBytes(int64_t value) { m_sizeInBytes = value; }