use new channel identifiers in M3U and XMLTV to disambiguate in Plex (#1920)
This commit is contained in:
@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [Unreleased]
|
||||
### Changed
|
||||
- **BREAKING CHANGE**: Change channel identifiers used in XMLTV to work around bad behavior in Plex
|
||||
|
||||
### Fixed
|
||||
- Fix startup error with MySql backend caused by database cleaner
|
||||
- Fix emptying trash with ElasticSearch backend
|
||||
|
||||
@@ -7,6 +7,7 @@ using ErsatzTV.Core.Domain.Filler;
|
||||
using ErsatzTV.Core.Emby;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Iptv;
|
||||
using ErsatzTV.Core.Jellyfin;
|
||||
using ErsatzTV.Core.Streaming;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
@@ -514,6 +515,8 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
||||
{
|
||||
ProgrammeStart = start,
|
||||
ProgrammeStop = stop,
|
||||
ChannelId = ChannelIdentifier.FromNumber(request.ChannelNumber),
|
||||
ChannelIdLegacy = ChannelIdentifier.LegacyFromNumber(request.ChannelNumber),
|
||||
request.ChannelNumber,
|
||||
HasCustomTitle = hasCustomTitle,
|
||||
displayItem.CustomTitle,
|
||||
@@ -569,6 +572,8 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
||||
{
|
||||
ProgrammeStart = start,
|
||||
ProgrammeStop = stop,
|
||||
ChannelId = ChannelIdentifier.FromNumber(request.ChannelNumber),
|
||||
ChannelIdLegacy = ChannelIdentifier.LegacyFromNumber(request.ChannelNumber),
|
||||
request.ChannelNumber,
|
||||
HasCustomTitle = hasCustomTitle,
|
||||
displayItem.CustomTitle,
|
||||
@@ -629,6 +634,8 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
||||
{
|
||||
ProgrammeStart = start,
|
||||
ProgrammeStop = stop,
|
||||
ChannelId = ChannelIdentifier.FromNumber(request.ChannelNumber),
|
||||
ChannelIdLegacy = ChannelIdentifier.LegacyFromNumber(request.ChannelNumber),
|
||||
request.ChannelNumber,
|
||||
HasCustomTitle = hasCustomTitle,
|
||||
displayItem.CustomTitle,
|
||||
@@ -686,6 +693,8 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
||||
{
|
||||
ProgrammeStart = start,
|
||||
ProgrammeStop = stop,
|
||||
ChannelId = ChannelIdentifier.FromNumber(request.ChannelNumber),
|
||||
ChannelIdLegacy = ChannelIdentifier.LegacyFromNumber(request.ChannelNumber),
|
||||
request.ChannelNumber,
|
||||
HasCustomTitle = hasCustomTitle,
|
||||
displayItem.CustomTitle,
|
||||
@@ -738,6 +747,8 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
||||
{
|
||||
ProgrammeStart = start,
|
||||
ProgrammeStop = stop,
|
||||
ChannelId = ChannelIdentifier.FromNumber(request.ChannelNumber),
|
||||
ChannelIdLegacy = ChannelIdentifier.LegacyFromNumber(request.ChannelNumber),
|
||||
request.ChannelNumber,
|
||||
HasCustomTitle = hasCustomTitle,
|
||||
displayItem.CustomTitle,
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Xml;
|
||||
using Dapper;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Iptv;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -78,6 +79,8 @@ public class RefreshChannelListHandler : IRequestHandler<RefreshChannelList>
|
||||
{
|
||||
var data = new
|
||||
{
|
||||
ChannelId = ChannelIdentifier.FromNumber(channel.Number),
|
||||
ChannelIdLegacy = ChannelIdentifier.LegacyFromNumber(channel.Number),
|
||||
ChannelNumber = channel.Number,
|
||||
ChannelName = channel.Name,
|
||||
ChannelCategories = GetCategories(channel.Categories),
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using ErsatzTV.Core.Iptv;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace ErsatzTV.Core.Tests.Iptv;
|
||||
|
||||
[TestFixture]
|
||||
public class ChannelIdentifierTests
|
||||
{
|
||||
[TestCase("1.23", "1.23.etv")]
|
||||
[TestCase("12.3", "12.3.etv")]
|
||||
[TestCase("123", "123.etv")]
|
||||
[TestCase("1.24", "1.24.etv")]
|
||||
[TestCase("12.4", "12.4.etv")]
|
||||
[TestCase("124", "124.etv")]
|
||||
public void TestLegacy(string channelNumber, string expected)
|
||||
{
|
||||
string actual = ChannelIdentifier.LegacyFromNumber(channelNumber);
|
||||
actual.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("1.23", "C1.23.150.ersatztv.org")]
|
||||
[TestCase("12.3", "C12.3.198.ersatztv.org")]
|
||||
[TestCase("123", "C123.246.ersatztv.org")]
|
||||
[TestCase("1.24", "C1.24.151.ersatztv.org")]
|
||||
[TestCase("12.4", "C12.4.199.ersatztv.org")]
|
||||
[TestCase("124", "C124.247.ersatztv.org")]
|
||||
public void TestNew(string channelNumber, string expected)
|
||||
{
|
||||
string actual = ChannelIdentifier.FromNumber(channelNumber);
|
||||
actual.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace ErsatzTV.Core.Iptv;
|
||||
|
||||
public static class ChannelIdentifier
|
||||
{
|
||||
public static string LegacyFromNumber(string channelNumber)
|
||||
{
|
||||
return $"{channelNumber}.etv";
|
||||
}
|
||||
|
||||
public static string FromNumber(string channelNumber)
|
||||
{
|
||||
// get rid of any decimal (only two are allowed)
|
||||
int number = (int)(decimal.Parse(channelNumber, CultureInfo.InvariantCulture) * 100);
|
||||
int id = 0;
|
||||
while (number != 0)
|
||||
{
|
||||
id += number % 10 + 48;
|
||||
number /= 10;
|
||||
}
|
||||
|
||||
return $"C{channelNumber}.{id}.ersatztv.org";
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public class ChannelPlaylist
|
||||
|
||||
sb.AppendLine(
|
||||
CultureInfo.InvariantCulture,
|
||||
$"#EXTINF:0 tvg-id=\"{channel.Number}.etv\" channel-id=\"{shortUniqueId}\" channel-number=\"{channel.Number}\" CUID=\"{shortUniqueId}\" tvg-chno=\"{channel.Number}\" tvg-name=\"{channel.Name}\" tvg-logo=\"{logo}\" group-title=\"{channel.Group}\" tvc-stream-vcodec=\"{vcodec}\" tvc-stream-acodec=\"{acodec}\", {channel.Name}");
|
||||
$"#EXTINF:0 tvg-id=\"{ChannelIdentifier.FromNumber(channel.Number)}\" channel-id=\"{shortUniqueId}\" channel-number=\"{channel.Number}\" CUID=\"{shortUniqueId}\" tvg-chno=\"{channel.Number}\" tvg-name=\"{channel.Name}\" tvg-logo=\"{logo}\" group-title=\"{channel.Group}\" tvc-stream-vcodec=\"{vcodec}\" tvc-stream-acodec=\"{acodec}\", {channel.Name}");
|
||||
sb.AppendLine(
|
||||
CultureInfo.InvariantCulture,
|
||||
$"{_scheme}://{_host}{_baseUrl}/iptv/channel/{channel.Number}.{format}");
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
{{ ##
|
||||
|
||||
Available values:
|
||||
- channel_id
|
||||
- channel_id_legacy
|
||||
- channel_number
|
||||
- channel_name
|
||||
- channel_categories
|
||||
- channel_has_artwork
|
||||
- channel_artwork_path
|
||||
- channel_name_encoded
|
||||
|
||||
|
||||
{RequestBase} and {AccessTokenUri} are replaced dynamically when XMLTV is requested,
|
||||
and must remain as-is in this template to work properly with ETV URLs.
|
||||
External URLs do not require these placeholders.
|
||||
@@ -16,7 +18,7 @@ The resulting XML will be minified by ErsatzTV - so feel free to keep things nic
|
||||
|
||||
## }}
|
||||
|
||||
<channel id="{{ channel_number }}.etv">
|
||||
<channel id="{{ channel_id }}">
|
||||
<display-name>{{ channel_number }} {{ channel_name }}</display-name>
|
||||
<display-name>{{ channel_number }}</display-name>
|
||||
<display-name>{{ channel_name }}</display-name>
|
||||
@@ -24,7 +26,7 @@ The resulting XML will be minified by ErsatzTV - so feel free to keep things nic
|
||||
<category lang="en">{{ category }}</category>
|
||||
{{ end }}
|
||||
{{ if channel_has_artwork }}
|
||||
<icon src="{RequestBase}/iptv/logos/{{ channel_artwork_path }}.jpg{AccessTokenUri}" />
|
||||
<icon src="{RequestBase}/iptv/logos/{{ channel_artwork_path }}.jpg{AccessTokenUri}" />
|
||||
{{ else }}
|
||||
<icon src="{RequestBase}/iptv/logos/gen{AccessTokenUri}&text={{ channel_name_encoded }}" />
|
||||
{{ end }}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Available values:
|
||||
- programme_start
|
||||
- programme_stop
|
||||
- channel_id
|
||||
- channel_id_legacy
|
||||
- channel_number
|
||||
- has_custom_title
|
||||
- custom_title
|
||||
@@ -22,12 +24,12 @@ Available values:
|
||||
- show_content_rating
|
||||
- show_guids
|
||||
- episode_guids
|
||||
|
||||
|
||||
The resulting XML will be minified by ErsatzTV - so feel free to keep things nicely formatted here.
|
||||
|
||||
## }}
|
||||
|
||||
<programme start="{{ programme_start }}" stop="{{ programme_stop }}" channel="{{ channel_number }}.etv">
|
||||
<programme start="{{ programme_start }}" stop="{{ programme_stop }}" channel="{{ channel_id }}">
|
||||
{{ if has_custom_title }}
|
||||
<title lang="en">{{ custom_title }}</title>
|
||||
{{ else }}
|
||||
@@ -51,7 +53,7 @@ The resulting XML will be minified by ErsatzTV - so feel free to keep things nic
|
||||
{{ if show_has_content_rating }}
|
||||
{{ for rating in show_content_rating | string.split '/' }}
|
||||
{{ if rating | string.downcase | string.starts_with 'us:' }}
|
||||
<rating system="VCHIP">
|
||||
<rating system="VCHIP">
|
||||
{{ else }}
|
||||
<rating>
|
||||
{{ end }}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Available values:
|
||||
- programme_start
|
||||
- programme_stop
|
||||
- channel_id
|
||||
- channel_id_legacy
|
||||
- channel_number
|
||||
- has_custom_title
|
||||
- custom_title
|
||||
@@ -17,12 +19,12 @@ Available values:
|
||||
- movie_has_content_rating
|
||||
- movie_content_rating
|
||||
- movie_guids
|
||||
|
||||
|
||||
The resulting XML will be minified by ErsatzTV - so feel free to keep things nicely formatted here.
|
||||
|
||||
## }}
|
||||
|
||||
<programme start="{{ programme_start }}" stop="{{ programme_stop }}" channel="{{ channel_number }}.etv">
|
||||
<programme start="{{ programme_start }}" stop="{{ programme_stop }}" channel="{{ channel_id }}">
|
||||
{{ if has_custom_title }}
|
||||
<title lang="en">{{ custom_title }}</title>
|
||||
{{ else }}
|
||||
@@ -44,7 +46,7 @@ The resulting XML will be minified by ErsatzTV - so feel free to keep things nic
|
||||
{{ if movie_has_content_rating }}
|
||||
{{ for rating in movie_content_rating | string.split '/' }}
|
||||
{{ if rating | string.starts_with 'us:' }}
|
||||
<rating system="MPAA">
|
||||
<rating system="MPAA">
|
||||
{{ else }}
|
||||
<rating>
|
||||
{{ end }}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Available values:
|
||||
- programme_start
|
||||
- programme_stop
|
||||
- channel_id
|
||||
- channel_id_legacy
|
||||
- channel_number
|
||||
- has_custom_title
|
||||
- custom_title
|
||||
@@ -25,12 +27,12 @@ Available values:
|
||||
- music_video_all_artists
|
||||
- music_video_studios
|
||||
- music_video_directors
|
||||
|
||||
|
||||
The resulting XML will be minified by ErsatzTV - so feel free to keep things nicely formatted here.
|
||||
|
||||
## }}
|
||||
|
||||
<programme start="{{ programme_start }}" stop="{{ programme_stop }}" channel="{{ channel_number }}.etv">
|
||||
<programme start="{{ programme_start }}" stop="{{ programme_stop }}" channel="{{ channel_id }}">
|
||||
{{ if has_custom_title }}
|
||||
<title lang="en">{{ custom_title }}</title>
|
||||
{{ else }}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Available values:
|
||||
- programme_start
|
||||
- programme_stop
|
||||
- channel_id
|
||||
- channel_id_legacy
|
||||
- channel_number
|
||||
- has_custom_title
|
||||
- custom_title
|
||||
@@ -14,12 +16,12 @@ Available values:
|
||||
- other_video_genres
|
||||
- other_video_has_content_rating
|
||||
- other_video_content_rating
|
||||
|
||||
|
||||
The resulting XML will be minified by ErsatzTV - so feel free to keep things nicely formatted here.
|
||||
|
||||
## }}
|
||||
|
||||
<programme start="{{ programme_start }}" stop="{{ programme_stop }}" channel="{{ channel_number }}.etv">
|
||||
<programme start="{{ programme_start }}" stop="{{ programme_stop }}" channel="{{ channel_id }}">
|
||||
{{ if has_custom_title }}
|
||||
<title lang="en">{{ custom_title }}</title>
|
||||
{{ else }}
|
||||
@@ -40,7 +42,7 @@ The resulting XML will be minified by ErsatzTV - so feel free to keep things nic
|
||||
{{ if other_video_has_content_rating }}
|
||||
{{ for rating in other_video_content_rating | string.split '/' }}
|
||||
{{ if rating | string.starts_with 'us:' }}
|
||||
<rating system="MPAA">
|
||||
<rating system="MPAA">
|
||||
{{ else }}
|
||||
<rating>
|
||||
{{ end }}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Available values:
|
||||
- programme_start
|
||||
- programme_stop
|
||||
- channel_id
|
||||
- channel_id_legacy
|
||||
- channel_number
|
||||
- has_custom_title
|
||||
- custom_title
|
||||
@@ -23,12 +25,12 @@ Available values:
|
||||
- song_has_release_date
|
||||
- song_release_date
|
||||
- song_studios
|
||||
|
||||
|
||||
The resulting XML will be minified by ErsatzTV - so feel free to keep things nicely formatted here.
|
||||
|
||||
## }}
|
||||
|
||||
<programme start="{{ programme_start }}" stop="{{ programme_stop }}" channel="{{ channel_number }}.etv">
|
||||
<programme start="{{ programme_start }}" stop="{{ programme_stop }}" channel="{{ channel_id }}">
|
||||
{{ if has_custom_title }}
|
||||
<title lang="en">{{ custom_title }}</title>
|
||||
{{ else }}
|
||||
|
||||
Reference in New Issue
Block a user