Browse Source

add xmltv time zone option (#1624)

pull/1625/head
Jason Dove 1 year ago committed by GitHub
parent
commit
3b4c993530
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 39
      ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs
  3. 5
      ErsatzTV.Application/Configuration/Commands/UpdateXmltvSettings.cs
  4. 34
      ErsatzTV.Application/Configuration/Commands/UpdateXmltvSettingsHandler.cs
  5. 3
      ErsatzTV.Application/Configuration/Queries/GetXmltvSettings.cs
  6. 19
      ErsatzTV.Application/Configuration/Queries/GetXmltvSettingsHandler.cs
  7. 6
      ErsatzTV.Application/Configuration/XmltvSettingsViewModel.cs
  8. 7
      ErsatzTV.Application/Configuration/XmltvTimeZone.cs
  9. 1
      ErsatzTV.Core/Domain/ConfigElementKey.cs
  10. 35
      ErsatzTV/Pages/Settings.razor
  11. 1
      ErsatzTV/Program.cs

2
CHANGELOG.md

@ -29,6 +29,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -29,6 +29,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add *experimental* new streaming mode `HLS Segmenter V2`
- In my initial testing, this streaming mode produces significantly fewer playback warnings/errors
- If it tests well for others, it *may* replace the current `HLS Segmenter` in a future release
- Add setting to change XMLTV data from `Local` time zone to `UTC`
- This is needed because some clients (incorrectly) ignore time zone specifier and require UTC times
### Fixed
- Fix antiforgery error caused by reusing existing browser tabs across docker container restarts

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

@ -1,10 +1,12 @@ @@ -1,10 +1,12 @@
using System.Globalization;
using System.Xml;
using ErsatzTV.Application.Configuration;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Emby;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Jellyfin;
using ErsatzTV.Core.Streaming;
using ErsatzTV.Infrastructure.Data;
@ -22,6 +24,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -22,6 +24,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly ILocalFileSystem _localFileSystem;
private readonly IConfigElementRepository _configElementRepository;
private readonly ILogger<RefreshChannelDataHandler> _logger;
private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
@ -29,16 +32,20 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -29,16 +32,20 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
RecyclableMemoryStreamManager recyclableMemoryStreamManager,
IDbContextFactory<TvContext> dbContextFactory,
ILocalFileSystem localFileSystem,
IConfigElementRepository configElementRepository,
ILogger<RefreshChannelDataHandler> logger)
{
_recyclableMemoryStreamManager = recyclableMemoryStreamManager;
_dbContextFactory = dbContextFactory;
_localFileSystem = localFileSystem;
_configElementRepository = configElementRepository;
_logger = logger;
}
public async Task Handle(RefreshChannelData request, CancellationToken cancellationToken)
{
_logger.LogDebug("Refreshing channel data (XMLTV) for channel {Channel}", request.ChannelNumber);
_localFileSystem.EnsureFolderExists(FileSystemLayout.ChannelGuideCacheFolder);
string movieTemplateFileName = GetMovieTemplateFileName();
@ -222,7 +229,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -222,7 +229,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
File.Move(tempFile, targetFile, true);
}
private static async Task WritePlayoutXml(
private async Task WritePlayoutXml(
RefreshChannelData request,
List<PlayoutItem> sorted,
XmlTemplateContext templateContext,
@ -234,6 +241,10 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -234,6 +241,10 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
XmlMinifier minifier,
XmlWriter xml)
{
XmltvTimeZone xmltvTimeZone = await _configElementRepository
.GetValue<XmltvTimeZone>(ConfigElementKey.XmltvTimeZone)
.IfNoneAsync(XmltvTimeZone.Local);
// skip all filler that isn't pre-roll
var i = 0;
while (i < sorted.Count && sorted[i].FillerKind != FillerKind.None &&
@ -278,13 +289,27 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -278,13 +289,27 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
PlayoutItem finishItem = sorted[finishIndex];
i = finishIndex;
string start = startItem.StartOffset.ToString("yyyyMMddHHmmss zzz", CultureInfo.InvariantCulture)
DateTimeOffset startTime = xmltvTimeZone switch
{
XmltvTimeZone.Utc => new DateTimeOffset(startItem.Start, TimeSpan.Zero),
_ => startItem.StartOffset
};
DateTimeOffset stopTime = (xmltvTimeZone, displayItem.GuideFinishOffset.HasValue) switch
{
(XmltvTimeZone.Utc, true) => new DateTimeOffset(displayItem.GuideFinish!.Value, TimeSpan.Zero),
(XmltvTimeZone.Utc, false) => new DateTimeOffset(finishItem.Finish, TimeSpan.Zero),
(_, true) => displayItem.GuideFinishOffset!.Value,
(_, false) => finishItem.FinishOffset
};
string start = startTime
.ToString("yyyyMMddHHmmss zzz", CultureInfo.InvariantCulture)
.Replace(":", string.Empty);
string stop = stopTime
.ToString("yyyyMMddHHmmss zzz", CultureInfo.InvariantCulture)
.Replace(":", string.Empty);
string stop = displayItem.GuideFinishOffset.HasValue
? displayItem.GuideFinishOffset.Value.ToString("yyyyMMddHHmmss zzz", CultureInfo.InvariantCulture)
.Replace(":", string.Empty)
: finishItem.FinishOffset.ToString("yyyyMMddHHmmss zzz", CultureInfo.InvariantCulture)
.Replace(":", string.Empty);
await WriteItemToXml(
request,

5
ErsatzTV.Application/Configuration/Commands/UpdateXmltvSettings.cs

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.Configuration;
public record UpdateXmltvSettings(XmltvSettingsViewModel XmltvSettings) : IRequest<Either<BaseError, Unit>>;

34
ErsatzTV.Application/Configuration/Commands/UpdateXmltvSettingsHandler.cs

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
using System.Threading.Channels;
using ErsatzTV.Application.Channels;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Configuration;
public class UpdateXmltvSettingsHandler(
IDbContextFactory<TvContext> dbContextFactory,
IConfigElementRepository configElementRepository,
ChannelWriter<IBackgroundServiceRequest> workerChannel)
: IRequestHandler<UpdateXmltvSettings, Either<BaseError, Unit>>
{
public async Task<Either<BaseError, Unit>> Handle(
UpdateXmltvSettings request,
CancellationToken cancellationToken) => await ApplyUpdate(request.XmltvSettings);
private async Task<Unit> ApplyUpdate(XmltvSettingsViewModel xmltvSettings)
{
await configElementRepository.Upsert(ConfigElementKey.XmltvTimeZone, xmltvSettings.TimeZone);
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
foreach (string channelNumber in await dbContext.Channels.Map(c => c.Number).ToListAsync())
{
await workerChannel.WriteAsync(new RefreshChannelData(channelNumber));
}
return Unit.Default;
}
}

3
ErsatzTV.Application/Configuration/Queries/GetXmltvSettings.cs

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Configuration;
public record GetXmltvSettings : IRequest<XmltvSettingsViewModel>;

19
ErsatzTV.Application/Configuration/Queries/GetXmltvSettingsHandler.cs

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Configuration;
public class GetXmltvSettingsHandler(IConfigElementRepository configElementRepository)
: IRequestHandler<GetXmltvSettings, XmltvSettingsViewModel>
{
public async Task<XmltvSettingsViewModel> Handle(GetXmltvSettings request, CancellationToken cancellationToken)
{
Option<XmltvTimeZone> maybeTimeZone =
await configElementRepository.GetValue<XmltvTimeZone>(ConfigElementKey.XmltvTimeZone);
return new XmltvSettingsViewModel
{
TimeZone = await maybeTimeZone.IfNoneAsync(XmltvTimeZone.Local)
};
}
}

6
ErsatzTV.Application/Configuration/XmltvSettingsViewModel.cs

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
namespace ErsatzTV.Application.Configuration;
public class XmltvSettingsViewModel
{
public XmltvTimeZone TimeZone { get; set; }
}

7
ErsatzTV.Application/Configuration/XmltvTimeZone.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
namespace ErsatzTV.Application.Configuration;
public enum XmltvTimeZone
{
Local = 0,
Utc = 1
}

1
ErsatzTV.Core/Domain/ConfigElementKey.cs

@ -42,4 +42,5 @@ public class ConfigElementKey @@ -42,4 +42,5 @@ public class ConfigElementKey
public static ConfigElementKey LibraryRefreshInterval => new("scanner.library_refresh_interval");
public static ConfigElementKey PlayoutDaysToBuild => new("playout.days_to_build");
public static ConfigElementKey PlayoutSkipMissingItems => new("playout.skip_missing_items");
public static ConfigElementKey XmltvTimeZone => new("xmltv.time_zone");
}

35
ErsatzTV/Pages/Settings.razor

@ -291,6 +291,27 @@ @@ -291,6 +291,27 @@
</MudButton>
</MudCardActions>
</MudCard>
<MudCard Class="mb-6" Style="width: 350px">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h6">Program Guide (XMLTV)</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudSelect Class="mt-3"
Label="XMLTV Time Zone"
@bind-Value="_xmltvSettings.TimeZone"
For="@(() => _xmltvSettings.TimeZone)">
<MudSelectItem Value="@XmltvTimeZone.Local">Local</MudSelectItem>
<MudSelectItem Value="@XmltvTimeZone.Utc">UTC</MudSelectItem>
</MudSelect>
</MudCardContent>
<MudCardActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveXmltvSettings())" StartIcon="@Icons.Material.Filled.Save">
Save Settings
</MudButton>
</MudCardActions>
</MudCard>
</MudStack>
</MudGrid>
</MudContainer>
@ -312,6 +333,7 @@ @@ -312,6 +333,7 @@
private int _libraryRefreshInterval;
private PlayoutSettingsViewModel _playoutSettings = new();
private GeneralSettingsViewModel _generalSettings = new();
private XmltvSettingsViewModel _xmltvSettings = new();
public void Dispose()
{
@ -336,6 +358,7 @@ @@ -336,6 +358,7 @@
_playoutSettings = await Mediator.Send(new GetPlayoutSettings(), _cts.Token);
_playoutSuccess = _playoutSettings.DaysToBuild > 0;
_generalSettings = await Mediator.Send(new GetGeneralSettings(), _cts.Token);
_xmltvSettings = await Mediator.Send(new GetXmltvSettings(), _cts.Token);
await RefreshCustomResolutions();
}
@ -420,6 +443,18 @@ @@ -420,6 +443,18 @@
},
Right: _ => Snackbar.Add("Successfully saved general settings", Severity.Success));
}
private async Task SaveXmltvSettings()
{
Either<BaseError, Unit> result = await Mediator.Send(new UpdateXmltvSettings(_xmltvSettings), _cts.Token);
result.Match(
Left: error =>
{
Snackbar.Add(error.Value, Severity.Error);
Logger.LogError("Unexpected error saving xmltv settings: {Error}", error.Value);
},
Right: _ => Snackbar.Add("Successfully saved xmltv settings", Severity.Success));
}
private async Task RefreshCustomResolutions() => _customResolutions = await Mediator.Send(new GetAllResolutions(), _cts.Token)
.Map(list => list.Filter(r => r.IsCustom).ToList());

1
ErsatzTV/Program.cs

@ -79,6 +79,7 @@ public class Program @@ -79,6 +79,7 @@ public class Program
.MinimumLevel.Override("ErsatzTV.FFmpeg", LoggingLevelSwitches.StreamingLevelSwitch)
.MinimumLevel.Override("ErsatzTV.Core.FFmpeg.FFmpegLibraryProcessService", LoggingLevelSwitches.StreamingLevelSwitch)
.MinimumLevel.Override("ErsatzTV.Controllers.IptvController", LoggingLevelSwitches.StreamingLevelSwitch)
.MinimumLevel.Override("ErsatzTV.Controllers.InternalController", LoggingLevelSwitches.StreamingLevelSwitch)
// http
.MinimumLevel.Override("Serilog.AspNetCore.RequestLoggingMiddleware", LoggingLevelSwitches.HttpLevelSwitch)

Loading…
Cancel
Save