Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Media group #736

Merged
merged 4 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pvr.iptvsimple/addon.xml.in
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon
id="pvr.iptvsimple"
version="20.9.1"
version="20.10.0"
name="IPTV Simple Client"
provider-name="nightik and Ross Nicholson">
<requires>@ADDON_DEPENDS@
Expand Down
5 changes: 5 additions & 0 deletions pvr.iptvsimple/changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
v20.10.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

v20.9.1
- Don't URL encode paths using resource:// or special:// protocols

Expand Down
20 changes: 20 additions & 0 deletions pvr.iptvsimple/resources/instance-settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,26 @@
</dependencies>
<control type="toggle" />
</setting>
<setting id="mediaM3UGroupPath" type="integer" parent="mediaEnabled" label="30156" help="30806">
<level>0</level>
<default>0</default>
<constraints>
<options>
<option label="30157">0</option> <!-- DONT_USE_GROUP_TITLE -->
<option label="30158">1</option> <!-- ALWAYS_APPEND_GROUP_TITLE -->
<option label="30159">2</option> <!-- USE_GROUP_TITLE_IF_NO_PATH -->
</options>
</constraints>
<control type="spinner" format="integer" />
</setting>
<setting id="mediaForcePlaylist" type="boolean" parent="mediaEnabled" label="30160" help="30807">
<level>2</level>
<default>false</default>
<dependencies>
<dependency type="enable" setting="mediaEnabled" operator="is">true</dependency>
</dependencies>
<control type="toggle" />
</setting>
<setting id="mediaVODAsRecordings" type="boolean" parent="mediaEnabled" label="30154" help="30804">
<level>2</level>
<default>true</default>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 ""
8 changes: 7 additions & 1 deletion src/iptvsimple/InstanceSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,13 @@ void InstanceSettings::ReadSettings()
m_instance.CheckInstanceSettingEnum<EpgLogosMode>("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<MediaUseM3UGroupPathMode>("mediaM3UGroupPath", m_mediaUseM3UGroupPathMode);
m_instance.CheckInstanceSettingBoolean("mediaForcePlaylist", m_mediaForcePlaylist);
m_instance.CheckInstanceSettingBoolean("mediaTitleSeasonEpisode", m_includeShowInfoInMediaTitle);

// Timeshift
Expand Down Expand Up @@ -279,6 +281,10 @@ ADDON_STATUS InstanceSettings::SetSetting(const std::string& settingName, const
return SetSetting<bool, ADDON_STATUS>(settingName, settingValue, m_groupMediaBySeason, ADDON_STATUS_OK, ADDON_STATUS_OK);
else if (settingName == "mediaTitleSeasonEpisode")
return SetSetting<bool, ADDON_STATUS>(settingName, settingValue, m_includeShowInfoInMediaTitle, ADDON_STATUS_OK, ADDON_STATUS_OK);
else if (settingName == "mediaM3UGroupPath")
return SetEnumSetting<MediaUseM3UGroupPathMode, ADDON_STATUS>(settingName, settingValue, m_mediaUseM3UGroupPathMode, ADDON_STATUS_OK, ADDON_STATUS_OK);
else if (settingName == "mediaForcePlaylist")
return SetSetting<bool, ADDON_STATUS>(settingName, settingValue, m_mediaForcePlaylist, ADDON_STATUS_OK, ADDON_STATUS_OK);
else if (settingName == "mediaVODAsRecordings")
return SetSetting<bool, ADDON_STATUS>(settingName, settingValue, m_showVodAsRecordings, ADDON_STATUS_OK, ADDON_STATUS_OK);
// Timeshift
Expand Down
13 changes: 13 additions & 0 deletions src/iptvsimple/InstanceSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -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
Expand Down
21 changes: 20 additions & 1 deletion src/iptvsimple/Media.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>& 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});

Expand Down
3 changes: 2 additions & 1 deletion src/iptvsimple/Media.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#pragma once

#include "ChannelGroups.h"
#include "data/MediaEntry.h"

#include <string>
Expand All @@ -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<int>& groupIdList, iptvsimple::ChannelGroups& channelGroups, bool channelHadGroups);

std::vector<iptvsimple::data::MediaEntry>& GetMediaEntryList() { return m_media; }

Expand Down
21 changes: 18 additions & 3 deletions src/iptvsimple/PlaylistLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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__);

}
Expand Down Expand Up @@ -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 "";
Expand Down
6 changes: 6 additions & 0 deletions src/iptvsimple/data/MediaEntry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
2 changes: 1 addition & 1 deletion src/iptvsimple/data/MediaEntry.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down