Browse Source

use new channel identifiers in M3U and XMLTV to disambiguate in Plex (#1920)

pull/1921/head
Jason Dove 10 months ago committed by GitHub
parent
commit
c063720169
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      CHANGELOG.md
  2. 11
      ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs
  3. 3
      ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs
  4. 33
      ErsatzTV.Core.Tests/Iptv/ChannelIdentifierTests.cs
  5. 25
      ErsatzTV.Core/Iptv/ChannelIdentifier.cs
  6. 2
      ErsatzTV.Core/Iptv/ChannelPlaylist.cs
  7. 8
      ErsatzTV/Resources/Templates/_channel.sbntxt
  8. 8
      ErsatzTV/Resources/Templates/_episode.sbntxt
  9. 8
      ErsatzTV/Resources/Templates/_movie.sbntxt
  10. 6
      ErsatzTV/Resources/Templates/_musicVideo.sbntxt
  11. 8
      ErsatzTV/Resources/Templates/_otherVideo.sbntxt
  12. 6
      ErsatzTV/Resources/Templates/_song.sbntxt

3
CHANGELOG.md

@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. @@ -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

11
ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs

@ -7,6 +7,7 @@ using ErsatzTV.Core.Domain.Filler; @@ -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> @@ -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> @@ -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> @@ -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> @@ -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> @@ -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,

3
ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs

@ -4,6 +4,7 @@ using System.Xml; @@ -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> @@ -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),

33
ErsatzTV.Core.Tests/Iptv/ChannelIdentifierTests.cs

@ -0,0 +1,33 @@ @@ -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);
}
}

25
ErsatzTV.Core/Iptv/ChannelIdentifier.cs

@ -0,0 +1,25 @@ @@ -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";
}
}

2
ErsatzTV.Core/Iptv/ChannelPlaylist.cs

@ -86,7 +86,7 @@ public class ChannelPlaylist @@ -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}");

8
ErsatzTV/Resources/Templates/_channel.sbntxt

@ -1,13 +1,15 @@ @@ -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 @@ -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 @@ -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 }}

8
ErsatzTV/Resources/Templates/_episode.sbntxt

@ -3,6 +3,8 @@ @@ -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: @@ -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 @@ -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 }}

8
ErsatzTV/Resources/Templates/_movie.sbntxt

@ -3,6 +3,8 @@ @@ -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: @@ -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 @@ -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 }}

6
ErsatzTV/Resources/Templates/_musicVideo.sbntxt

@ -3,6 +3,8 @@ @@ -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: @@ -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 }}

8
ErsatzTV/Resources/Templates/_otherVideo.sbntxt

@ -3,6 +3,8 @@ @@ -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: @@ -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 @@ -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 }}

6
ErsatzTV/Resources/Templates/_song.sbntxt

@ -3,6 +3,8 @@ @@ -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: @@ -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 }}

Loading…
Cancel
Save