diff --git a/ErsatzTV.Application/Configuration/Queries/GetMpegTsScripts.cs b/ErsatzTV.Application/Configuration/Queries/GetMpegTsScripts.cs new file mode 100644 index 000000000..07d680d85 --- /dev/null +++ b/ErsatzTV.Application/Configuration/Queries/GetMpegTsScripts.cs @@ -0,0 +1,5 @@ +using ErsatzTV.Core.FFmpeg; + +namespace ErsatzTV.Application.Configuration; + +public record GetMpegTsScripts : IRequest>; diff --git a/ErsatzTV.Application/Configuration/Queries/GetMpegTsScriptsHandler.cs b/ErsatzTV.Application/Configuration/Queries/GetMpegTsScriptsHandler.cs new file mode 100644 index 000000000..87ee2c89f --- /dev/null +++ b/ErsatzTV.Application/Configuration/Queries/GetMpegTsScriptsHandler.cs @@ -0,0 +1,14 @@ +using ErsatzTV.Core.FFmpeg; +using ErsatzTV.Core.Interfaces.FFmpeg; + +namespace ErsatzTV.Application.Configuration; + +public class GetMpegTsScriptsHandler(IMpegTsScriptService mpegTsScriptService) + : IRequestHandler> +{ + public async Task> Handle(GetMpegTsScripts request, CancellationToken cancellationToken) + { + await mpegTsScriptService.RefreshScripts(); + return mpegTsScriptService.GetScripts().OrderBy(x => x.Name).ToList(); + } +} diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs index 2d97809a5..fc40c28c1 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs @@ -85,6 +85,10 @@ public class UpdateFFmpegSettingsHandler : IRequestHandler +public class GetFFmpegSettingsHandler(IConfigElementRepository configElementRepository) + : IRequestHandler { - private readonly IConfigElementRepository _configElementRepository; - - public GetFFmpegSettingsHandler(IConfigElementRepository configElementRepository) => - _configElementRepository = configElementRepository; - public async Task Handle( GetFFmpegSettings request, CancellationToken cancellationToken) { - Option ffmpegPath = await _configElementRepository.GetValue(ConfigElementKey.FFmpegPath, cancellationToken); - Option ffprobePath = await _configElementRepository.GetValue(ConfigElementKey.FFprobePath, cancellationToken); + Option ffmpegPath = await configElementRepository.GetValue( + ConfigElementKey.FFmpegPath, + cancellationToken); + Option ffprobePath = await configElementRepository.GetValue( + ConfigElementKey.FFprobePath, + cancellationToken); Option defaultFFmpegProfileId = - await _configElementRepository.GetValue(ConfigElementKey.FFmpegDefaultProfileId, cancellationToken); + await configElementRepository.GetValue(ConfigElementKey.FFmpegDefaultProfileId, cancellationToken); Option saveReports = - await _configElementRepository.GetValue(ConfigElementKey.FFmpegSaveReports, cancellationToken); + await configElementRepository.GetValue(ConfigElementKey.FFmpegSaveReports, cancellationToken); Option preferredAudioLanguageCode = - await _configElementRepository.GetValue(ConfigElementKey.FFmpegPreferredLanguageCode, cancellationToken); + await configElementRepository.GetValue( + ConfigElementKey.FFmpegPreferredLanguageCode, + cancellationToken); Option useEmbeddedSubtitles = - await _configElementRepository.GetValue(ConfigElementKey.FFmpegUseEmbeddedSubtitles, cancellationToken); + await configElementRepository.GetValue( + ConfigElementKey.FFmpegUseEmbeddedSubtitles, + cancellationToken); Option extractEmbeddedSubtitles = - await _configElementRepository.GetValue(ConfigElementKey.FFmpegExtractEmbeddedSubtitles, cancellationToken); + await configElementRepository.GetValue( + ConfigElementKey.FFmpegExtractEmbeddedSubtitles, + cancellationToken); Option watermark = - await _configElementRepository.GetValue(ConfigElementKey.FFmpegGlobalWatermarkId, cancellationToken); + await configElementRepository.GetValue(ConfigElementKey.FFmpegGlobalWatermarkId, cancellationToken); Option fallbackFiller = - await _configElementRepository.GetValue(ConfigElementKey.FFmpegGlobalFallbackFillerId, cancellationToken); + await configElementRepository.GetValue( + ConfigElementKey.FFmpegGlobalFallbackFillerId, + cancellationToken); Option hlsSegmenterIdleTimeout = - await _configElementRepository.GetValue(ConfigElementKey.FFmpegSegmenterTimeout, cancellationToken); + await configElementRepository.GetValue(ConfigElementKey.FFmpegSegmenterTimeout, cancellationToken); Option workAheadSegmenterLimit = - await _configElementRepository.GetValue(ConfigElementKey.FFmpegWorkAheadSegmenters, cancellationToken); + await configElementRepository.GetValue(ConfigElementKey.FFmpegWorkAheadSegmenters, cancellationToken); Option initialSegmentCount = - await _configElementRepository.GetValue(ConfigElementKey.FFmpegInitialSegmentCount, cancellationToken); + await configElementRepository.GetValue(ConfigElementKey.FFmpegInitialSegmentCount, cancellationToken); Option outputFormatKind = - await _configElementRepository.GetValue(ConfigElementKey.FFmpegHlsDirectOutputFormat, cancellationToken); + await configElementRepository.GetValue( + ConfigElementKey.FFmpegHlsDirectOutputFormat, + cancellationToken); + Option defaultMpegTsScript = + await configElementRepository.GetValue( + ConfigElementKey.FFmpegDefaultMpegTsScript, + cancellationToken); var result = new FFmpegSettingsViewModel { @@ -52,7 +66,8 @@ public class GetFFmpegSettingsHandler : IRequestHandler( ConfigElementKey.FFmpegHlsDirectOutputFormat, cancellationToken); + Option defaultMpegTsScript = + await _configElementRepository.GetValue( + ConfigElementKey.FFmpegDefaultMpegTsScript, + cancellationToken); var result = new FFmpegSettingsViewModel { @@ -249,7 +253,8 @@ public class GetTroubleshootingInfoHandler : IRequestHandler new("ffmpeg.segmenter.work_ahead_limit"); public static ConfigElementKey FFmpegInitialSegmentCount => new("ffmpeg.segmenter.initial_segment_count"); public static ConfigElementKey FFmpegHlsDirectOutputFormat => new("ffmpeg.hls_direct.output_format"); + public static ConfigElementKey FFmpegDefaultMpegTsScript => new("ffmpeg.default_mpegts_script"); public static ConfigElementKey SearchIndexVersion => new("search_index.version"); public static ConfigElementKey HDHRTunerCount => new("hdhr.tuner_count"); public static ConfigElementKey HDHRUUID => new("hdhr.uuid"); diff --git a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs index 48ca2a0ec..9d6e1d176 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs @@ -826,7 +826,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService Channel channel, string scheme, string host, - string accessToken) + string accessToken, + CancellationToken cancellationToken) { var resolution = new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height); @@ -844,10 +845,13 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService } // TODO: save reports? + string defaultScript = await _configElementRepository + .GetValue(ConfigElementKey.FFmpegDefaultMpegTsScript, cancellationToken) + .IfNoneAsync("Default"); List allScripts = _mpegTsScriptService.GetScripts(); foreach (var script in allScripts.Where(s => string.Equals( s.Name, - "Default", + defaultScript, StringComparison.OrdinalIgnoreCase))) { Option maybeCommand = await _mpegTsScriptService.Execute( diff --git a/ErsatzTV.Core/FFmpeg/MpegTsScript.cs b/ErsatzTV.Core/FFmpeg/MpegTsScript.cs index 31f435ead..97639861a 100644 --- a/ErsatzTV.Core/FFmpeg/MpegTsScript.cs +++ b/ErsatzTV.Core/FFmpeg/MpegTsScript.cs @@ -4,6 +4,9 @@ namespace ErsatzTV.Core.FFmpeg; public class MpegTsScript { + [YamlIgnore] + public string Id { get; set; } + [YamlMember(Alias = "name", ApplyNamingConventions = false)] public string Name { get; set; } diff --git a/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs b/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs index fd6ef6b31..096b827dc 100644 --- a/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs +++ b/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs @@ -65,7 +65,8 @@ public interface IFFmpegProcessService Channel channel, string scheme, string host, - string accessToken); + string accessToken, + CancellationToken cancellationToken); Task ResizeImage(string ffmpegPath, string inputFile, string outputFile, int height); diff --git a/ErsatzTV.Infrastructure/FFmpeg/MpegTsScriptService.cs b/ErsatzTV.Infrastructure/FFmpeg/MpegTsScriptService.cs index 39410a47d..a9d32514b 100644 --- a/ErsatzTV.Infrastructure/FFmpeg/MpegTsScriptService.cs +++ b/ErsatzTV.Infrastructure/FFmpeg/MpegTsScriptService.cs @@ -31,6 +31,7 @@ public class MpegTsScriptService( Option maybeScript = FromYaml(await localFileSystem.ReadAllText(definition)); foreach (var script in maybeScript) { + script.Id = Path.GetFileName(folder); Scripts[folder] = script; } } diff --git a/ErsatzTV/Pages/Settings/FFmpegSettings.razor b/ErsatzTV/Pages/Settings/FFmpegSettings.razor index 25414463b..1c4c94455 100644 --- a/ErsatzTV/Pages/Settings/FFmpegSettings.razor +++ b/ErsatzTV/Pages/Settings/FFmpegSettings.razor @@ -1,10 +1,12 @@ @page "/settings/ffmpeg" +@using ErsatzTV.Application.Configuration @using ErsatzTV.Application.FFmpegProfiles @using ErsatzTV.Application.Filler @using ErsatzTV.Application.MediaItems @using ErsatzTV.Application.Resolutions @using ErsatzTV.Application.Watermarks @using ErsatzTV.Core.Domain.Filler +@using ErsatzTV.Core.FFmpeg @using ErsatzTV.FFmpeg.OutputFormat @implements IDisposable @inject IMediator Mediator @@ -124,6 +126,17 @@ MKV + +
+ Default MPEG-TS Script +
+ + @foreach (MpegTsScript script in _mpegTsScripts) + { + @script.Name + } + +
Custom Resolutions @@ -162,6 +175,7 @@ private List _watermarks = []; private List _fillerPresets = []; private List _customResolutions = []; + private readonly List _mpegTsScripts = []; public void Dispose() { @@ -187,6 +201,9 @@ _fillerPresets = await Mediator.Send(new GetAllFillerPresets(), token) .Map(list => list.Filter(fp => fp.FillerKind == FillerKind.Fallback).ToList()); + _mpegTsScripts.Clear(); + _mpegTsScripts.AddRange(await Mediator.Send(new GetMpegTsScripts(), token)); + await RefreshCustomResolutions(token); } catch (OperationCanceledException)